Co robi eval () Pythona?


305

W książce, którą czytam na temat Pythona, nadal używa kodu eval(input('blah'))

Przeczytałem dokumentację i rozumiem ją, ale wciąż nie widzę, jak zmienia ona input()funkcję.

Co to robi? Czy ktoś może wyjaśnić?


4
Funkcja Eval próbuje wykonać i zinterpretować przekazany do niej ciąg (argument) jako kod Pythona. x = 1 print (eval ('x + 1')) Wyjście powyższego kodu będzie wynosić 2. Wadą takiego podejścia jest to, że użytkownik uzyskuje niezależność pisania kodu, co może prowadzić do spustoszenia. Chociaż można ograniczyć użytkowników z dostęp do wielu zmiennych i metod poprzez przekazanie globalnego i lokalnego parametru w funkcji eval.
ATIF IBAD KHAN

Odpowiedzi:


276

Funkcja eval pozwala programowi Python uruchamiać kod Pythona wewnątrz siebie.

przykład eval (powłoka interaktywna):

>>> x = 1
>>> eval('x + 1')
2
>>> eval('x')
1

25
haha, to był trywialny przykład, ale możesz pozwolić użytkownikowi wpisać dowolne polecenie i pozwolić na wykonanie go przez Python. Aby użytkownik mógł wpisać ciąg znaków w poleceniu, a następnie uruchomić kod w Pythonie. Na przykład: eval („__ import __ ('os'). Remove ('file')”).
BYS2

60
Będzie to wydawać się bezużyteczne, dopóki nie znajdziesz takiej potrzeby. Jest używany w witrynach takich jak codepad.org, aby umożliwić wykonywanie skryptów w środowisku testowym. eval()może być również używany do wykonywania wysoce dynamicznego kodu, ale przed użyciem go należy w pełni zdawać sobie sprawę z zagrożeń bezpieczeństwa i wydajności.
George Cummins

6
@GeorgeCummins, codepad.org nie używa evalani nie może robić tego, co robi eval.
Mike Graham

16
@GeorgeCummins: codepag.org uruchamia wszystko w piaskownicy: więzienie chroot z kontrolą ptrace na maszynie wirtualnej, aby zapobiec szkodliwemu kodowi. O wiele bardziej skomplikowane niż zwykła ewaluacja. Ponadto eval jest specyficzny dla Pythona. codepad obsługuje wiele języków.
FogleBird,

4
@GeorgeCummins, codepad działa bardzo złożony system do bezpiecznego uruchamiania dowolnych programów. eval, poza niebezpieczeństwem, nie może uruchamiać całych programów, jak robi to w programie codepad, ponieważ może ocenić tylko jedno wyrażenie.
Mike Graham

165

eval()interpretuje ciąg jako kod. Powodem, dla którego tak wiele osób ostrzegło cię przed użyciem tego, jest to, że użytkownik może użyć tego jako opcji uruchamiania kodu na komputerze. Jeśli masz eval(input())i oszaimportowałeś, osoba może wpisać, w input() os.system('rm -R *')którym skasują wszystkie twoje pliki z twojego katalogu domowego. (Zakładając, że masz system uniksowy). Korzystanie eval()jest dziurą w zabezpieczeniach. Jeśli chcesz przekonwertować ciągi znaków na inne formaty, spróbuj użyć rzeczy, które to robią, takich jak int().


14
Masz na myśli używając evalze input()jest dziura bezpieczeństwa. Nie umieszczaj input()w ewaluacji oświadczenia, a wszystko będzie dobrze.
Rohmer

19
@Rohmer, niebezpieczne dane mogą pochodzić z dowolnego miejsca: żądania sieciowe, pola wprowadzania formularzy, odczyty plików ... nie tylko z danych wejściowych konsoli. Nawet jeśli sam napiszesz pliki, mogą one nadal zawierać dane wejściowe pochodzące z niezaufanego źródła. Podobnie evaljest w wielu przypadkach kwestia bezpieczeństwa.
sanderd17

3
ponieważ inputzwykle pobiera dane z konsoli, użytkownik może po prostu wyjść z programu i wpisać rm -R *mimo wszystko ...
cz

63

Wiele dobrych odpowiedzi tutaj, ale żadna nie opisuje użycia eval()w kontekście jego globalsi localskwargs, tj. eval(expression, globals=None, locals=None)(Patrz eval tutaj dokumenty ).

Można ich użyć do ograniczenia funkcji dostępnych za pośrednictwem evalfunkcji. Na przykład, jeśli załadujesz świeżego interpretera Pythona, locals()i globals()będą takie same i będą wyglądać mniej więcej tak:

>>>globals()
{'__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__doc__': None,
 '__spec__': None, '__builtins__': <module 'builtins' (built-in)>,
 '__package__': None, '__name__': '__main__'}

