Как работает eval() в Python
Функция принимает в качестве аргумента строку, выполняет ее как выражение на Python и возвращает результат. В нее можно передавать выражения, которые возвращают результат, например выполнять математические операции. При этом нельзя передавать целые блоки кода (составные конструкции). Другими словами, запрещено передавать в качестве аргументов условные конструкции с if, циклы с for (для генерации списков, словарей и других объектов использовать for можно) и while, строки с ключевыми словами import, def, class. Если попробовать передать такие аргументы, то вернется SyntaxError.
Рассмотрим синтаксис:
eval(expression, globals=None, locals=None)
Описание параметров:
- expression (обязательный) — строка, содержащая выражение, которое нужно выполнить;
- globals (необязательный) — словарь с доступными глобальными переменными и методами (если None, то могут использоваться все текущие глобальные переменные и методы);
- locals (необязательный) — словарь с доступными локальными методами и переменными (если None, то совпадает с глобальной областью видимости).
Безопасность
Использование eval() может привести к снижению безопасности программы, потому что:
- она выполняет произвольный код, который может передать, в том числе злоумышленник, например, такая строка удаляет все файлы в текущей директории без подтверждения:
"__import__('subprocess').getoutput('rm –rf *')"
- по умолчанию у нее есть доступ ко всем встроенным в Python функциям и объектам, а также к переменным текущей области;
- злоумышленники могут находить способы получения доступа даже в случае, если две проблемы выше решены.
Поэтому eval() не рекомендуется применять без крайней необходимости, но если ее все же включили в код, то есть несколько способов обезопасить программу:
- ограничить глобальную и локальную области видимости
# Все переменные, кроме a, b и c будут недоступны
expression = "a * b + c"
result = eval(expression, {"a": 2, "b": 3}, {"c": 5})
print(result)
# Вывод: 11
- ограничить доступ к __builtins__ (встроенному в Python объекту, содержащему стандартные функции, объекты и исключения)
# Так будет недоступен весь словарь
expression = "sum([1, 2, 3])"
eval(expression, {"__builtins__": {}}, {})
# Вывод: NameError: name 'sum' is not defined
# Если доступ к определенным элементам нужен, то можно предоставить его так
expression = "sum([1, 2, 3])"
eval(expression, {"__builtins__": {}, "sum": sum}, {})
# Вывод: 6
- ограничить имена переменных, функций и других объектов, которые могут использоваться в выражении, переданном на вход
# Можно использовать литералы, операторы и две функции: min() и len()
def safe_eval(expression):
allowed_functions = {"min": min, "len": len}
compiled_code = compile(expression, "<string>", "eval")
for name in compiled_code.co_names:
if name not in allowed_functions:
raise NameError(f"Использовать '{name}' нельзя.")
return eval(compiled_code, {"__builtins__": {}}, allowed_functions)
print(safe_eval("min([1, 2, 3])"))
# Вывод: 1
print(safe_eval("len([1, 2, 3])"))
# Вывод: 3
print(safe_eval("sum([1, 2, 3])"))
# Вывод: NameError…
- исключить использование имен полностью (если это возможно)
# Можно использовать только литералы и операторы
def eval_literals_only(expression):
compiled_code = compile(expression, "<string>", "eval")
if compiled_code.co_names:
raise NameError("Выполнить операцию нельзя.")
return eval(compiled_code , {"__builtins__": {}}, {})
print(eval_literals_only("3.5 + 2.5"))
# Вывод: 6.0
print(eval_literals_only("abs(-5)"))
# Вывод: NameError…
- использовать literal_eval() — функцию, которая способна обработать строки, содержащие только литералы (операторы также не поддерживаются)
from ast import literal_eval
expression = "{'key1': 1, 'key2': 2}"
print(literal_eval(expression))
# Вывод: {'key1': 1, 'key2': 2}
# Такое выражение обработано не будет
expression = "3 + 2"
print(literal_eval(expression))
# ValueError...
Примеры использования
Рассмотрим несколько простых примеров использования eval():
# Математическая операция
expression = "5 + 3 * (3 - 1)"
result = eval(expression)
print(result)
# Вывод: 11
# Обращение к переменным
x = 15
y = 115
expression = "x + y"
result = eval(expression)
print(result)
# Вывод: 130
# Вызов функции
def multiply(a, b):
return a * b
expression = "multiply(3, 5)"
result = eval(expression)
print(result)
# Вывод: 15
# Оценка булева выражения
x = 5
print(eval('x == 4'))
# Вывод: False
В этих случаях использование функции не делает программу более уязвимой, хоть она и имеет доступ к глобальным переменным. Однако такое использование и нецелесообразно: все эти операции можно выполнить проще. — напрямую, без eval(). Поэтому рассмотрим ситуации, когда ее применение оправданно:
- в программах, где пользователи могут задавать собственные фрагменты кода для того, чтобы изменить их поведение под свои потребности. Допустим, есть программа для работы с текстами, в которой пользователи могут преобразовывать свой текст — переводить все символы в верхний регистр и менять их местами:
def process_text(user_script, input_text):
allowed_functions = {"upper": str.upper, "replace": str.replace}
compiled_code = compile(user_script, "<string>", "eval")
for name in compiled_code.co_names:
if name not in allowed_functions and name != "text":
raise NameError(f"Использовать '{name}' нельзя.")
return eval(compiled_code, {"__builtins__": None, "text": input_text}, allowed_functions)
user_input = input("Введите выражение для обработки текста: ")
input_text = input("Введите текст: ")
try:
result = process_text(user_input, input_text)
print(f"Результат: {result}")
except Exception as e:
print(f"Ошибка: {e}")
# Допустим, пользователь ввел команду text.upper().replace('N', 'M') и текст «Line»
# Вывод: LIME
# Теперь пользователь ввел text.lower().replace('N', 'M') и текст «Line»
# Вывод: Ошибка: Использовать 'lower' нельзя.
в приложениях, где пользователи могут ввести математическое выражение, которое нужно вычислить:
def calculate_expression(user_input):
compiled_code = compile(user_input, "<string>", "eval")
if compiled_code.co_names:
raise NameError("Использовать функции нельзя.")
return eval(compiled_code, {"__builtins__": None}, {})
user_input = input("Введите математическое выражение: ")
try:
result = calculate_expression(user_input)
print(f"Результат: {result}")
except Exception as e:
print(f"Ошибка: {e}")
# Допустим, пользователь ввел 15 - 3
# Вывод: 12
# Теперь пользователь ввел abs(-5)
# Вывод: Ошибка: Использовать функции нельзя.
Главное
- eval() — это встроенная в Python функция, которая принимает строку, анализирует и выполняет ее как код, возвращая результат.
- Передавать в нее можно исключительно выражения: если передать составную конструкцию, то вернется SyntaxError.
- В связи с риском возникновения уязвимостей, eval() лучше не использовать, когда можно обойтись без нее.
Если ее необходимо ввести в код, то стоит позаботиться о безопасности: ограничить области видимости и доступ к встроенным функциям и объектам Python. Обозначить те имена переменных, функций, методов и объектов, которые могут использоваться в строке, передаваемой на вход, или же исключить их использование вовсе, применять literal_eval().