Eval error python

О встроенной функции eval(), вопросах ее безопасного и эффективного использования в Python. В конце статьи пишем программу, обобщающую полученные знания.

Функция eval() полезна, когда необходимо выполнить динамически обновляемое выражение Python из какого-либо ввода (например, функции input()), представленного в виде строки или объекта байт-кода. Это невероятно полезный инструмент, но то, что она может выполнять программный код, имеет важные последствия для безопасности, которые следует учесть перед ее применением.

Статья является сокращенным переводом публикации Леоданиса Посо Рамоса Python eval(): Evaluate Expressions Dynamically. Из этого руководства вы узнаете:

  • Как работает eval().
  • Как использовать eval() для динамического выполнения кода.
  • Как минимизировать риски для безопасности, связанные с использованием eval().

Вы можете использовать встроеннyю функцию eval() для динамического исполнения выражений из ввода на основе строки или скомпилированного кода. Если вы передаете в eval() строку, то функция анализирует ее, компилирует в байт-код и выполняет как выражение Python.

Сигнатура eval() определена следующим образом:

        eval(expression[, globals[, locals]])
    

Первый аргумент expression содержит выражение, которое необходимо выполнить. Функция также принимает два необязательных аргумента globals и locals, о которых мы поговорим в соответствующих разделах. Начнём по порядку – с аргумента expression.

Примечание

Для динамического выполнения кода можно также использовать функцию exec(). Основное различие между eval() и exec() состоит в том, что eval() может выполнять лишь выражения, тогда как функции exec() можно «скормить» любой фрагмент кода Python.

Первый аргумент: expression

Когда мы вызываем eval(), содержание expression воспринимается интерпретатором как выражение Python. Посмотрите на следующие примеры, принимающие строковый ввод:

        >>> eval("2 ** 8")
256
>>> eval("1024 + 1024")
2048
>>> eval("sum([8, 16, 32])")
56
>>> x = 100
>>> eval("x * 2")
200
    

При вызове eval() со строковым выражением в качестве аргумента, функция возвращает значение, полученное в результате оценки входной строки. По умолчанию eval() имеет доступ к глобальным именам, таким как x в приведенном выше примере.

Чтобы оценить строковое выражение, eval() выполняет следующую последовательность действий:

  1. Парсинг выражения.
  2. Компилирование в байт-код.
  3. Выполнение кода выражения Python.
  4. Возвращение результата.

Имя аргумента expression подчеркивает, что функция работает только с выражениями, но не составными конструкциями. При попытке передачи блока кода вместо выражения будет получено исключение SyntaxError:

        >>> x = 100
>>> eval("if x: print(x)")
SyntaxError: invalid syntax

    

Таким образом, в eval() нельзя передать конструкции c if, import, def или class, с циклами for и while. Однако ключевое слово for может использоваться в eval() в случае выражений для генераторов списков.

В eval() запрещены и операции присваивания:

        >>> eval("pi = 3.1416")
SyntaxError: invalid syntax
    

SyntaxError также вызывается в случаях, когда eval() не удается распарсить выражение из-за ошибки в записи:

        >>> eval("5 + 7 *")
SyntaxError: unexpected EOF while parsing
    

В eval() можно передавать объекты кода (code objects). Чтобы скомпилировать код, который вы собираетесь передать eval(), можно использовать compile(). Это встроенная функция, которая может компилировать строку в объект кода или AST-объект.

Детали того, как использовать compile(), выходят за рамки этого руководства, но здесь мы кратко рассмотрим первые три обязательных аргумента:

  1. source содержит исходный код, который необходимо скомпилировать. Этот аргумент принимает обычные строки, байтовые строки и объекты AST.
  2. filename определяет файл, из которого прочитан код. Если используется строчный ввод, значение аргумента должно быть равно строке <string>.
  3. mode указывает, какой тип объекта кода мы хотим получить. Если нужно обработать код с помощью eval(), в качестве значения аргумента указывается "eval"

Таким образом, мы можем использовать compile() для предоставления объектов кода вeval() вместо обычных строк:

        >>> code = compile("5 + 4", "<string>", "eval")
>>> eval(code)
9
>>> import math
>>> code = compile("4 / 3 * math.pi * math.pow(25, 3)", "<string>", "eval")
>>> eval(code)
65449.84694978735
    

Использование объектов кода полезно при многократном вызове. Если мы предварительно скомпилируем входное выражение, то последующие вызовы eval() будут выполняться быстрее, так как не будут повторяться шаги синтаксического анализа и компиляции.

Второй аргумент: globals

Аргумент globals опционален. Он содержит словарь, обеспечивающий доступ eval() к глобальному пространству имен. С помощью глобальных переменных можно указать eval(), какие глобальные имена использовать при выполнении выражения.

Глобальные имена – это все те имена, которые доступны в текущей глобальной области или пространстве имен. Вы можете получить к ним доступ из любого места в вашем коде.

Все имена, переданные глобальным переменным в словаре, будут доступны eval() во время выполнения.

        >>> x = 100  # Глобальная переменная
>>> eval("x + 100", {"x": x})
200
>>> y = 200  # Другая глобальная переменная
>>> eval("x + y", {"x": x})
NameError: name 'y' is not defined
    

Любые глобальные имена, определенные вне пользовательского словаря globals, не будут доступны изнутри eval(), будет вызвано исключение NameError.

        >>> eval("x + y", {"x": x, "y": y})
300
    

Вы также можете указать имена, которых нет в текущей глобальной области видимости. Чтобы это работало, нужно указать конкретное значение для каждого имени. Тогда eval() будет интерпретировать эти имена, как если бы это были глобальные переменные:

        >>> eval("x + y + z", {"x": x, "y": y, "z": 300})
600
>>> z  # самой переменной нет в глобальной области видимости
NameError: name 'z' is not defined
    

Если вы предоставите eval() пользовательский словарь, который не содержит значения для ключа "__builtins__", то ссылка на словарь встроенных функций всё равно будет автоматически добавлена к ключу "__builtins__", прежде чем выражение будет проанализировано. Это гарантирует, что eval() имеет полный доступ ко всем встроенным именам Python при оценке выражения.

        >>> eval("sum([2, 2, 2])", {})
6
>>> eval("min([1, 2, 3])", {})
1
>>> eval("pow(10, 2)", {})
100
    

Несмотря на переданный пустой словарь ({}), eval() имеет доступ к встроенным функциям.

При вызове eval() без передачи пользовательского словаря в глобальные переменные аргумент по умолчанию будет использовать словарь, возвращаемый globals() в среде, где вызывается eval():

        >>> x = 100  # Глобальная переменная
>>> y = 200  # Другая глобальная переменная
>>> eval("x + y")
300
    

Таким образом, передача словаря в аргументе globals служит как способ намеренно ограничить область видимость имен для функции eval().

Третий аргумент: locals

Аргумент locals также является необязательным аргументом. В этом случае словарь содержит переменные, которые eval() использует в качестве локальных имен при оценке выражения.

Локальными называются те имена (переменные, функции, классы и т.д.), которые мы определяем внутри данной функции. Локальные имена видны только изнутри включающей функции.

        >>> eval("x + 100", {}, {"x": 100})
200
>>> eval("x + y", {}, {"x": 100})
NameError: name 'y' is not defined
    

Обратите внимание, что для передачи словаря locals сначала необходимо предоставить словарь для globals. Передача по ключу в случае eval() не работает:

        >>> eval("x + 100", locals={"x": 100})
TypeError: eval() takes no keyword arguments
    

Главное практическое различие между globals и locals заключается в том, что Python автоматически вставит ключ "__builtins__" в globals, если этот ключ еще не существует. Cловарь locals остается неизменным во время выполнения eval().

Выполнение выражений с eval()

Функция eval() используется, когда нужно динамически изменять выражения, а применение других техник и инструментов Python требует избыточных усилий. В этом разделе мы обсудим, как использовать eval() для булевых, математических и прочих выражений Python.

Булевы выражения

Булевы выражения – это выражения Python, которые возвращают логическое значение. Обычно они используются для проверки, является ли какое-либо условие истинным или ложным:

        >>> x = 100
>>> y = 100
>>> eval("x != y")
False
>>> eval("x < 200 and y > 100")
False
>>> eval("x is y")
True
>>> eval("x in {50, 100, 150, 200}")
True
    

Зачем же может потребоваться использовать eval() вместо непосредственного применения логического выражения? Предположим, нам нужно реализовать условный оператор, но вы хотите на лету менять условие:

        def func(a, b, condition):
    if eval(condition):
        return a + b
    return a - b
    
        >>> func(2, 4, "a > b")
-2
>>> func(2, 4, "a < b")
6
>>> func(2, 2, "a is b")
4
    

Внутри func() для оценки предоставленного условия используется функция eval(), возвращающая a+b или a-b в соответствии с результатом оценки.

Теперь представьте, как бы вы реализовали то же поведение без eval() для обработки любого логического выражения.

Математические выражения

Один из распространенных вариантов использования eval() в Python – оценка математических выражений из строкового ввода. Например, если вы хотите создать калькулятор на Python, вы можете использовать eval(), чтобы оценить вводимые пользователем данные и вернуть результат вычислений:

        >>> eval("5 + 7")
12
>>> eval("(5 + 7) / 2")
6.0
>>> import math
>>> eval("math.sqrt(math.pow(10, 2) + math.pow(15, 2))")
18.027756377319946
    

Выражения общего вида

Вы можете использовать eval() и с более сложными выражениями Python, включающими вызовы функций, создание объектов, доступ к атрибутам и т. д.

Например, можно вызвать встроенную функцию или функцию, импортированную с помощью стандартного или стороннего модуля. В следующих примерах eval() используется для запуска различных системных команд.

        >>> import subprocess
>>> # Запуск команды echo
>>> eval("subprocess.getoutput('echo Hello, World')")
'Hello, World'
>>> # Запуск Firefox (если он установлен)
>>> eval("subprocess.getoutput('firefox')")
    

Таким образом, можно передавать команды через какой-либо строковый интерфейс (например, форму в браузере) и выполнять код Python.

Однако по той же причине eval() может подвергнуть нас серьезным угрозам безопасности, например, позволит злоумышленнику запускать системные команды или выполнять кода на нашем компьютере. В следующем разделе мы обсудим способы устранения угроз безопасности, связанных с eval().

Минимизация проблем безопасности, связанных с eval()

В связи с проблемами безопасности обычно рекомендуется по возможности не использовать eval(). Но если вы решили, что функция необходима, простое практическое правило состоит в том, чтобы никогда не использовать ее для любого ввода, которому нельзя доверять. Сложность в том, чтобы выяснить, каким видам ввода доверять можно .

В качестве примера того, как безответственное использование eval() может сделать ваш код небезопасным, предположим, что вы хотите создать онлайн-сервис для оценки произвольных выражений Python. Ваш пользователь вводит выражения и нажимает кнопку «Выполнить». Приложение получает пользовательский ввод и передает его для выполнения в eval() .

Если вы используете Linux и приложения имеет необходимые разрешения, то злонамеренный пользователь может ввести опасную строку, подобную следующей:

        "__import__('subprocess').getoutput('rm –rf *')"
    

Выполнение выражения удалит все файлы в текущей директории.

Примечание

__import__() – это встроенная функция, которая принимает имя модуля в виде строки и возвращает ссылку на объект модуля. __import__() – это функция, которая полностью отличается от оператора import. Как мы упоминали выше, вы не можете вызвать оператор импорта с помощью eval().

