2.34. Динамический код

Три встроенные функции принимают строку с исходным кодом Python и выполняют её: eval(), exec() и compile(). Вместе они позволяют коду конструировать и выполнять больше кода во время выполнения – что иногда является именно правильным инструментом, а гораздо чаще источником ошибок и брешей в безопасности.

Предупреждение

Эти функции выполняют произвольный код Python. Передача пользовательского ввода в eval или exec – строки из файла, который пользователь может редактировать, полезной нагрузки, полученной по сети, значения, введённого в приглашении 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, изолируйте место вызова, логируйте точную строку, которая вот-вот будет выполнена, и относитесь к исходному коду как к самой подозрительной вещи в вашем коде.