2.34. Kod dynamiczny¶
Trzy funkcje wbudowane przyjmują łańcuch ze źródłem Pythona i uruchamiają go: eval(), exec() oraz compile(). Razem pozwalają one kodowi konstruować i wykonywać więcej kodu w czasie działania – co czasami jest dokładnie właściwym narzędziem, a znacznie częściej źródłem błędów i luk bezpieczeństwa.
Ostrzeżenie
Te funkcje wykonują dowolny kod Pythona. Przekazanie danych wejściowych użytkownika do eval lub exec – łańcucha z pliku, który użytkownik może edytować, ładunku odebranego przez sieć, wartości wpisanej w wierszu poleceń REPL – pozwala tym danym zrobić wszystko, co mógłby zrobić wywołujący skrypt, aż po usunięcie każdego pliku na urządzeniu włącznie. Używaj ich rozważnie, nigdy wewnątrz kodu uruchamianego automatycznie i nigdy na danych, których nie kontrolujesz.
2.34.1. eval¶
eval() uruchamia pojedyncze wyrażenie i zwraca jego wartość:
>>> eval("3 * 7")
21
>>> name = "OpenMV"
>>> eval("name.lower()")
'openmv'
Wyrażenie domyślnie widzi zmienne globalne i lokalne wywołującego, dlatego name jest rozwiązywane w drugim przykładzie. Przekazanie jawnych słowników pozwala odizolować ewaluację w piaskownicy:
eval("a + b", {"__builtins__": None}, {"a": 1, "b": 2})
Nawet w piaskownicy eval jest niebezpieczne. Istnieją dobrze znane techniki ucieczki z takich piaskownic; nie polegaj na samej sztuczce z pustym __builtins__ w przypadku niezaufanych danych wejściowych.
2.34.2. exec¶
exec() uruchamia blok kodu zamiast pojedynczego wyrażenia – instrukcje, definicje funkcji, pętle – i zwraca None:
exec("for i in range(3): print(i)")
Wynik:
0
1
2
Blok może definiować nazwy, które stają się dostępne później, z pewnymi zastrzeżeniami dotyczącymi zakresu lokalnego i globalnego. exec wewnątrz funkcji rzadko zachowuje się tak, jak oczekuje piszący; jeśli go potrzebujesz, uruchamiaj go na poziomie modułu.
2.34.3. compile¶
compile() zamienia łańcuch źródłowy w obiekt kodu, który można później przekazać do eval() lub exec(). Użyj go, gdy to samo źródło ma być uruchamiane wiele razy – parsowanie odbywa się raz, a wykonanie jest szybsze:
expr = compile("x * x", "<expr>", "eval")
for x in range(5):
print(eval(expr))
Wynik:
0
1
4
9
16
Środkowy argument to etykieta, która pojawia się w śladach stosu (tracebackach), jeśli kod zgłosi wyjątek. Trzeci argument to "eval" dla pojedynczego wyrażenia, "exec" dla bloku lub "single" dla instrukcji w stylu interaktywnym, która wypisuje swój wynik.
2.34.4. Kiedy sięgać po te narzędzia¶
Prawie nigdy. Większość zastosowań, jakie wyobrażają sobie początkujący, ma bezpieczniejsze alternatywy:
Odczyt pliku konfiguracyjnego. Użyj
json– dane strukturalne, bez wykonywania kodu.Ewaluacja wartości liczbowej wpisanej przez użytkownika. Użyj
int()/float()do parsowania, a następnie wykonaj działania arytmetyczne. Jeśli użytkownik naprawdę musi wprowadzić formułę, użyj małego parsera wyrażeń, a nieeval.
Gdy naprawdę potrzebujesz eval / exec / compile, odizoluj miejsce wywołania, rejestruj dokładny łańcuch, który ma zostać wykonany, i traktuj to źródło jako najbardziej podejrzaną rzecz w swoim kodzie.