Калькулятор в python eval

Как правильно написать калькулятор на питоне с помощью eval()

В комментариях к статьям по синтаксическому анализу я иногда вижу такие:

на питоне калькулятор пишется проще простого — print(eval(input()))

Ну, вобщем‑то — да, но если, например, вы прикрутите такой калькулятор к своему сайту, то любой желающий вместо 2+2*2 может написать exec(«import os; os.removedirs(‘/’)») , предварительно изучив все ваши секретные файлы подобным же образом. Такая перспектива не может радовать, но и отказываться от eval() тоже не стоит.

— А что делать‑то? — спросите вы. Ответ простой: валидировать входящие данные, как вы это всегда делаете. Только не те, которые передаются функции eval() — для этого вам действительно пришлось бы написать свой синтаксический анализатор, а внутренние данные, которыми оперирует реализация eval() .

— А! Я знаю — скажете вы — используем compile() , валидируем код и тогда уже вызываем eval() . Что-ж, возможно и так. Но я не знаю простого способа валидировать байт‑код. Нет. Нам надо валидировать результат питоновского синтаксического анализатора — абстрактное синтаксическое дерево или AST. Это гораздо проще, хоть и звучит пугающе.

def my_eval(expression): tree = ast.parse(expression, mode='eval') code = compile(tree, filename='', mode='eval') return eval(code)

Нам, естественнно, понадобится модуль ast:

Итак, у нас есть tree . Вам, конечно же, любопытно, что там внутри, и действительно, надо же знать, что мы собираемся валидировать. Но просто так посмотреть не получится — это объект, и print() или pprint() выдадут всего лишь Я пользовался такой функцией:

def dump(node): def _format(node, indent): if isinstance(node, ast.AST): print('%sAST %s' % (' ' * indent, node.__class__.__name__)) for a, b in ast.iter_fields(node): print('%s%s' % (' ' * indent, a)) _format(b, indent + 4) elif isinstance(node, list): print('%sLIST %s' % (' ' * indent, node.__class__.__name__)) for x in node: _format(x, indent) else: print('%s%s' % (' ' * indent, repr(node))) _format(node, 0)

Можете вставить dump(tree) в my_eval(), поиграться и посмотреть, какое оно — это дерево. Только не надо копипастить мой пример с removedirs() .

Кому играться лениво — вот пара примеров:

>>> print(my_eval(input())) 2+2*2 AST Expression body AST BinOp left AST Constant value 2 kind None op AST Add right AST BinOp left AST Constant value 2 kind None op AST Mult right AST Constant value 2 kind None 6
>>> print(my_eval(input())) print(globals()) AST Expression body AST Call func AST Name id 'print' ctx AST Load args LIST list AST Call func AST Name id 'globals' ctx AST Load args LIST list keywords LIST list keywords LIST list , '__spec__': None, '__annotations__': <>, '__builtins__': , 'my_eval': , 'ast': , 'dump': > None

Видите, чем отличаются безопасные выражения от небезопасных? Безопасные содержат только Constant и BinOp , а небезопасные — всякие Call и Name . Полный список операций и других узлов дерева можно посмотреть в документации к модулю ast. Это поможет определиться, что запретить, а что разрешить. Для простого калькулятора, я считаю, достаточно разрешить основные операции BinOp и UnaryOp . Плюс Constant .

