2.34. Динамічний код¶
Три вбудовані функції приймають рядок вихідного коду Python і виконують його: eval(), exec() та compile(). Разом вони дозволяють коду конструювати та виконувати більше коду під час виконання — що іноді є саме правильним інструментом, але значно частіше є джерелом помилок і дірок безпеки.
Попередження
Ці функції виконують довільний код Python. Передача введення користувача до eval або exec — рядка з файлу, який користувач може редагувати, payload, отриманого через мережу, значення, введеного в REPL, — дозволяє цьому введенню робити все, що може робити викликаючий скрипт, включаючи видалення кожного файлу на пристрої. Використовуйте їх обдумано, ніколи всередині коду, що запускається автоматично, і ніколи на даних, які ви не контролюєте.
2.34.1. eval¶
eval() виконує один вираз і повертає його значення:
>>> eval("3 * 7")
21
>>> name = "OpenMV"
>>> eval("name.lower()")
'openmv'
Вираз бачить глобальні та локальні змінні виклику за замовчуванням, саме тому name розпізнається у другому прикладі. Передача явних словників дозволяє ізолювати обчислення:
eval("a + b", {"__builtins__": None}, {"a": 1, "b": 2})
Навіть в ізольованому середовищі eval є небезпечним. Існують відомі техніки для виходу з таких ізольованих середовищ; не покладайтеся лише на трюк з порожнім __builtins__ для ненадійного введення.
2.34.2. exec¶
exec() виконує блок коду, а не один вираз — оператори, визначення функцій, цикли — і повертає None:
exec("for i in range(3): print(i)")
Виведення:
0
1
2
Блок може визначати імена, які стають доступними після, з деякими застереженнями щодо локальної та глобальної областей видимості. exec всередині функції рідко поводиться так, як очікує автор; якщо вам це потрібно, запускайте його на рівні модуля.
2.34.3. compile¶
compile() перетворює рядок джерельного коду на об’єкт коду, який пізніше можна передати до eval() або exec(). Використовуйте його, коли те саме джерело буде виконуватися багато разів — аналіз відбувається один раз, виконання швидше:
expr = compile("x * x", "<expr>", "eval")
for x in range(5):
print(eval(expr))
Виведення:
0
1
4
9
16
Другий аргумент — це мітка, яка з’являється у трасуваннях, якщо код викидає виключення. Третій аргумент — "eval" для одного виразу, "exec" для блоку або "single" для оператора в інтерактивному стилі, який виводить свій результат.
2.34.4. Коли варто використовувати¶
Майже ніколи. Більшість випадків використання, які уявляють початківці, мають безпечніші альтернативи:
Читання конфігураційного файлу. Використовуйте
json— структуровані дані, без виконання.Обчислення числового значення, введеного користувачем. Використовуйте
int()/float()для аналізу, потім арифметику. Якщо користувачу справді потрібно ввести формулу, використовуйте невеликий парсер виразів, а неeval.
Коли вам справді потрібні eval / exec / compile, ізолюйте місце виклику, журналюйте точний рядок, який збирається виконатися, і ставтеся до джерела як до найбільш підозрілої речі у вашому коді.