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