Ограничение globals и locals

Вы можете ограничить среду выполнения eval(), задавая собственные словари аргументам globals и locals. Например, пустые словари для обоих аргументов, чтобы eval() не мог получить доступ к именам в текущей области или пространстве имен вызывающей стороны:

        >>> x = 100
>>> eval("x * 5", {}, {})
NameError: name 'x' is not defined
    

К сожалению, это ограничение не устраняет другие проблемы безопасности, связанные с использованием eval(), поскольку остается доступ ко всем встроенным именам и функциям Python.

Ограничение __builtins__

Как мы видели ранее, перед синтаксическим анализом выражения eval() автоматически вставляет ссылку на словарь __builtins__ в globals. Злоумышленник может использовать это поведение, используя встроенную функцию __import__(), чтобы получить доступ к стандартной библиотеке или любому стороннему модулю, установленному в системе:

        >>> eval("__import__('math').sqrt(25)", {}, {})
5.0
>>> eval("__import__('subprocess').getoutput('echo Hello, World')", {}, {})
'Hello, World'
    

Чтобы минимизировать риски, можно переопределить __builtins__ в globals:

        >>> eval("__import__('math').sqrt(25)", {"__builtins__": {}}, {})
NameError: name '__import__' is not defined
    

Ограничение имён во входных данных

Однако даже после таких ухищрений Python останется уязвим. Например, можно получить доступ к объекту класса, используя литерал типа, например "", [], {} или (), а также некоторые специальные атрибуты:

        >>> "".__class__.__base__
object
>>> [].__class__.__base__
object
    

Получив доступ к объекту, можно использовать специальный метод .__subclasses__(), чтобы получить доступ ко всем классам, которые наследованы объектом. Вот как это работает:

        for sub_class in ().__class__.__base__.__subclasses__():
    print(sub_class.__name__)
    
        type
weakref
weakcallableproxy
weakproxy
int
bytearray
bytes
list
...
    

Этот код напечатает большой список классов. Некоторые из этих классов довольно мощные и могут быть чрезвычайно опасны в чужих руках. Это открывает еще одну важную дыру в безопасности, которую вы не сможете закрыть, просто ограничивая окружение eval():

        input_string = """[
    c for c in ().__class__.__base__.__subclasses__()
    if c.__name__ == "range"
    ][0](10)"""
    
        >>> list(eval(input_string, {"__builtins__": {}}, {}))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    

Генератор списка в приведенном коде фильтрует классы объекта, чтобы вернуть список, содержащий класс range. Далее range вызывается для создания соответствующего объекта. Это хитрый способ обойти исключение TypeError, вызываемое в результате ограничения "__builtins__".

        >>> list(eval(range(10), {"__builtins__": {}}, {}))
TypeError: eval() arg 1 must be a string, bytes or code object
    

Возможное решение этой уязвимости состоит в том, чтобы ограничить использование имен во входных данных набором безопасных имен либо исключить всякое использование любых имен.

Чтобы реализовать эту технику, необходимо выполнить следующие шаги:

  1. Создать словарь, содержащий имена, которые могут использоваться в eval().
  2. Скомпилировать входную строку в байт-код, используя compile() в режиме eval.
  3. Проверить .co_names в объекте байт-кода, чтобы убедиться, что он содержит только разрешенные имена.
  4. Вызвать исключение NameError, если пользователь пытается использовать недопустимое имя.

Взглянем на следующую функцию, в которой реализованы все эти шаги:

        def eval_expression(input_string):
    allowed_names = {"sum": sum}
    code = compile(input_string, "<string>", "eval")
    for name in code.co_names:
        if name not in allowed_names:
            raise NameError(f"Использование {name} не разрешено.")
    return eval(code, {"__builtins__": {}}, allowed_names)
    

Эта функция ограничивает имена, которые можно использовать в eval(), именами в словаре allowed_names. Для этого функция использует .co_names – атрибут объекта кода, содержащий кортеж имен в объекте кода.

Следующие примеры показывают, как написанная нами функция eval_expression() работает на практике:

        >>> eval_expression("3 + 4 * 5 + 25 / 2")
35.5
>>> eval_expression("sum([1, 2, 3])")
6
>>> eval_expression("pow(10, 2)")
NameError: Использование pow не разрешено.
    

Если нужно полностью запретить применение имен, достаточно переписать eval_expression() следующим образом:

        def eval_expression(input_string):
    code = compile(input_string, "<string>", "eval")
    if code.co_names:
        raise NameError(f"Использование имён запрещено.")
    return eval(code, {"__builtins__": {}}, {})
    
        >>> eval_expression("3 + 4 * 5 + 25 / 2")
35.5
>>> eval_expression("sum([1, 2, 3])")
NameError: Использование имён запрещено.
    

Ограничение входных данных до литералов

Типичный пример использования eval() в Python – это выполнение выражений, содержащих стандартные литералы Python. Задача настолько распространенная, что стандартная библиотека предоставляет соответствующую функцию literal_eval(). Функция не поддерживает операторы, но работает со списками, кортежами, числами, строками и т. д.:

        >>> from ast import literal_eval
>>> literal_eval("15.02")
15.02
>>> literal_eval("[1, 15]")
[1, 15]
>>> literal_eval("{'one': 1, 'two': 2}")
{'one': 1, 'two': 2}
>>> literal_eval("sum([1, 15]) + 5 + 8 * 2")
ValueError: malformed node or string: ...
    

Использование eval() совместно с input()

В Python 3.x встроенная функция input() читает пользовательский ввод из командной строки, преобразует его в строку, удаляет завершающий символ новой строки и возвращает результат вызывающей стороне. Поскольку результатом input() является строка, ее можно передать в eval() и выполнить как выражение Python:

        >>> eval(input("Введите математическое выражение: "))
Введите математическое выражение: 15*2
30
    

Это распространенный вариант использования eval(). Он также эмулирует поведеие input() в версиях Python 2.x, где функции можно было передать строковое выражение для выполнения (впоследствии от этого отказались из соображений безопасности).

Построим обработчик математических выражений

Итак, мы узнали, как работает eval() в Python и как использовать функцию на практике. Мы также выяснили, что eval() имеет важные последствия для безопасности и что обычно считается хорошей практикой избегать использования eval() в коде. Однако в некоторых ситуациях eval() может сэкономить много времени и усилий.

В этом разделе вы напишем приложение для оценки математических выражений на лету. Без использования eval(), нам потребовалось бы выполнить следующие шаги:

  1. Распарсить входное выражение.
  2. Преобразовать компоненты выражения в объекты Python (числа, операторы, функции).
  3. Объединить всё в исполняемое выражение.
  4. Проверить валидность выражения для Python.
  5. Выполнить итоговое выражение и вернуть результат вычислений.

Это потребовало бы большой работы, учитывая разнообразие возможных выражений, которые Python может обрабатывать. К счастью, теперь мы знаем о функции eval().

Всё приложение будет храниться в скрипте mathrepl.py. Постепенно мы его заполним необходимым содержимым. Начнем со следующего кода:

mathrepl.py
        import math

__version__ = "1.0"

ALLOWED_NAMES = {
    k: v for k, v in math.__dict__.items() if not k.startswith("__")
}

PS1 = "mr>>"

WELCOME = f"""
MathREPL {__version__} - обработчик математических выражений на Python!
Введите математическое выражение после приглашения "{PS1}".
Для дополнительной информации используйте команду help.
Чтобы выйти, наберите quit или exit.
"""

USAGE = f"""
Соберите математическое выражение из чисел и операторов.
Можно использовать любые из следующих функций и констант:

{', '.join(ALLOWED_NAMES.keys())}
"""
    

Модуль math мы используем для того, чтобы определить все доступные имена. Три строковые константы применяются для вывода строк в интерфейсе программы. Напишем ключевую функцию нашей программы:

mathrepl.py
        def evaluate(expression):
    """Вычисляет математическое выражение."""
    # Компиляция выражения в байт-код
    code = compile(expression, "<string>", "eval")

    # Валидация доступных имен
    for name in code.co_names:
        if name not in ALLOWED_NAMES:
            raise NameError(f"The use of '{name}' is not allowed")

    return eval(code, {"__builtins__": {}}, ALLOWED_NAMES)
    

Осталось лишь написать код для взаимодействия с пользователем. В функции main() мы определяем основной цикл программы для чтения введенных данных и расчета математических выражений, введенных пользователем:

mathrepl.py
        def main():
    """Читает и рассчитывает введенное выражение"""
    print(WELCOME)
    while True:
        # Читаем пользовательский ввод
        try:
            expression = input(f"{PS1} ")
        except (KeyboardInterrupt, EOFError):
            raise SystemExit()

        # Поддержка специальных команд
        if expression.lower() == "help":
            print(USAGE)
            continue
        if expression.lower() in {"quit", "exit"}:
            raise SystemExit()

        # Вычисление выражения и обработка ошибок
        try:
            result = evaluate(expression)
        except SyntaxError:
            # Некорректное выражение
            print("Вы ввели некорректное выражение.")
            continue
        except (NameError, ValueError) as err:
            # Если пользователь попытался использовать неразрешенное имя
            # или неверное значение в переданной функции
            print(err)
            continue

        # Выводим результат, если не было ошибок
        print(f"Результат: {result}")
    

Проверим результат нашей работы:

Shell
        python3 mathrepl.py

MathREPL 1.0 - обработчик математических выражений на Python!
Введите математическое выражение после приглашения "mr>>".
Для дополнительной информации используйте команду help.
Чтобы выйти, наберите quit или exit.

