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, изолируйте место вызова, логируйте точную строку, которая вот-вот будет выполнена, и относитесь к исходному коду как к самой подозрительной вещи в вашем коде.