Z pewnością w builtinsmodule znajdują się funkcje , które mogą wyrządzić znaczne szkody w systemie. Możliwe jest jednak zablokowanie czegokolwiek i wszystkiego, czego nie chcemy, aby były dostępne. Weźmy przykład. Powiedzmy, że chcemy zbudować listę reprezentującą domenę dostępnych rdzeni w systemie. Dla mnie mam 8 rdzeni, więc chciałbym listę [1, 8].

>>>from os import cpu_count
>>>eval('[1, cpu_count()]')
[1, 8]

Podobnie wszystko __builtins__jest dostępne.

>>>eval('abs(-1)')
1

Ok. Widzimy więc jedną funkcję, którą chcemy ujawnić, oraz przykład jednej (z wielu, które mogą być znacznie bardziej złożone) metody, której nie chcemy ujawniać. Więc zablokujmy wszystko.

>>>eval('[1, cpu_count()]', {'__builtins__':None}, {})
TypeError: 'NoneType' object is not subscriptable

Skutecznie zablokowaliśmy wszystkie __builtins__funkcje i jako taki wprowadziliśmy poziom ochrony do naszego systemu. W tym momencie możemy zacząć dodawać funkcje, które chcemy udostępnić.

>>>from os import cpu_count
>>>exposed_methods = {'cpu_count': cpu_count}
>>>eval('cpu_count()', {'__builtins__':None}, exposed_methods)
8
>>>eval('abs(cpu_count())', {'__builtins__':None}, exposed_methods)
TypeError: 'NoneType' object is not subscriptable

Teraz mamy cpu_countdostępną funkcję, wciąż blokując wszystko, czego nie chcemy. Moim zdaniem jest to bardzo potężne i wyraźnie z zakresu innych odpowiedzi, a nie powszechne wdrożenie. Istnieje wiele zastosowań czegoś takiego i pod warunkiem, że jest obsługiwany poprawnie, osobiście uważam, że evalmożna go bezpiecznie wykorzystać w świetnej cenie.

NB

Kolejną fajną kwargsrzeczą jest to, że możesz zacząć używać skrótu do swojego kodu. Załóżmy, że używasz eval jako części potoku do wykonania importowanego tekstu. Tekst nie musi mieć dokładnego kodu, może być zgodny z formatem plików szablonów i nadal wykonywać dowolne czynności. Na przykład:

>>>from os import cpu_count
>>>eval('[1,cores]', {'__builtins__': None}, {'cores': cpu_count()})
[1, 8]

29

W Pythonie 2.x input(...)jest równoważne zmianie nazwy eval(raw_input(...))w Pythonie 3.x , co, jak podejrzewam, prowadzi do zamieszania (prawdopodobnie przeglądałeś dokumentację w Pythonie 2.x). Dodatkowo działałby dobrze w Pythonie 3.x, ale podniósłby a w Pythonie 2.raw_inputinputinputeval(input(...))TypeError

W tym przypadku evalsłuży do wymuszenia ciągu zwracanego z inputwyrażenia i interpretowanego. Ogólnie uważa się to za złą praktykę.


Lub jest to książka w języku Python 3.x, inputco oznacza to, co raw_inputzrobiono w wersji 2.x.
dan04,

1
Tak, co przyszło mi do głowy po napisaniu pierwszej odpowiedzi, i tak właśnie jest.
zeekay

6

Może wprowadzający w błąd przykład czytania linii i interpretowania jej.

Spróbuj eval(input())i wpisz "1+1"- powinno to zostać wydrukowane 2. Eval ocenia wyrażenia.


Dlaczego powinienem wpisywać go między cudzysłowami? Wejście pobiera ciąg znaków i przekazuje go do eval, nie wykonując kodu, więc powinienem być w porządku, jeśli po prostu wpisałbym 1 + 1 ... ¿?
JC Rocamonde

Chodzi o to, że miksujesz P2.x i 3.x. W Pythonie 2 twój kod działa, ale nie ma sensu sprawdzać dwukrotnie. W Pythonie 3 tak nie jest i zwraca ciąg znaków.
JC Rocamonde,

6

eval()ocenia przekazany ciąg jako wyrażenie w języku Python i zwraca wynik. Na przykład eval("1 + 1")interpretuje i wykonuje wyrażenie "1 + 1"i zwraca wynik (2).

Jednym z powodów, dla których możesz się mylić, jest to, że cytowany kod zawiera pewien poziom pośredni. Wewnętrzne wywołanie funkcji (wejście) jest wykonywane jako pierwsze, więc użytkownik widzi monit „bla”. Wyobraźmy sobie, że odpowiadają „1 + 1” (cytaty dodane dla zachowania przejrzystości, nie wpisuj ich podczas uruchamiania programu), funkcja wejściowa zwraca ten ciąg, który jest następnie przekazywany do funkcji zewnętrznej (eval), która interpretuje ciąg i zwraca wynik (2).