mr>> 25 * 2
Результат: 50
mr>> sqrt(25)
Результат: 5.0
mr>> pi
Результат: 3.141592653589793
mr>> 5 * (25 + 4
Вы ввели некорректное выражение.
mr>> sum([1, 2, 3, 4, 5])
The use of 'sum' is not allowed
mr>> sqrt(-15)
math domain error
mr>> factorial(-15)
factorial() not defined for negative values
mr>> exit
    

Вот и всё – наш обработчик математических выражений готов! В случае ошибок при вводе или математически некорректных выражений мы получаем необходимое пояснение. Для самой обработки введенных данных потребовалось лишь несколько строк и функция eval().

Заключение

Итак, вы можете использовать eval() для выполнения выражений Python из строкового или кодового ввода. Эта встроенная функция полезна, когда вы пытаетесь динамически обновлять выражения Python и хотите избежать проблем с созданием собственного обработчика выражений. Однако пользоваться ей стоит с осторожностью.

***

Другие наши недавние статьи с подробным разбором различных аспектов стандартной библиотеки Python:

  • Всё, что нужно знать о декораторах Python
  • Как хранить объекты Python со сложной структурой
  • 15 вещей, которые нужно знать о словарях Python

Время прочтения
6 мин

Просмотры 111K

Сколько было сломано копий при обсуждении вопроса «Возможно ли сделать eval безопасным?» — невозможно сосчитать. Всегда находится кто-то, кто утверждает, что нашёл способ оградиться от всех возможных последствий выполнения этой функции.
Когда мне понадобилось найти развёрнутый ответ на этот вопрос, я наткнулся на один пост. Меня приятно удивила глубина исследования, так что я решил, что это стоит перевести.

Коротко о проблеме

В Python есть встроенная функция eval(), которая выполняет строку с кодом и возвращает результат выполнения:

assert eval("2 + 3 * len('hello')") == 17

Это очень мощная, но в то же время и очень опасная инструкция, особенно если строки, которые вы передаёте в eval, получены не из доверенного источника. Что будет, если строкой, которую мы решим скормить eval‘у, окажется os.system('rm -rf /')? Интерпретатор честно запустит процесс удаления всех данных с компьютера, и хорошо ещё, если он будет выполняться от имени наименее привилегированного пользователя (в последующих примерах я буду использовать clear (cls, если вы используете Windows) вместо rm -rf /, чтобы никто из читателей случайно не выстрелил себе в ногу).

Какие есть решения?

Некоторые утверждают, что возможно сделать eval безопасным, если запускать его без доступа к символам из globals. В качестве второго (опционального) аргумента eval() принимает словарь, который будет использован вместо глобального пространства имён (все классы, методы, переменные и пр., объявленные на «верхнем» уровне, доступные из любой точки кода) кодом, который будет выполнен eval‘ом. Если eval вызывается без этого аргумента, он использует текущее глобальное пространство имён, в которое мог быть импортирован модуль os. Если же передать пустой словарь, глобальное пространство имён для eval‘а будет пустым. Вот такой код уже не сможет выполниться и возбудит исключение NameError: name 'os' is not defined:

eval("os.system('clear')", {})

Однако мы всё ещё можем импортировать модули и обращаться к ним, используя встроенную функцию __import__. Так, код ниже отработает без ошибок:

eval("__import__('os').system('clear')", {})

Следующей попыткой обычно становится решение запретить доступ к __builtins__ изнутри eval‘a, так как имена, подобные __import__, доступны нам потому, что они находятся в глобальной переменной __builtins__. Если мы явно передадим вместо неё пустой словарь, код ниже уже не сможет быть выполнен:

eval("__import__('os').system('clear')", {'__builtins__':{}}) # NameError: name '__import__' is not defined

Ну а теперь-то мы в безопасности?

Некоторые говорят, что «да» и совершают ошибку. Для примера, вот этот небольшой кусок кода вызовет segfault, если вы запустите его в CPython:

s = """
(lambda fc=(
    lambda n: [
        c for c in 
            ().__class__.__bases__[0].__subclasses__() 
            if c.__name__ == n
        ][0]
    ):
    fc("function")(
        fc("code")(
            0,0,0,0,"KABOOM",(),(),(),"","",0,""
        ),{}
    )()
)()
"""
eval(s, {'__builtins__':{}})

Итак, давайте разберёмся, что же здесь происходит. Начнём с этого:

().__class__.__bases__[0]

Как многие могли догадаться, это просто один из способов обратиться к object. Мы не можем просто написать object, так как __builtins__ пусты, но мы можем создать пустой кортеж (тьюпл), первым базовым классом которого является object и, пройдясь по его свойствам, получить доступ к классу object.
Теперь мы получаем список всех классов, которые наследуют object или, иными словами, список всех классов, объявленных в программе на данный момент:

().__class__.__bases__[0].__subclasses__() 

Если заменить для удобочитаемости это выражение на ALL_CLASSES, нетрудно будет заметить, что выражение ниже находит класс по его имени:

[c for c in ALL_CLASSES if c.__name__ == n][0]

Далее в коде нам надо будет дважды искать класс, так что создадим функцию:

lambda n: [c for c in ALL_CLASSES if c.__name__ == n][0]

Чтобы вызвать функцию, надо как-то её назвать, но, так как мы будем выполнять этот код внутри eval‘a, мы не можем ни объявить функцию (используя def), ни использовать оператор присвоения, чтобы привязать нашу лямбду к какой-нибудь переменной.
Однако, есть и третий вариант: параметры по умолчанию. При объявлении лямбды, как и при объявлении любой обычной функции, мы можем задать параметры по умолчанию, так что если мы поместим весь код внутри ещё одной лямбды, и зададим ей нашу, как параметр по умолчанию, — мы добьёмся желаемого:

(lambda fc=(
    lambda n: [
        c for c in ALL_CLASSES if c.__name__ == n
        ][0]
    ):
    # теперь мы можем обращаться к нашей лямбде через fc
)()

Итак, мы имеем функцию, которая умеет искать классы, и можем обращаться к ней по имени. Что дальше? Мы создадим объект класса code (внутренний класс, его экземпляром, например, является свойство func_code объекта функции):

fc("code")(0,0,0,0,"KABOOM",(),(),(),"","",0,"")

Из всех инициализующих параметров нас интересует только «KABOOM». Это и есть последовательность байт-кодов, которую будет использовать наш объект, и, как вы уже могли догадаться, эта последовательность не является «хорошей». На самом деле любого байт-кода из неё хватило бы, так как всё это — бинарные операторы, которые будут вызваны при пустом стеке, что приведёт к segfault‘у CPython. «KABOOM» просто выглядит забавнее, спасибо lvh за этот пример.

Итак, у нас есть объект класса code, но напрямую выполнить его мы не можем. Тогда создадим функцию, кодом которой и будет наш объект:

fc("function")(CODE_OBJECT, {})

Ну и теперь, когда у нас есть функция, мы можем её выполнить. Конкретно эта функция попытается выполнить наш некорректно составленный байт-код и приведёт к краху интерпретатора.
Вот весь код ещё раз:

(lambda fc=(lambda n: [c for c in ().__class__.__bases__[0].__subclasses__() if c.__name__ == n][0]):
    fc("function")(fc("code")(0,0,0,0,"KABOOM",(),(),(),"","",0,""),{})()
)()

Заключение

Итак, надеюсь теперь ни у кого не осталось сомнений в том, что eval НЕ БЕЗОПАСЕН, даже если убрать доступ к глобальным и встроенным переменным.

В примере выше мы использовали список всех подклассов класса object, чтобы создать объекты классов code и function. Точно таким же образом можно получить (и инстанцировать) любой класс, существующий в программе на момент вызова eval().
Вот ещё один пример того, что можно сделать:

s = """
[
    c for c in 
    ().__class__.__bases__[0].__subclasses__() 
    if c.__name__ == "Quitter"
][0](0)()
"""
eval(s, {'__builtins__':{}})

Модуль lib/site.py содержит класс Quitter, который вызывается интерпретатором, когда вы набираете quit().
Код выше находит этот класс, инстанциирует его и вызывает, чем завершает работу интерпретатора.

Сейчас мы запускали eval в пустом окружении, исходя из того, что указанный в статье код — это весь код нашей программы.
В случае использования eval‘а в реальном приложении злоумышленник может получить доступ ко всем классам, которые вы используете, так что его возможности не будут ограничены практически ничем.

Проблема всех подобных попыток сделать eval безопасным в том, что они все основаны на идее «чёрных списков», идее о том, что надо убрать доступ ко всем вещам, которые, как нам кажется, могут быть опасны при использовании в eval‘е. С такой стратегией практически нет шансов на победу, ведь если окажется незапрещённым хоть что-то, система будет уязвима.

Когда я проводил исследование этой темы, я наткнулся на защищенный режим выполнения eval‘а в Python, который является ещё одной попыткой побороть эту проблему:

>>> eval("(lambda:0).func_code", {'__builtins__':{}})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
RuntimeError: function attributes not accessible in restricted mode

Вкратце, он работает следующим образом: если __builtins__ внутри eval отличаются от «официальных» — eval переходит в защищенный режим, в котором закрыт доступ к некоторым опасным свойствам, таким как func_code у функций. Более подробное описание этого режима можно найти тут, но, как мы уже видели выше, он тоже не является «серебряной пулей».

И всё-таки, можно ли сделать eval безопасным? Сложно сказать. Как мне кажется, злоумышленнику не удастся навредить без доступа к объектам с двумя нижними подчёркиваниями, обрамляющими имя, так что возможно, если исключить из обработки все строки с двумя нижними подчёркиваниями, то мы будем в безопасности. Возможно…

P.S.

В треде на Reddit я нашёл короткий сниппет, позволяющий нам в eval получить «оригинальные» __builtins__:

[
    c for c in ().__class__.__base__.__subclasses__() 
    if c.__name__ == 'catch_warnings'
][0]()._module.__builtins__

Традиционное P.P.S. для хабра: прошу обо всех ошибках, неточностях и опечатках писать в личку :)

Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Evaluate Expressions Dynamically With Python eval()

Python’s eval() allows you to evaluate arbitrary Python expressions from a string-based or compiled-code-based input. This function can be handy when you’re trying to dynamically evaluate Python expressions from any input that comes as a string or a compiled code object.

Although Python’s eval() is an incredibly useful tool, the function has some important security implications that you should consider before using it. In this tutorial, you’ll learn how eval() works and how to use it safely and effectively in your Python programs.

In this tutorial, you’ll learn:

  • How Python’s eval() works
  • How to use eval() to dynamically evaluate arbitrary string-based or compiled-code-based input
  • How eval() can make your code insecure and how to minimize the associated security risks

Additionally, you’ll learn how to use Python’s eval() to code an application that interactively evaluates math expressions. With this example, you’ll apply everything you’ve learned about eval() to a real-world problem. If you want to get the code for this application, then you can click on the box below:

Understanding Python’s eval()

You can use the built-in Python eval() to dynamically evaluate expressions from a string-based or compiled-code-based input. If you pass in a string to eval(), then the function parses it, compiles it to bytecode, and evaluates it as a Python expression. But if you call eval() with a compiled code object, then the function performs just the evaluation step, which is quite convenient if you call eval() several times with the same input.

The signature of Python’s eval() is defined as follows:

eval(expression[, globals[, locals]])

The function takes a first argument, called expression, which holds the expression that you need to evaluate. eval() also takes two optional arguments:

  1. globals
  2. locals

In the next three sections, you’ll learn what these arguments are and how eval() uses them to evaluate Python expressions on the fly.

The First Argument: expression

The first argument to eval() is called expression. It’s a required argument that holds the string-based or compiled-code-based input to the function. When you call eval(), the content of expression is evaluated as a Python expression. Check out the following examples that use string-based input:

>>>

>>> eval("2 ** 8")
256
>>> eval("1024 + 1024")
2048
>>> eval("sum([8, 16, 32])")
56
>>> x = 100
>>> eval("x * 2")
200

When you call eval() with a string as an argument, the function returns the value that results from evaluating the input string. By default, eval() has access to global names like x in the above example.

To evaluate a string-based expression, Python’s eval() runs the following steps:

  1. Parse expression
  2. Compile it to bytecode
  3. Evaluate it as a Python expression
  4. Return the result of the evaluation

The name expression for the first argument to eval() highlights that the function works only with expressions and not with compound statements. The Python documentation defines expression as follows:

expression

A piece of syntax which can be evaluated to some value. In other words, an expression is an accumulation of expression elements like literals, names, attribute access, operators or function calls which all return a value. In contrast to many other languages, not all language constructs are expressions. There are also statements which cannot be used as expressions, such as while. Assignments are also statements, not expressions. (Source)

On the other hand, a Python statement has the following definition:

statement

A statement is part of a suite (a “block” of code). A statement is either an expression or one of several constructs with a keyword, such as if, while or for. (Source)

If you try to pass a compound statement to eval(), then you’ll get a SyntaxError. Take a look at the following example in which you try to execute an if statement using eval():

>>>

>>> x = 100
>>> eval("if x: print(x)")
  File "<string>", line 1
    if x: print(x)
    ^
SyntaxError: invalid syntax

