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, ізолюйте місце виклику, журналюйте точний рядок, який збирається виконатися, і ставтеся до джерела як до найбільш підозрілої речі у вашому коді.