Przeczytaj więcej o eval tutaj .


6

eval(), jak sama nazwa wskazuje, ocenia przekazany argument.

raw_input()jest teraz input()w wersji Python 3.x. Najczęściej spotykanym przykładem użycia eval()jest użycie tej funkcji w celu zapewnienia funkcjonalności, która jest input()dostępna w wersji Pythona w wersji 2.x. raw_input zwrócił dane wprowadzone przez użytkownika jako ciąg, podczas gdy dane wejściowe oszacowały wartość wprowadzonych danych i zwróciły je.

eval(input("bla bla"))w ten sposób replikuje funkcjonalność input()w 2.x, tj. ocenę danych wprowadzonych przez użytkownika.

W skrócie: eval()ocenia przekazane mu argumenty, a zatem eval('1 + 1')zwrócił 2.


6

Jedną z przydatnych aplikacji eval()jest ocena wyrażeń Pythona na podstawie ciągu znaków. Na przykład załaduj z reprezentacji ciągu słownika pliku:

running_params = {"Greeting":"Hello "}
fout = open("params.dat",'w')
fout.write(repr(running_params))
fout.close()

Przeczytaj jako zmienną i edytuj:

fin = open("params.dat",'r')
diction=eval(fin.read())
diction["Greeting"]+="world"
fin.close()
print diction

Wynik:

{'Greeting': 'Hello world'}

7
Jak to odpowiada na pytanie, na które pytanie eval?
jkd

4

Spóźniłem się z odpowiedzią na to pytanie, ale wydaje się, że nikt nie udzielił jasnej odpowiedzi na to pytanie.

Jeśli użytkownik wprowadzi wartość liczbową, input()zwróci ciąg.

>>> input('Enter a number: ')
Enter a number: 3
>>> '3'
>>> input('Enter a number: ')
Enter a number: 1+1
'1+1'

Tak więc eval()oceni zwracaną wartość (lub wyrażenie), która jest łańcuchem i zwraca liczbę całkowitą / liczbę zmiennoprzecinkową.

>>> eval(input('Enter a number: '))
Enter a number: 1+1
2
>>> 
>>> eval(input('Enter a number: '))
Enter a number: 3.14
3.14

Oczywiście jest to zła praktyka. int()lub float()powinien być użyty zamiast eval()w tym przypadku.

>>> float(input('Enter a number: '))
Enter a number: 3.14
3.14

3

Inną opcją, jeśli chcesz ograniczyć łańcuch oceny do prostych literałów, jest użycie ast.literal_eval(). Kilka przykładów:

import ast

# print(ast.literal_eval(''))          # SyntaxError: unexpected EOF while parsing
# print(ast.literal_eval('a'))         # ValueError: malformed node or string
# print(ast.literal_eval('import os')) # SyntaxError: invalid syntax
# print(ast.literal_eval('1+1'))       # 2: but only works due to a quirk in parser
# print(ast.literal_eval('1*1'))       # ValueError: malformed node or string
print(ast.literal_eval("{'a':1}"))     # {'a':1}

Z dokumentów :

Bezpiecznie oceń węzeł wyrażenia lub ciąg znaków zawierający literał Pythona lub wyświetlacz kontenera. Podany ciąg znaków lub węzeł może składać się wyłącznie z następujących struktur literału Pythona: ciągów, bajtów, liczb, krotek, list, dykt, zestawów, boolanów i Brak.

Można to wykorzystać do bezpiecznej oceny ciągów zawierających wartości Pythona z niezaufanych źródeł bez konieczności samodzielnego analizowania wartości. To nie stanie ocenić dowolnie złożonych wyrażeń, na przykład z udziałem operatorów lub indeksowanie.

Jeśli chodzi o to, dlaczego jest tak ograniczony, z listy mailingowej :

Dopuszczanie wyrażeń operatora z literałami jest możliwe, ale o wiele bardziej skomplikowane niż bieżąca implementacja. Prosta implementacja nie jest bezpieczna: bez problemu można indukować praktycznie nieograniczone użycie procesora i pamięci (spróbuj „9 ** 9 ** 9” lub „[Brak] * 9 ** 9”).

Jeśli chodzi o użyteczność, funkcja ta jest przydatna do „odczytania” dosłownych wartości i pojemników, jak określono w repr (). Można to na przykład wykorzystać do serializacji w formacie, który jest podobny do JSON, ale bardziej wydajny.


1
ast.literal_evalnie obsługuje operatorów, w przeciwieństwie do twojego '1+1'przykładu. Niemniej jednak obsługuje listy, liczby, ciągi itp., A zatem jest dobrą alternatywą dla typowych evalprzypadków użycia.
benjimin

@benjimin oh masz rację - to tylko dziwactwo, że akceptuje 1 + 1! stackoverflow.com/questions/40584417/…
Brian Burns
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.