If you try to evaluate a compound statement using Python’s eval(), then you’ll get a SyntaxError like in the above traceback. That’s because eval() only accepts expressions. Any other statement, such as if, for, while, import, def, or class, will raise an error.

Assignment operations aren’t allowed with eval() either:

>>>

>>> eval("pi = 3.1416")
  File "<string>", line 1
    pi = 3.1416
       ^
SyntaxError: invalid syntax

If you try to pass an assignment operation as an argument to Python’s eval(), then you’ll get a SyntaxError. Assignment operations are statements rather than expressions, and statements aren’t allowed with eval().

You’ll also get a SyntaxError any time the parser doesn’t understand the input expression. Take a look at the following example in which you try to evaluate an expression that violates Python syntax:

>>>

>>> # Incomplete expression
>>> eval("5 + 7 *")
  File "<string>", line 1
    5 + 7 *
          ^
SyntaxError: unexpected EOF while parsing

You can’t pass an expression to eval() that violates Python syntax. In the above example, you try to evaluate an incomplete expression ("5 + 7 *") and get a SyntaxError because the parser doesn’t understand the syntax of the expression.

You can also pass compiled code objects to Python’s eval(). To compile the code that you’re going to pass to eval(), you can use compile(). This is a built-in function that can compile an input string into a code object or an AST object so that you can evaluate it with eval().

The details of how to use compile() are beyond the scope of this tutorial, but here’s a quick look at its first three required arguments:

  1. source holds the source code that you want to compile. This argument accepts normal strings, byte strings, and AST objects.
  2. filename gives the file from which the code was read. If you’re going to use a string-based input, then the value for this argument should be "<string>".
  3. mode specifies which kind of compiled code you want to get. If you want to process the compiled code with eval(), then this argument should be set to "eval".

You can use compile() to supply code objects to eval() instead of normal strings. Check out the following examples:

>>>

>>> # Arithmetic operations
>>> code = compile("5 + 4", "<string>", "eval")
>>> eval(code)
9
>>> code = compile("(5 + 7) * 2", "<string>", "eval")
>>> eval(code)
24
>>> import math
>>> # Volume of a sphere
>>> code = compile("4 / 3 * math.pi * math.pow(25, 3)", "<string>", "eval")
>>> eval(code)
65449.84694978735

If you use compile() to compile the expressions that you’re going to pass to eval(), then eval() goes through the following steps:

  1. Evaluate the compiled code
  2. Return the result of the evaluation

If you call Python’s eval() using a compiled-code-based input, then the function performs the evaluation step and immediately returns the result. This can be handy when you need to evaluate the same expression multiple times. In this case, it’s best to precompile the expression and reuse the resulting bytecode on subsequent calls to eval().

If you compile the input expression beforehand, then successive calls to eval() will run faster because you won’t be repeating the parsing and compiling steps. Unneeded repetitions can lead to high CPU times and excessive memory consumption if you’re evaluating complex expressions.

The Second Argument: globals

The second argument to eval() is called globals. It’s optional and holds a dictionary that provides a global namespace to eval(). With globals, you can tell eval() which global names to use while evaluating expression.

Global names are all those names that are available in your current global scope or namespace. You can access them from anywhere in your code.

All the names passed to globals in a dictionary will be available to eval() at execution time. Check out the following example, which shows how to use a custom dictionary to supply a global namespace to eval():

>>>

>>> x = 100  # A global variable
>>> eval("x + 100", {"x": x})
200
>>> y = 200  # Another global variable
>>> eval("x + y", {"x": x})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
NameError: name 'y' is not defined

If you supply a custom dictionary to the globals argument of eval(), then eval() will take only those names as globals. Any global names defined outside of this custom dictionary won’t be accessible from inside eval(). That’s why Python raises a NameError when you try to access y in the above code: The dictionary passed to globals doesn’t include y.

You can insert names into globals by listing them in your dictionary, and then those names will be available during the evaluation process. For example, if you insert y into globals, then the evaluation of "x + y" in the above example will work as expected:

>>>

>>> eval("x + y", {"x": x, "y": y})
300

Since you add y to your custom globals dictionary, the evaluation of "x + y" is successful and you get the expected return value of 300.

You can also supply names that don’t exist in your current global scope. For this to work, you need to supply a concrete value for each name. eval() will interpret these names as global names when running:

>>>

>>> eval("x + y + z", {"x": x, "y": y, "z": 300})
600
>>> z
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'z' is not defined

Even though z isn’t defined in your current global scope, the variable is present in globals with a value of 300. In this case, eval() has access to z as though it were a global variable.

The mechanism behind globals is quite flexible. You can pass any visible variable (global, local, or nonlocal) to globals. You can also pass custom key-value pairs like "z": 300 in the above example. eval() will treat all of them as global variables.

An important point regarding globals is that if you supply a custom dictionary to it that doesn’t contain a value for the key "__builtins__", then a reference to the dictionary of builtins will be automatically inserted under "__builtins__" before expression gets parsed. This ensures that eval() will have full access to all of Python’s built-in names when evaluating expression.

The following examples show that even though you supply an empty dictionary to globals, the call to eval() will still have access to Python’s built-in names:

>>>

>>> eval("sum([2, 2, 2])", {})
6
>>> eval("min([1, 2, 3])", {})
1
>>> eval("pow(10, 2)", {})
100

In the above code, you supply an empty dictionary ({}) to globals. Since that dictionary doesn’t contain a key called "__builtins__", Python automatically inserts one with a reference to the names in builtins. This way, eval() has full access to all of Python’s built-in names when it parses expression.

If you call eval() without passing a custom dictionary to globals, then the argument will default to the dictionary returned by globals() in the environment where eval() is called:

>>>

>>> x = 100  # A global variable
>>> y = 200  # Another global variable
>>> eval("x + y")  # Access both global variables
300

When you call eval() without supplying a globals argument, the function evaluates expression using the dictionary returned by globals() as its global namespace. So, in the above example, you can freely access x and y because they’re global variables included in your current global scope.

The Third Argument: locals

Python’s eval() takes a third argument called locals. This is another optional argument that holds a dictionary. In this case, the dictionary contains the variables that eval() uses as local names when evaluating expression.

Local names are those names (variables, functions, classes, and so on) that you define inside a given function. Local names are visible only from inside the enclosing function. You define these kinds of names when you’re writing a function.

Since eval() is already written, you can’t add local names to its code or local scope. However, you can pass a dictionary to locals, and eval() will treat those names as local names:

>>>

>>> eval("x + 100", {}, {"x": 100})
200
>>> eval("x + y", {}, {"x": 100})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
NameError: name 'y' is not defined

The second dictionary in the first call to eval() holds the variable x. This variable is interpreted by eval() as a local variable. In other words, it’s seen as a variable defined in the body of eval().

You can use x in expression, and eval() will have access to it. In contrast, if you try to use y, then you’ll get a NameError because y isn’t defined in either the globals namespace or the locals namespace.

Like with globals, you can pass any visible variable (global, local, or nonlocal) to locals. You can also pass custom key-value pairs like "x": 100 in the above example. eval() will treat all of them as local variables.

Note that to supply a dictionary to locals, you first need to supply a dictionary to globals. It’s not possible to use keyword arguments with eval():

>>>

>>> eval("x + 100", locals={"x": 100})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: eval() takes no keyword arguments

If you try to use keyword arguments when calling eval(), then you’ll get a TypeError explaining that eval() takes no keyword arguments. So, you need to supply a globals dictionary before you can supply a locals dictionary.

If you don’t pass a dictionary to locals, then it defaults to the dictionary passed to globals. Here’s an example in which you pass an empty dictionary to globals and nothing to locals:

>>>

>>> x = 100
>>> eval("x + 100", {})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
NameError: name 'x' is not defined

Given that you don’t provide a custom dictionary to locals, the argument defaults to the dictionary passed to globals. In this case, eval() doesn’t have access to x because globals holds an empty dictionary.

The main practical difference between globals and locals is that Python will automatically insert a "__builtins__" key into globals if that key doesn’t already exist. This happens whether or not you supply a custom dictionary to globals. On the other hand, if you supply a custom dictionary to locals, then that dictionary will remain unchanged during the execution of eval().

Evaluating Expressions With Python’s eval()

You can use Python’s eval() to evaluate any kind of Python expression but not Python statements such as keyword-based compound statements or assignment statements.

eval() can be handy when you need to dynamically evaluate expressions and using other Python techniques or tools would considerably increase your development time and effort. In this section, you’ll learn how you can use Python’s eval() to evaluate Boolean, math, and general-purpose Python expressions.

Boolean Expressions

Boolean expressions are Python expressions that return a truth value (True or False) when the interpreter evaluates them. They’re commonly used in if statements to check if some condition is true or false. Since Boolean expressions aren’t compound statements, you can use eval() to evaluate them:

>>>

>>> x = 100
>>> y = 100
>>> eval("x != y")
False
>>> eval("x < 200 and y > 100")
False
>>> eval("x is y")
True
>>> eval("x in {50, 100, 150, 200}")
True

You can use eval() with Boolean expressions that use any of the following Python operators:

  • Value comparison operators: <, >, <=, >=, ==, !=
  • Logical (Boolean) operators: and, or, not
  • Membership test operators: in, not in
  • Identity operators: is, is not

In all cases, the function returns the truth value of the expression that you’re evaluating.

Now, you may be thinking, why should I use eval() instead of using the Boolean expression directly? Well, suppose you need to implement a conditional statement, but you want to change the condition on the fly:

>>>

>>> def func(a, b, condition):
...     if eval(condition):
...         return a + b
...     return a - b
...
>>> func(2, 4, "a > b")
-2
>>> func(2, 4, "a < b")
6
>>> func(2, 2, "a is b")
4

Inside func(), you use eval() to evaluate the supplied condition and return either a + b or a - b according to the result of the evaluation. You use just a few different conditions in the above example, but you could use any number of others provided that you stick with the names a and b that you defined in func().

Now imagine how you would implement something like this without the use of Python’s eval(). Would that take less code and time? No way!

Math Expressions

One common use case of Python’s eval() is to evaluate math expressions from a string-based input. For example, if you want to create a Python calculator, then you can use eval() to evaluate the user’s input and return the result of the calculations.

The following examples show how you can use eval() along with math to perform math operations:

>>>

>>> # Arithmetic operations
>>> eval("5 + 7")
12
>>> eval("5 * 7")
35
>>> eval("5 ** 7")
78125
>>> eval("(5 + 7) / 2")
6.0
>>> import math
>>> # Area of a circle
>>> eval("math.pi * pow(25, 2)")
1963.4954084936207
>>> # Volume of a sphere
>>> eval("4 / 3 * math.pi * math.pow(25, 3)")
65449.84694978735
>>> # Hypotenuse of a right triangle
>>> eval("math.sqrt(math.pow(10, 2) + math.pow(15, 2))")
18.027756377319946

When you use eval() to evaluate math expressions, you can pass in expressions of any kind or complexity. eval() will parse them, evaluate them and, if everything is okay, give you the expected result.

General-Purpose Expressions

So far, you’ve learned how to use eval() with Boolean and math expressions. However, you can use eval() with more complex Python expressions that incorporate function calls, object creation, attribute access, comprehensions, and so on.

For example, you can call a built-in function or one that you’ve imported with a standard or third-party module:

>>>

>>> # Run the echo command
>>> import subprocess
>>> eval("subprocess.getoutput('echo Hello, World')")
'Hello, World'
>>> # Launch Firefox (if available)
>>> eval("subprocess.getoutput('firefox')")
''