_allowed_nodes = ( # базовые узлы: ast.BinOp, ast.UnaryOp, ast.Constant, # основные BinOps: ast.Add, ast.Sub, ast.Mult, ast.Div, ast.FloorDiv, ast.Mod, ast.Pow, # основные UnaryOps: ast.UAdd, ast.USub ) def validate_ast(tree): # валидируем корень дерева if not isinstance(tree, ast.Expression): raise Exception('Неправильное выражение') # валидируем узлы def validate_children(node): for child in ast.iter_child_nodes(node): if not isinstance(child, _allowed_nodes): raise Exception('Неправильное выражение') validate_children(child) validate_children(tree)

Вот так всё просто. Смело вызывайте эту функцию из my_eval() перед compile() :

def my_eval(expression): tree = ast.parse(expression, mode='eval') validate_ast(tree) code = compile(tree, filename='', mode='eval') return eval(code)

и будет вам счастье. Но, имейте ввиду, документация к функции compile() сообщает:

Warning

It is possible to crash the Python interpreter with a sufficiently large/complex string when compiling to an AST object due to stack depth limitations in Python’s AST compiler.

Так что ограничивайте длину строки, передаваемой my_eval() . И тогда счастье будет настоящим.

Читайте также:  My Podcasts

— Постойте! — скажете вы — Мне не нужен простой бухгалтерский калькулятор! Где хоть какой-нибудь минимальный набор математических функций? Не отправлять же пользователя в пешее путешествие искать косинус фи?

М-да, пожалуй, соглашусь с вами. Давайте разрешим кое-что из модуля math . Но это будут вызовы функций, а значит — небезопасные области тьмы. Нам придётся добавить ast.Call , ast.Name и ast.Load в _allowed_nodes , и это открывает путь к exec() . Чтобы избежать неприятностей, надо ограничить контекст выполнения, в котором важно наличие пустого __builtins__ :

def my_eval(expression): tree = ast.parse(expression, mode='eval') validate_ast(tree) code = compile(tree, filename='', mode='eval') context = > return eval(code, context)

Плюс, надо добавить поддержку узлов-списков в валидатор:

def validate_ast(tree): # валидируем корень дерева if not isinstance(tree, ast.Expression): raise Exception('Неправильное выражение') # валидируем узлы def validate_children(node): for child in ast.iter_child_nodes(node): if isinstance(child, list): for grandchild in child: validate_children(grandchild) else: if not isinstance(child, _allowed_nodes): raise Exception('Неправильное выражение') validate_children(child) validate_children(tree)

NameError: name ‘exec’ is not defined

Ура! Ничего лучшего я, конечно, придумать не могу, но надеюсь что те, кто умнее меня, напишут в комментариях как это взломать и, соответственно, защититься.

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

import math def my_eval(expression): tree = ast.parse(expression, mode='eval') validate_ast(tree) code = compile(tree, filename='', mode='eval') context = < '__builtins__': <>, 'sin': math.sin, 'cos': math.cos > return eval(code, context)

Может показаться, что достаточно создать контекст всего один раз, вне функции, как мы это сделали с _allowed_nodes в валидаторе, но! — _allowed_nodes у нас всё-таки immutable, а для контекста есть риск модификации, случайной или преднамеренной. Известные аналогичные грабли — использование dict и list в качестве значений по умолчанию для аргументов. Поэтому лучше создавать контекст каждый раз, внутри функции.

Читайте также:  Python как изменить элемент словаря

Ну, вот собственно, и всё. Пользуйтесь и наслаждайтесь. Для тех, кому лень собирать копипастой всё в кучу, ловите полный исходник:

import ast import math def my_eval(expression): tree = ast.parse(expression, mode='eval') #dump(tree) validate_ast(tree) code = compile(tree, filename='', mode='eval') context = < '__builtins__': <>, 'sin': math.sin, 'cos': math.cos > return eval(code, context) _allowed_nodes = ( # базовые узлы: ast.BinOp, ast.UnaryOp, ast.Constant, ast.Call, ast.Name, ast.Load, # основные BinOps: ast.Add, ast.Sub, ast.Mult, ast.Div, ast.FloorDiv, ast.Mod, ast.Pow, # основные UnaryOps: ast.UAdd, ast.USub ) def validate_ast(tree): # валидируем корень дерева if not isinstance(tree, ast.Expression): raise Exception('Неправильное выражение') # валидируем узлы def validate_children(node): for child in ast.iter_child_nodes(node): if isinstance(child, list): for grandchild in child: validate_children(grandchild) else: if not isinstance(child, _allowed_nodes): raise Exception('Неправильное выражение') validate_children(child) validate_children(tree) def dump(node): def _format(node, indent): if isinstance(node, ast.AST): print('%sAST %s' % (' ' * indent, node.__class__.__name__)) for a, b in ast.iter_fields(node): print('%s%s' % (' ' * indent, a)) _format(b, indent + 4) elif isinstance(node, list): print('%sLIST %s' % (' ' * indent, node.__class__.__name__)) for x in node: _format(x, indent) else: print('%s%s' % (' ' * indent, repr(node))) _format(node, 0) print(my_eval(input()))

Источник

3 программы на Python в одну строку

3 программы в одну строку на python

Статьи

Введение

В данной статье напишем 3 программы на python в одну строку.

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

Калькулятор в одну строку

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

while True: print(eval(input('>>>')))

В самом начале программы создаётся бесконечный цикл while;

Функция print() выводит функцию eval();

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

Читайте также:  Home Page

Факториал в одну строку

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

Первый вариант

В данном варианте как раз будет присутствовать рекурсия, так что можно его использовать как её пример.

factorial = lambda x: factorial(x - 1) * x if x > 1 else 1

Создаём функцию factorial;

Вызываем функцию factorial внутри себя, тем самым делая рекурсию и умножая число на последующее;

В условии мы проверяем, закончилось ли введённое число при вычитании единицы с каждой итерацией.

Второй вариант

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

print('Factorial:',eval(str([i for i in range(1,int(input('number -> '))+1)]).replace(', ','*')[1:-1]))

В самом начале функция print() выводит функцию eval();

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

Срабатывает метод replace(), в котором находится срез [1:-1].

Проверка строки на палиндром в одну строку

Ну и последняя программа, которую мы напишем проверяет строку на палиндром с помощью среза.

palindrom = lambda a: 'Строка является палиндромом' if a == a[::-1] else "Строка не является палиндромом"

Создаём функцию palindrom;

Задаётся условие, что если строка является палиндромом, то в консоль выводится “Строка является палиндромом”, если же нет, то выводится “Строка не является палиндром”.

Заключение

И так, мы разобрали целых 3 программы, которые можно написать в одну строку с помощью python, надеюсь они Вам понравились!

На этом статься подходит к концу, спасибо всем кто читал 🙂

Источник

Оцените статью