In this example, you use Python’s eval() to execute a few system commands. As you can imagine, you can do a ton of useful things with this feature. However, eval() can also expose you to serious security risks, like allowing a malicious user to run system commands or any arbitrary piece of code in your machine.

In the next section, you’ll look at ways to address some of the security risks associated with eval().

Minimizing the Security Issues of eval()

Although it has an almost unlimited number of uses, Python’s eval() also has important security implications. eval() is considered insecure because it allows you (or your users) to dynamically execute arbitrary Python code.

This is considered bad programming practice because the code that you’re reading (or writing) is not the code that you’ll execute. If you’re planning to use eval() to evaluate input from a user or any other external source, then you won’t know for sure what code is going to be executed. That’s a serious security risk if your application runs in the wrong hands.

For this reason, good programming practices generally recommend against using eval(). But if you choose to use the function anyway, then the rule of thumb is to never ever use it with untrusted input. The tricky part of this rule is figuring out which kinds of input you can trust.

As an example of how using eval() irresponsibly can make your code insecure, suppose you want to build an online service for evaluating arbitrary Python expressions. Your user will introduce expressions and then click the Run button. The application will get the user’s input and pass it to eval() for evaluation.

This application will run on your personal server. Yes, the same server where you have all those valuable files. If you’re running a Linux box and the application’s process has the right permissions, then a malicious user could introduce a dangerous string like the following:

"__import__('subprocess').getoutput('rm –rf *')"

The above code would delete all the files in the application’s current directory. That would be awful, wouldn’t it?

When the input is untrusted, there’s no completely effective way to avoid the security risks associated with eval(). However, you can minimize your risk by restricting the execution environment of eval(). You’ll learn a few techniques for doing so in the following sections.

Restricting globals and locals

You can restrict the execution environment of eval() by passing custom dictionaries to the globals and locals arguments. For example, you can pass empty dictionaries to both arguments to prevent eval() from accessing names in the caller’s current scope or namespace:

>>>

>>> # Avoid access to names in the caller's current scope
>>> x = 100
>>> eval("x * 5", {}, {})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
NameError: name 'x' is not defined

If you pass empty dictionaries ({}) to globals and locals, then eval() won’t find the name x in either its global namespace or its local namespace when evaluating the string "x * 5". As a result, eval() will throw a NameError.

Unfortunately, restricting the globals and locals arguments like this doesn’t eliminate all the security risks associated with the use of Python’s eval(), because you can still access all of Python’s built-in names.

Restricting the Use of Built-In Names

As you saw earlier, Python’s eval() automatically inserts a reference to the dictionary of builtins into globals before parsing expression. A malicious user could exploit this behavior by using the built-in function __import__() to get access to the standard library and any third-party module that you’ve installed on your system.

The following examples show that you can use any built-in function and any standard module like math or subprocess even after you’ve restricted globals and locals:

>>>

>>> eval("sum([5, 5, 5])", {}, {})
15
>>> eval("__import__('math').sqrt(25)", {}, {})
5.0
>>> eval("__import__('subprocess').getoutput('echo Hello, World')", {}, {})
'Hello, World'

Even though you restrict globals and locals using empty dictionaries, you can still use any built-in function like you did with sum() and __import__() in the above code.

You can use __import__() to import any standard or third-party module just like you did above with math and subprocess. With this technique, you can access any function or class defined in math, subprocess, or any other module. Now imagine what a malicious user could do to your system using subprocess or any other powerful module in the standard library.

To minimize this risk, you can restrict access to Python’s built-in functions by overriding the "__builtins__" key in globals. Good practice recommends using a custom dictionary containing the key-value pair "__builtins__": {}. Check out the following example:

>>>

>>> eval("__import__('math').sqrt(25)", {"__builtins__": {}}, {})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
NameError: name '__import__' is not defined

If you pass a dictionary containing the key-value pair "__builtins__": {} to globals, then eval() won’t have direct access to Python’s built-in functions like __import__(). However, as you’ll see in the next section, this approach still doesn’t make eval() completely secure.

Restricting Names in the Input

Even though you can restrict the execution environment of Python’s eval() using custom globals and locals dictionaries, the function will still be vulnerable to a few fancy tricks. For example, you can access the class object using a type literal like "", [], {}, or () along with some special attributes:

>>>

>>> "".__class__.__base__
<class 'object'>
>>> [].__class__.__base__
<class 'object'>
>>> {}.__class__.__base__
<class 'object'>
>>> ().__class__.__base__
<class 'object'>

Once you have access to object, you can use the special method .__subclasses__() to get access to all of the classes that inherit from object. Here’s how it works:

>>>

>>> for sub_class in ().__class__.__base__.__subclasses__():
...     print(sub_class.__name__)
...
type
weakref
weakcallableproxy
weakproxy
int
...

This code will print a large list of classes to your screen. Some of these classes are quite powerful and can be extremely dangerous in the wrong hands. This opens up another important security hole that you can’t close by simply restricting the execution environment of eval():

>>>

>>> input_string = """[
...     c for c in ().__class__.__base__.__subclasses__()
...     if c.__name__ == "range"
... ][0](10)"""
>>> list(eval(input_string, {"__builtins__": {}}, {}))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

The list comprehension in the above code filters the classes that inherit from object to return a list containing the class range. The first index ([0]) returns the class range. Once you have access to range, you call it to generate a range object. Then you call list() on the range object to generate a list of ten integers.

In this example, you use range to illustrate a security hole in eval(). Now imagine what a malicious user could do if your system exposed classes like subprocess.Popen.

A possible solution to this vulnerability is to restrict the use of names in the input, either to a bunch of safe names or to no names at all. To implement this technique, you need to go through the following steps:

  1. Create a dictionary containing the names that you want to use with eval().
  2. Compile the input string to bytecode using compile() in mode "eval".
  3. Check .co_names on the bytecode object to make sure it contains only allowed names.
  4. Raise a NameError if the user tries to enter a name that’s not allowed.

Take a look at the following function in which you implement all these steps:

>>>

>>> def eval_expression(input_string):
...     # Step 1
...     allowed_names = {"sum": sum}
...     # Step 2
...     code = compile(input_string, "<string>", "eval")
...     # Step 3
...     for name in code.co_names:
...         if name not in allowed_names:
...             # Step 4
...             raise NameError(f"Use of {name} not allowed")
...     return eval(code, {"__builtins__": {}}, allowed_names)

In eval_expression(), you implement all of the steps you saw before. This function restricts the names that you can use with eval() to only those names in the dictionary allowed_names. To do this, the function uses .co_names, which is an attribute of a code object that returns a tuple containing the names in the code object.

The following examples show how eval_expression() works in practice:

>>>

>>> eval_expression("3 + 4 * 5 + 25 / 2")
35.5
>>> eval_expression("sum([1, 2, 3])")
6
>>> eval_expression("len([1, 2, 3])")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 10, in eval_expression
NameError: Use of len not allowed
>>> eval_expression("pow(10, 2)")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 10, in eval_expression
NameError: Use of pow not allowed

If you call eval_expression() to evaluate arithmetic operations, or if you use expressions that include allowed names, then you’ll get the expected result. Otherwise, you’ll get a NameError. In the above examples, the only name you’ve allowed is sum(). Other names like len() and pow() are not allowed, so the function raises a NameError when you try to use them.

If you want to completely disallow the use of names, then you can rewrite eval_expression() as follows:

>>>

>>> def eval_expression(input_string):
...     code = compile(input_string, "<string>", "eval")
...     if code.co_names:
...         raise NameError(f"Use of names not allowed")
...     return eval(code, {"__builtins__": {}}, {})
...
>>> eval_expression("3 + 4 * 5 + 25 / 2")
35.5
>>> eval_expression("sum([1, 2, 3])")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in eval_expression
NameError: Use of names not allowed

Now your function doesn’t allow any names in the input string. To accomplish this, you check for names in .co_names and raise a NameError if one is found. Otherwise, you evaluate input_string and return the result of the evaluation. In this case, you use an empty dictionary to restrict locals as well.

You can use this technique to minimize the security issues of eval() and strengthen your armor against malicious attacks.

Restricting the Input to Only Literals

A common use case for Python’s eval() is to evaluate strings that contain standard Python literals and turn them into concrete objects.

The standard library provides a function called literal_eval() that can help achieve this goal. The function doesn’t support operators, but it does support lists, tuples, numbers, strings, and so on:

>>>

>>> from ast import literal_eval
>>> # Evaluating literals
>>> literal_eval("15.02")
15.02
>>> literal_eval("[1, 15]")
[1, 15]
>>> literal_eval("(1, 15)")
(1, 15)
>>> literal_eval("{'one': 1, 'two': 2}")
{'one': 1, 'two': 2}
>>> # Trying to evaluate an expression
>>> literal_eval("sum([1, 15]) + 5 + 8 * 2")
Traceback (most recent call last):
  ...
ValueError: malformed node or string: <_ast.BinOp object at 0x7faedecd7668>

Notice that literal_eval() only works with standard type literals. It doesn’t support the use of operators or names. If you try to feed an expression to literal_eval(), then you’ll get a ValueError. This function can also help you minimize the security risks associated with the use of Python’s eval().

Using Python’s eval() With input()

In Python 3.x, the built-in input() reads the user input at the command line, converts it to a string, strips the trailing newline, and returns the result to the caller. Since the result of input() is a string, you can feed it to eval() and evaluate it as a Python expression:

>>>

>>> eval(input("Enter a math expression: "))
Enter a math expression: 15 * 2
30
>>> eval(input("Enter a math expression: "))
Enter a math expression: 5 + 8
13

You can wrap Python’s eval() around input() to automatically evaluate the user’s input. This is a common use case for eval() because it emulates the behavior of input() in Python 2.x, in which input() evaluates the user’s input as a Python expression and returns the result.

This behavior of input() in Python 2.x was changed in Python 3.x because of its security implications.

Building a Math Expressions Evaluator

So far, you’ve learned how Python’s eval() works and how to use it in practice. You’ve also learned that eval() has important security implications and that it’s generally considered good practice to avoid the use of eval() in your code. However, there are some situations in which Python’s eval() can save you a lot of time and effort.

In this section, you’re going to code an application to evaluate math expressions on the fly. If you wanted to solve this problem without using eval(), then you’d need to go through the following steps:

  1. Parse the input expression.
  2. Change the expression’s components into Python objects (numbers, operators, functions, and so on).
  3. Combine everything into an expression.
  4. Confirm that the expression is valid in Python.
  5. Evaluate the final expression and return the result.

That would be a lot of work considering the wide variety of possible expressions that Python can process and evaluate. Fortunately, you can use eval() to solve this problem, and you’ve already learned several techniques to reduce the associated security risks.

You can get the source code for the application that you’re going to build in this section by clicking on the box below:

First, fire up your favorite code editor. Create a new Python script called mathrepl.py, and then add the following code:

 1import math
 2
 3__version__ = "1.0"
 4
 5ALLOWED_NAMES = {
 6    k: v for k, v in math.__dict__.items() if not k.startswith("__")
 7}
 8
 9PS1 = "mr>>"
10
11WELCOME = f"""
12MathREPL {__version__}, your Python math expressions evaluator!
13Enter a valid math expression after the prompt "{PS1}".
14Type "help" for more information.
15Type "quit" or "exit" to exit.
16"""
17
18USAGE = f"""
19Usage:
20Build math expressions using numeric values and operators.
21Use any of the following functions and constants:
22
23{', '.join(ALLOWED_NAMES.keys())}
24"""

In this piece of code, you first import Python’s math module. This module will allow you to perform math operations using predefined functions and constants. The constant ALLOWED_NAMES holds a dictionary containing the non-special names in math. This way, you’ll be able to use them with eval().

You also define three more string constants. You’ll use them as the user interface to your script and you’ll print them to the screen as needed.

Now you’re ready to code the core functionality of your application. In this case, you want to code a function that receives math expressions as input and returns their result. To do this, you write a function called evaluate():

26def evaluate(expression):
27    """Evaluate a math expression."""
28    # Compile the expression
29    code = compile(expression, "<string>", "eval")
30
31    # Validate allowed names
32    for name in code.co_names:
33        if name not in ALLOWED_NAMES:
34            raise NameError(f"The use of '{name}' is not allowed")
35
36    return eval(code, {"__builtins__": {}}, ALLOWED_NAMES)

Here’s how the function works:

  1. In line 26, you define evaluate(). This function takes the string expression as an argument and returns a float that represents the result of evaluating the string as a math expression.

  2. In line 29, you use compile() to turn the input string expression into compiled Python code. The compiling operation will raise a SyntaxError if the user enters an invalid expression.

  3. In line 32, you start a for loop to inspect the names contained in expression and confirm that they can be used in the final expression. If the user provides a name that is not in the list of allowed names, then you raise a NameError.

  4. In line 36, you perform the actual evaluation of the math expression. Notice that you pass custom dictionaries to globals and locals as good practice recommends. ALLOWED_NAMES holds the functions and constants defined in math.

The use of custom values for the globals and locals parameters, along with the check of names in line 33, allows you to minimize the security risks associated with the use of eval().

Your math expression evaluator will be finished when you write its client code in main(). In this function, you’ll define the program’s main loop and close the cycle of reading and evaluating the expressions that your user enters in the command line.

For this example, the application will:

  1. Print a welcome message to the user
  2. Show a prompt ready to read the user’s input
  3. Provide options to get usage instructions and to terminate the application
  4. Read the user’s math expression
  5. Evaluate the user’s math expression
  6. Print the result of the evaluation to the screen

Check out the following implementation of main():

38def main():
39    """Main loop: Read and evaluate user's input."""
40    print(WELCOME)
41    while True:
42        # Read user's input
43        try:
44            expression = input(f"{PS1} ")
45        except (KeyboardInterrupt, EOFError):
46            raise SystemExit()
47
48        # Handle special commands
49        if expression.lower() == "help":
50            print(USAGE)
51            continue
52        if expression.lower() in {"quit", "exit"}:
53            raise SystemExit()
54
55        # Evaluate the expression and handle errors
56        try:
57            result = evaluate(expression)
58        except SyntaxError:
59            # If the user enters an invalid expression
60            print("Invalid input expression syntax")
61            continue
62        except (NameError, ValueError) as err:
63            # If the user tries to use a name that isn't allowed
64            # or an invalid value for a given math function
65            print(err)
66            continue
67
68        # Print the result if no error occurs
69        print(f"The result is: {result}")
70
71if __name__ == "__main__":
72    main()

Inside main(), you first print the WELCOME message. Then you read the user’s input in a try statement to catch KeyboardInterrupt and EOFError. If either of these exceptions occur, then you terminate the application.

If the user enters the help option, then the application shows your USAGE guide. Likewise, if the user enters quit or exit, then the application terminates.

Finally, you use evaluate() to evaluate the user’s math expression, and then you print the result to the screen. It’s important to note that a call to evaluate() can raise the following exceptions:

  • SyntaxError: This happens when the user enters an expression that doesn’t follow Python syntax.
  • NameError: This happens when the user tries to use a name (function, class, or attribute) that isn’t allowed.
  • ValueError: This happens when the user tries to use a value that isn’t allowed as an input to a given function in math.

Notice that in main(), you catch all of these exceptions and print messages to the user accordingly. This will allow the user to review the expression, fix the problem, and run the program again.

That’s it! You’ve built a math expression evaluator in about seventy lines of code using Python’s eval(). To run the application, open your system’s command line and type the following command:

This command will launch the math expression evaluator’s command-line interface (CLI). You’ll see something like this on your screen:

MathREPL 1.0, your Python math expressions evaluator!
Enter a valid math expression after the prompt "mr>>".
Type "help" for more information.
Type "quit" or "exit" to exit.

mr>>

Once you’re there, you can enter and evaluate any math expression. For example, type the following expressions:

mr>> 25 * 2
The result is: 50
mr>> sqrt(25)
The result is: 5.0
mr>> pi
The result is: 3.141592653589793

If you enter a valid math expression, then the application evaluates it and prints the result to your screen. If there are any problems with your expressions, then the application will tell you:

mr>> 5 * (25 + 4
Invalid input expression syntax
mr>> sum([1, 2, 3, 4, 5])
The use of 'sum' is not allowed
mr>> sqrt(-15)
math domain error
mr>> factorial(-15)
factorial() not defined for negative values

In the first example, you miss the closing parentheses, so you get a message telling you that the syntax is incorrect. Then you call sum(), which isn’t allowed, and you get an explanatory error message. Finally, you call a math function with an invalid input value, and the application generates a message identifying the problem in your input.

There you have it—your math expressions evaluator is ready! Feel free to add some extra features. A few ideas to get you started include enlarging the dictionary of allowed names and adding more elaborate warning messages. Give it a shot and let us know in the comments how it goes.

Conclusion

You can use Python’s eval() to evaluate Python expressions from a string-based or code-based input. This built-in function can be useful when you’re trying to evaluate Python expressions on the fly and you want to avoid the hassle of creating your own expressions evaluator from scratch.

In this tutorial, you’ve learned how eval() works and how to use it safely and effectively to evaluate arbitrary Python expressions.

You’re now able to:

  • Use Python’s eval() to dynamically evaluate basic Python expressions
  • Run more complex statements like function calls, object creation, and attribute access using eval()
  • Minimize the security risks associated with the use of Python’s eval()

Additionally, you’ve coded an application that uses eval() to interactively evaluate math expressions using a command-line interface. You can download the application’s code by clicking on the link below:

Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Evaluate Expressions Dynamically With Python eval()

While coding in Python, you might have used the Python console or command line to execute a piece of code directly. You can write some code without compiling it into a separate program. String concatenation or mathematical operations can be done easily this way. But, there may be a case when you might have to run some code within the program. This can be done using an eval() method offered by Python.

What is eval() in Python?

It is a built-in function. In layman language, the eval() method runs the python code (which is passed as an argument) within the program.

OR

The eval() method parses the expression passed to it and runs python expression(code) within the program.

Syntax

eval(expression, globals, locals)

Parameters

  • expression (Required): String as passed and evaluated as Python code
  • globals (Optional):  a dictionary containing global parameters
  • locals (Optional): a dictionary containing local parameters
  • Return: Returns the result of the evaluated expression

Example:

# Simple program to explain eval () method

x = 12
y = 20

print(eval('x+y'))

Explanation

Here, the eval() method is used to run the code as it is written inside the brackets. Thus, the value of x and y are added and printed to the screen.

eval() Function with User Input

The best way to use eval() function is executing user input while doing dynamic execution of statements.

Here is a complex example where we have asked the user to enter the function to execute.

Example:

# eval() with user input
from math import *

func = input("Enter Function to Evaluate:n")
try:
    print(eval(func))
except Exception as ex:
    print(ex)

Output:

Enter Math Function to Evaluate:
pow(2,3)
8.0

Explanation

The first line of the program imports all the methods in the math module. Then the input() method is used for obtaining the name of the function to execute from the user. The name of the function is stored in the func variable.

Then a try statement is used for printing out the result of the eval(func) method. Here, the function specified by the user is executed by the eval() method. If an exception is raised, it is printed by the print() method.

As per the output, the user has entered the function pow(2,3) for evaluation. So, the method finds out the value of the number 2 raised to the power of 3. The final output will be 8.

Vulnerabilities With eval() Function

As we know eval() function is used to execute in-built Python function with the user input. 

Suppose we have os module imported and the user enters os.system(‘rm -rf /’) command to execute.

Once the command gets executed, it will start to delete system files and corrupt our environment.

That’s why you must check the user entered data first, before using the eval() function to execute user input code. This can be very useful whenever you use globals and locals parameters. 

eval() Function with Global and Local Directory

With the help of global and local directory, we can restrict the user to execute a limited set to commands.

The code below checks the available functions and variables in the local and global scope.

Example:

# Python program to check local and global scope

# Import math library 
from math import *;

def power(n,m):
    return pow(n,m)

print(globals()) #This will return the current global symbol table.
print(locals())  #This will return the current local symbol table.
print(dir())  #List of the names in current local scope 

Output:

{'atan2': <built-in function atan2>, 'gamma': <built-in function gamma>, 'fabs': <built-in function fabs>, '__loader__': <_frozen_importlib.SourceFileLoader object at 0x7fa14f913e10>, 'log': <built-in function log>, 'log1p': <built-in function log1p>, 'sin': <built-in function sin>, 'copysign': <built-in function copysign>, '__name__': '__main__', 'factorial': <built-in function factorial>, 'log10': <built-in function log10>, 'lgamma': <built-in function lgamma>, 'degrees': <built-in function degrees>, 'sinh': <built-in function sinh>, 'cosh': <built-in function cosh>, 'acosh': <built-in function acosh>, 'ldexp': <built-in function ldexp>, '__file__': 'main.py', 'hypot': <built-in function hypot>, 'pi': 3.141592653589793, '__doc__': None, 'e': 2.718281828459045, 'pow': <built-in function pow>, 'frexp': <built-in function frexp>, 'fmod': <built-in function fmod>, 'trunc': <built-in function trunc>, 'asinh': <built-in function asinh>, 'erfc': <built-in function erfc>, 'acos': <built-in function acos>, 'expm1': <built-in function expm1>, '__spec__': None, 'erf': <built-in function erf>, 'radians': <built-in function radians>, 'isinf': <built-in function isinf>, 'ceil': <built-in function ceil>, '__builtins__': <module 'builtins' (built-in)>, 'fsum': <built-in function fsum>, 'tanh': <built-in function tanh>, 'log2': <built-in function log2>, 'tan': <built-in function tan>, 'cos': <built-in function cos>, 'power': <function power at 0x7fa14f962bf8>, 'atanh': <built-in function atanh>, 'asin': <built-in function asin>, 'floor': <built-in function floor>, 'modf': <built-in function modf>, 'atan': <built-in function atan>, 'isnan': <built-in function isnan>, 'sqrt': <built-in function sqrt>, '__package__': None, 'isfinite': <built-in function isfinite>, '__cached__': None, 'exp': <built-in function exp>}
{'atan2': <built-in function atan2>, 'gamma': <built-in function gamma>, 'fabs': <built-in function fabs>, '__loader__': <_frozen_importlib.SourceFileLoader object at 0x7fa14f913e10>, 'log': <built-in function log>, 'log1p': <built-in function log1p>, 'sin': <built-in function sin>, 'copysign': <built-in function copysign>, '__name__': '__main__', 'factorial': <built-in function factorial>, 'log10': <built-in function log10>, 'lgamma': <built-in function lgamma>, 'degrees': <built-in function degrees>, 'sinh': <built-in function sinh>, 'cosh': <built-in function cosh>, 'acosh': <built-in function acosh>, 'ldexp': <built-in function ldexp>, '__file__': 'main.py', 'hypot': <built-in function hypot>, 'pi': 3.141592653589793, '__doc__': None, 'e': 2.718281828459045, 'pow': <built-in function pow>, 'frexp': <built-in function frexp>, 'fmod': <built-in function fmod>, 'trunc': <built-in function trunc>, 'asinh': <built-in function asinh>, 'erfc': <built-in function erfc>, 'acos': <built-in function acos>, 'expm1': <built-in function expm1>, '__spec__': None, 'erf': <built-in function erf>, 'radians': <built-in function radians>, 'isinf': <built-in function isinf>, 'ceil': <built-in function ceil>, '__builtins__': <module 'builtins' (built-in)>, 'fsum': <built-in function fsum>, 'tanh': <built-in function tanh>, 'log2': <built-in function log2>, 'tan': <built-in function tan>, 'cos': <built-in function cos>, 'power': <function power at 0x7fa14f962bf8>, 'atanh': <built-in function atanh>, 'asin': <built-in function asin>, 'floor': <built-in function floor>, 'modf': <built-in function modf>, 'atan': <built-in function atan>, 'isnan': <built-in function isnan>, 'sqrt': <built-in function sqrt>, '__package__': None, 'isfinite': <built-in function isfinite>, '__cached__': None, 'exp': <built-in function exp>}
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'hypot', 'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'pi', 'pow', 'power', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'trunc']

Explanation

In the above example, you can see that eval() function has access to lots of functions. To access all these functions that are built-in, we have imported the math library. All the functions are from the math library.

In the program written above, all the methods within the math module are imported using the * operator. Then a function called power is defined using the def keyword. This function takes two parameters, n and m. The functions returns the result of the pow(n,m) where the value of n raised to the power m will be calculated.

The next print statement prints out the result of calling the globals() function. This returns a dictionary that has all the variables in the global namespace. The next print() method displays the variables of the local namespace. The last print statement displays the result of the dir() method. This function is used to returns all the names of the current local scope. The dir() returns all the attributes and methods of an object.     

eval() Function with Empty Directory

Code: 

print(eval('dir()',{})) 

Output:

['__builtins__']

You can see if we run eval() function with empty directory still it has the access to builtin methods. 

So to restrict the user to use the limited function, you need to specify this function as global.

Example:

# Python eval() function with global

# Import math library
from math import *;

# Take input from user 
userInput = input("Enter Math Function to Evaluate.nAllowed Functions are nsamllIntegral(n)nlargestIntegral(n)nabs(n)nfact(n)n");

try:
    print(eval(userInput,{'samllIntegral':ceil,'largestIntegral':floor,'abs':fabs,'fact':factorial}))
except Exception as excep:
    print(excep)

print('Operation Done')

Output:

Enter Math Function to Evaluate. 
Allowed Functions are 
samllIntegral(n) 
largestIntegral(n) 
abs(n) 
fact(n) 
samllIntegral(2.3) 
3 
Operation Done

Explanation

In the above example, we have restricted the user to run only limited programs like “ceil, floor, fabs” if the user enters in any other function then program will raise an exception. 

In the first line, all the methods are imported from the math module. The input() method is used for obtaining the function to evaluate, from the user. This value is stored in the userInput method. The allowed functions are smallIntegral(n), largestIntegral(n), abs(n) and fact(n). Then, inside the try block, the print statement is used for returning the value of calling the eval() method. All the functions are assigned a value within the dictionary 
{‘samllIntegral’:ceil,’largestIntegral’:floor,’abs’:fabs,’fact’:factorial}. If the user tries to execute any other method, an exception is raised. This will be caught by the except statement and printed to the screen using the print() method.

After the execution of the function according to the user’s choice, a print statement prints the string “Operation done”.

Example with Local Scope

# Python eval() function with local

# Import math library

userInput1 = int(input("Enter first value to Evaluate : "))
userInput2 = int(input("Enter first value to Evaluate : "))

try:
    print(eval('input_1 + input_2',{'__builtins__':None},{'input_1':userInput1,'input_2':userInput2}))
except Exception as excep:
    print(excep)

print('Operation Done')

Output:

Enter first value to Evaluate : 21
Enter first value to Evaluate : 21
42
Operation Done

Explanation

The first two lines are used for obtaining two values from the user using the input() method. The values are converted to an integer using the int() method. The two integers are stored in variables userInput1 and userInput2 respectively. Then a try and except block is executed that prints the result of the eval() method.

The eval() method takes three arguments. They are — (‘input_1 + input_2’,{‘__builtins__’:None},{‘input_1′:userInput1,’input_2’:userInput2}. This statement evaluates the summation of the two numbers entered by the user. The except statement handles any exception that may be raised by the try block during program execution. This exception will be printed to the screen.

The last print statement prints “Operation done”.

Conclusion

The details of the eval() method have been discussed. The eval() method is a very useful method for allowing the users to enter their own small programs and parse them instantly. Apart from writing code for parser programs, the eval() method can be used for executing small mathematical functions.   

Table of Contents

  • ◈ What Is A Syntax Error In Python?
  • ◈ What does unexpected EOF while parsing mean in Python?
  • ✨ Scenario 1: Incomplete Loop/Function/If Statement
    • ➥ Example 1: Unexpected End Of For Loop
    • ➥ Example 2: Unexpected End Of Function
  • ✨ Scenario 2: Missing Parenthesis
    • ➥ Example 1:
    • ➥ Example 2:
  • ✨ Scenario 3: Using try without except/finally
  • ✨ Scenario 4: Using the eval() function on str()
  • Conclusion

Aim:

In this article, we will discuss how to fix SyntaxError: unexpected EOF while parsing in Python?

This article dissects our problem with examples and helps you get a firm grip on the minute details, which ultimately leads to our problem.

◈ What Is A Syntax Error In Python?

Syntax errors occur when the Python compiler cannot understand the source code written by you and is unable to generate the machine code. Syntax error generally appear at compile-time and are reported by the interpreter.

Example: Incomplete if statement that lacks a colon at the end.

age = 25

if age<18

    print(‘Not An Adult!’)

else:

    print(‘Adult!’)

Output:

File “D:/PycharmProjects/pythonProject1/EOL_SyntaxError.py”, line 2
if age<18
SyntaxError: invalid syntax

◈ What does unexpected EOF while parsing mean in Python?

EOF is the abbreviation for End of File.

The EOFError is raised in situations where the end of a file is reached before running every block of code in the file.

Let’s visualize the following:

Once upon a time, there was a boy who wa
……

Most of you will find a glitch because you ran into an “unexpected end of paragraph” above. Not only does the paragraph above end mid-sentence, but also it ends mid-word! Now, imagine – Python is in a similar situation. That’s when it returns SyntaxError: unexpected EOF while parsing.

The following scenarios will help you to understand the occurrence of such errors and the ways to solve them.

Scenario 1: Incomplete Loop/Function/If Statement

You must include at least one line of code within a For loop, While loop, if statements or functions; otherwise, it leads to the occurrence of Unexpected EOF error.

➥ Example 1: Unexpected End Of For Loop

lang = [‘Java’,‘Python’,‘C’,‘C++’,‘Golang’]

for i in lang:

Output:

File “D:/PycharmProjects/pythonProject1/EOL_SyntaxError.py”, line 3
^
SyntaxError: unexpected EOF while parsing

✍️ Solution:

You can use a print statement within the for loop body if you want to print the items of the lang list. You may also opt to use the pass statement if you do not wish to print anything and also avoid the error.

lang = [‘Java’,‘Python’,‘C’,‘C++’,‘Golang’]

for i in lang:

    print(i)

Output:

Java
Python
C
C++
Golang

➥ Example 2: Unexpected End Of Function

def foo():

    return ‘I love Java2Blog!’

def func():

Output:

File “D:/PycharmProjects/pythonProject1/EOL_SyntaxError.py”, line 4
^
SyntaxError: unexpected EOF while parsing

✍️ Solution:

The above error occurred because Python found an unexpected end to the function func(). Therefore you can avoid this error by using a pass statement within the function or by using a print statement to print something that meets your requirement.

def foo():

    print(‘I love Java2Blog!’)

def func():

    pass

foo()

func()  # prints nothing

Output:

I love Java2Blog!

Scenario 2: Missing Parenthesis

If you forget to close all the parenthesis in a line of code within your program, then Python will raise the SyntaxError: unexpected EOF while parsing.

Example 1:

website = ‘Java2Blog’

author = ‘Shubham Sayon’

founder = ‘Arpit’

print(«The founder of {} is {} and its lead author is {}».format(website, founder, author)

Output:

SyntaxError: unexpected EOF while parsing

✍️ Solution:

The above error occurred because of a minute syntactic mistake wherein we forgot to close the print statement using a closing parenthesis ). To solve the error you simply have to close the print statement properly.

website = ‘Java2Blog’

author = ‘Shubham Sayon’

founder = ‘Arpit’

print(«The founder of {} is {} and its lead author is {}».format(website, founder, author))

Output:

The founder of Java2Blog is Arpit and its lead author is Shubham Sayon

Example 2:

Let us have a look at another case where a dictionary is incomplete and Python raises SyntaxError: unexpected EOF while parsing.

dict = {

    ‘blog’: ‘Java2Blog’,

    ‘lang’ : ‘Python’,

print(dict[‘blog’])

Output:

File “D:/PycharmProjects/pythonProject1/EOL_SyntaxError.py”, line 6
^
SyntaxError: unexpected EOF while parsing

✍️ Solution:

Close the dictionary using the closing parenthesis to avoid the error.

dict = {

    ‘blog’: ‘Java2Blog’,

    ‘lang’: ‘Python’,

}

print(dict[‘blog’])

Output:

Java2Blog

Scenario 3: Using try without except/finally

You will encounter the SyntaxError: unexpected EOF while parsing if you define a try block. However, you do not have an except or finally block.

Example:

try:

    print(«Hello Folks!»)

Output:

File “D:/PycharmProjects/pythonProject1/EOL_SyntaxError.py”, line 3
^
SyntaxError: unexpected EOF while parsing

✍️ Solution:

To overcome this error, you have to define an except or finally block corresponding to the try block.

try:

    print(«Hello Folks!»)

finally:

    pass

Output:

Hello Folks!

Scenario 4: Using the eval() function on str()

Python does not permit the usage of eval() function on str() and it leads to the SyntaxError: unexpected EOF while parsing.

Example:

text1 = ‘a string’

text3 = eval(str(text1))

if text1 == text3:

  print(«eval() Works!»)

Output:

File “D:/PycharmProjects/pythonProject1/EOL_SyntaxError.py”, line 2, in
text3 = eval(str(text1))
File “”, line 1
a string
^
SyntaxError: unexpected EOF while parsing

✍️ Solution:

To avoid the above error you can replace the str() function with the repr() function.

text1 = ‘a string’

text3 = eval(repr(text1))

if text1 == text3:

    print(«eval() Works!»)

Output:

eval() Works!

Conclusion

To summarize our discussion, “SyntaxError: unexpected EOF while parsing” error in Python occurs when Python reaches the end of execution abruptly before every line of code has finished its execution. This happens when:

  • The code has an incomplete Loop/Function/If Statement.
  • The code has a try block but no finally or except blocks.
  • You are trying to implement eval() on str().

To avoid this error, you should ensure that all the statements within your code are complete and have proper opening and closing parenthesis. Also, make sure that you define an except or finally block if the code has a try block.

I hope this article was helpful. Please subscribe and stay tuned for more exciting articles. Happy Learning! 📚

This chapter is taken from the book A Primer on Scientific Programming with Python by H. P. Langtangen, 5th edition, Springer, 2016.

Turning user text into live objects

It is possible to provide text with valid Python code as input to
a program and then turn the text into live objects as if the
text were written directly into the program beforehand.
This is a very powerful tool for letting users specify function formulas,
for instance, as input to a program. The program code itself has no
knowledge about the kind of function the user wants to work with, yet
at run time the user’s desired formula enters the computations.

The magic eval function

The eval functions takes a string as argument and
evaluates this string as a Python expression. The result of an
expression is an object. Consider

>>> r = eval('1+2')
>>> r
3
>>> type(r)
<type 'int'>

The result of r = eval('1+2') is the same as if we had written
r = 1+2 directly:

>>> r = 1+2
>>> r
3
>>> type(r)
<type 'int'>

In general, any valid Python expression stored as text in a string s
can be turned into live Python code by eval(s).

Here is an example where the string to be evaluated is '2.5', which
causes Python to see r = 2.5 and make a float object:

>>> r = eval('2.5')
>>> r
2.5
>>> type(r)
<type 'float'>

Let us proceed with some more examples. We can put the initialization of
a list inside quotes and use eval to make a list object:

>>> r = eval('[1, 6, 7.5]')
>>> r
[1, 6, 7.5]
>>> type(r)
<type 'list'>

Again, the assignment to r is equivalent to writing

We can also make a tuple object by using tuple syntax (standard
parentheses instead of brackets):

>>> r = eval('(-1, 1)')
>>> r
(-1, 1)
>>> type(r)
<type 'tuple'>

Another example reads

>>> from math import sqrt
>>> r = eval('sqrt(2)')
>>> r
1.4142135623730951
>>> type(r)
<type 'float'>

At the time we run eval('sqrt(2)'), this is the same as if we had
written

directly, and this is valid syntax only if the sqrt function is defined.
Therefore, the import of sqrt prior to running eval is
important in this example.

Applying eval to strings

If we put a string, enclosed in quotes, inside the expression string,
the result is a string object:

>>>
>>> r = eval('"math programming"')
>>> r
'math programming'
>>> type(r)
<type 'str'>

Note that we must use two types of quotes: first double quotes to mark
math programming as a string object and then another set of quotes,
here single quotes (but we could also have used triple single quotes),
to embed the text "math programming" inside a string. It does not
matter if we have single or double quotes as inner or outer quotes,
i.e., '"..."' is the same as "'...'", because ' and " are
interchangeable as long as a pair of either type is used consistently.

Writing just

>>> r = eval('math programming')

is the same as writing

which is an invalid expression. Python will in this case think that
math and programming are two (undefined) variables, and setting two
variables next to each other with a space in between is invalid Python
syntax. However,

>>> r = 'math programming'

is valid syntax, as this is how we initialize a string r in Python.
To repeat,
if we put the valid syntax 'math programming' inside a string,

eval(s) will evaluate the text inside the double quotes as
'math programming', which yields a string.

Applying eval to user input

So, why is the eval function so useful? When we get input via
raw_input or sys.argv, it is always in the form of a string
object, which often must be converted to another type of object,
usually an int or float. Sometimes we want to avoid specifying
one particular type. The eval function can then be of help: we feed
the string object from the input to the eval function and let the it
interpret the string and convert it to the right object.

An example may clarify the point. Consider a small program where we
read in two values and add them. The values could be strings, floats,
integers, lists, and so forth, as long as we can apply a + operator
to the values. Since we do not know if the user supplies a string,
float, integer, or something else, we just convert the input by
eval, which means that the user’s syntax will determine the type.
The program goes as follows
(add_input.py):

i1 = eval(raw_input('Give input: '))
i2 = eval(raw_input('Give input: '))
r = i1 + i2
print '%s + %s becomes %snwith value %s' % 
      (type(i1), type(i2), type(r), r)

Observe that we write out the two supplied values,
together with the types of the values (obtained by eval), and the sum.
Let us run the program with
an integer and a real number as input:

add_input.py
Give input: 4
Give input: 3.1
<type 'int'> + <type 'float'> becomes <type 'float'>
with value 7.1

The string '4', returned by the first call to raw_input,
is interpreted as an int by eval, while '3.1' gives
rise to a float object.

Supplying two lists also works fine:

add_input.py
Give input: [-1, 3.2]
Give input: [9,-2,0,0]
<type 'list'> + <type 'list'> becomes <type 'list'>
with value [-1, 3.2000000000000002, 9, -2, 0, 0]

If we want to use the program to add two strings, the strings must be
enclosed in quotes for eval to recognize the texts as string
objects (without the quotes, eval aborts with an error):

add_input.py
Give input: 'one string'
Give input: " and another string"
<type 'str'> + <type 'str'> becomes <type 'str'>
with value one string and another string

Not all objects are meaningful to add:

add_input.py
Give input: 3.2
Give input: [-1,10]
Traceback (most recent call last):
  File "add_input.py", line 3, in <module>
    r = i1 + i2
TypeError: unsupported operand type(s) for +: 'float' and 'list'

A similar program adding two arbitrary command-line arguments
reads ((add_input.py):

import sys
i1 = eval(sys.argv[1])
i2 = eval(sys.argv[2])
r = i1 + i2
print '%s + %s becomes %snwith value %s' % 
      (type(i1), type(i2), type(r), r)

Another important example on the usefulness of eval is to turn
formulas, given as input, into mathematics in the program. Consider
the program

from math import *   # make all math functions available
import sys
formula = sys.argv[1]
x = eval(sys.argv[2])
result = eval(formula)
print '%s for x=%g yields %g' % (formula, x, result)

Two command-line arguments are expected: a formula and a number. Say
the formula given is 2*sin(x)+1 and the number is 3.14. This
information is read from the command line as strings. Doing x =
eval(sys.argv[2])
means x = eval('3.14'), which is equivalent to x
= 3.14
, and x refers to a float object. The eval(formula)
expression means eval('2*sin(x)+1'), and the corresponding statement
result = eval(formula is therefore effectively result =
2*sin(x)+1
, which requires sin and x to be defined objects. The
result is a float (approximately 1.003). Providing cos(x) as the
first command-line argument creates a need to have cos defined, so
that is why we import all functions from the math module.
Let us try to run the program:

eval_formula.py "2*sin(x)+1" 3.14
2*sin(x)+1 for x=3.14 yields 1.00319

The very nice thing with using eval in x = eval(sys.argv[2]) is that
we can provide mathematical expressions like pi/2 or even
tanh(2*pi), as the latter just effectively results in the
statement x = tanh(2*pi), and this works fine as long has we have
imported tanh and pi.

The magic exec function

Having presented eval for turning strings into Python code, we take
the opportunity to also describe the related exec function to
execute a string containing arbitrary Python code, not only an
expression.

Suppose the user can write a formula as input to the
program, available to us in the form of a string object.
We would then like to turn this formula into a callable Python
function. For example, writing sin(x)*cos(3*x) + x**2 as the formula,
we would make the function

def f(x):
    return sin(x)*cos(3*x) + x**2

This is easy with exec: just construct the right Python syntax
for defining f(x) in a string and apply exec to the string,

formula = sys.argv[1]
code = """
def f(x):
    return %s
""" % formula
from math import *  # make sure we have sin, cos, exp, etc.
exec(code)

As an example,
think of "sin(x)*cos(3*x) + x**2" as the first command-line
argument. Then formula will hold this text, which is inserted into
the code string such that it becomes

"""
def f(x):
    return sin(x)*cos(3*x) + x**2
"""

Thereafter, exec(code) executes the code as if we had written
the contents of the code string directly into the program by hand.
With this technique, we can turn any user-given formula into a
Python function!

Let us now use this technique in a useful application.
Suppose we have made a function for computing
the integral ( int_a^b f(x)dx ) by the Midpoint rule
with ( n ) intervals:

def midpoint_integration(f, a, b, n=100):
    h = (b - a)/float(n)
    I = 0
    for i in range(n):
        I += f(a + i*h + 0.5*h)
    return h*I

We now want to read ( a ), ( b ), and ( n ) from the command line as well
as the formula that makes up the ( f(x) ) function:

from math import *
import sys
f_formula = sys.argv[1]
a = eval(sys.argv[2])
b = eval(sys.argv[3])
if len(sys.argv) >= 5:
    n = int(sys.argv[4])
else:
    n = 200

Note that we import everything from math and use eval when
reading the input for a and b as this will allow the user to
provide values like 2*cos(pi/3).

The next step is to convert the f_formula for ( f(x) ) into a
Python function g(x):

code = """
def g(x):
    return %s
""" % f_formula
exec(code)

Now we have an ordinary Python function g(x) that we can ask
the integration function to integrate:

I = midpoint_integration(g, a, b, n)
print 'Integral of %s on [%g, %g] with n=%d: %g' %  
      (f_formula, a, b, n, I)

The complete code is found in integrate.py. A sample run for ( int_0^{pi/2} sin(x)dx ) goes like

integrate.py "sin(x)" 0 pi/2
integral of sin(x) on [0, 1.5708] with n=200: 1

(The quotes in "sin(x)" are needed because of the parenthesis will
otherwise be interpreted by the shell.)

Turning string expressions into functions

The examples in the previous section indicate that it can be handy
to ask the user for a formula and turn that formula into a Python
function.
Since this operation is so useful, we have made a special tool that
hides the technicalities. The tool is named
StringFunction and works as follows:

>>> from scitools.StringFunction import StringFunction
>>> formula = 'exp(x)*sin(x)'
>>> f = StringFunction(formula)   # turn formula into f(x) func.

The f object now behaves as an ordinary Python function of x:

>>> f(0)
0.0
>>> f(pi)
2.8338239229952166e-15
>>> f(log(1))
0.0

Expressions involving other independent variables than x are also
possible.
Here is an example with the function
( g(t) = Ae^{-at}sin (omega x) ):

g = StringFunction('A*exp(-a*t)*sin(omega*x)',
                   independent_variable='t',
                   A=1, a=0.1, omega=pi, x=0.5)

The first argument is the function formula, as before, but now we need
to specify the name of the independent variable ('x' is default).
The other parameters in the function (( A ), ( a ), ( omega ), and ( x )) must be
specified with values, and we use keyword arguments, consistent with
the names in the function formula, for this purpose. Any of the parameters
A, a, omega, and x
can be changed later by calls like

g.set_parameters(omega=0.1)
g.set_parameters(omega=0.1, A=5, x=0)

Calling g(t) works as if g were a plain Python
function of t, which
also stores all the parameters A, a, omega, and
x, and their values. You can use pydoc
to bring
up more documentation on the possibilities with StringFunction.
Just run

pydoc scitools.StringFunction.StringFunction

A final important point is that StringFunction objects are as
computationally efficient as hand-written Python functions.
(This property is quite remarkable, as a string formula will in most
other programming languages be much slower to evaluate than if the
formula were hardcoded inside a plain function.)

Понравилась статья? Поделить с друзьями:
  • Escape from tarkov как изменить время рейда
  • Escape from tarkov unity error
  • Escape from tarkov network error
  • Escape from tarkov net framework ошибка
  • Escape from tarkov game aborted try reconnect как исправить ошибку