Создать лямбда-функцию из строки ** правильно **

Для строки, такой как

"2*(i+j) <= 100"

Я хочу сгенерировать соответствующую лямбда-функцию,

fn = lambda i,j: 2*(i+j) <= 100
  • Я могу сделать это с помощью eval, но я ищу менее злой метод.

  • Я нашел

    import ast
    f = ast.Lambda('i,j', '2*(i+j) <= 100')
    

    но я не знаю, как выполнить результат!

  • В идеале я хотел бы автоматически вывести список параметров ('i', 'j') - прямо сейчас, я просто использую re.findall('\ w +'), но я бы чтобы иметь возможность правильно использовать существующие функции, такие как cos вместо того, чтобы затенять их как "ключевые слова".


Я смотрел Есть ли библиотека Python для обработки сложных математических наборов (построена с использованием математической нотации set-builder)? и пытается выяснить, как лучше всего разобрать обозначение set-builder в lambdas для подачи на решатель ограничений.

Я в основном желаю ast.literal_eval, который также распознает переменные.

В идеале, учитывая i >= 20, я хотел бы вернуться ((lambda x: x >= 20), ['i']), который я мог бы затем напрямую передать на constraint.

Ответ 1

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

Если ваш ввод ненадежный, тогда он должен быть дезинфицирован.

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

Альтернативно, более сложный подход состоит в том, чтобы пройти дерево анализа АСТ, чтобы определить, имеются ли какие-либо нежелательные вызовы.

Третий подход состоит в том, чтобы пройти дерево анализа АСТ и выполнить его непосредственно. Это дает вам полный контроль над тем, что вызывает звонки. Функция ast.literal_eval использует такой подход. Возможно, вы начинаете со своего источника и выполняете некоторые сборки для любых операций, которые хотите поддерживать:

def literal_eval(node_or_string):
    """
    Safely evaluate an expression node or a string containing a Python
    expression.  The string or node provided may only consist of the following
    Python literal structures: strings, numbers, tuples, lists, dicts, booleans,
    and None.
    """
    _safe_names = {'None': None, 'True': True, 'False': False}
    if isinstance(node_or_string, basestring):
        node_or_string = parse(node_or_string, mode='eval')
    if isinstance(node_or_string, Expression):
        node_or_string = node_or_string.body
    def _convert(node):
        if isinstance(node, Str):
            return node.s
        elif isinstance(node, Num):
            return node.n
        elif isinstance(node, Tuple):
            return tuple(map(_convert, node.elts))
        elif isinstance(node, List):
            return list(map(_convert, node.elts))
        elif isinstance(node, Dict):
            return dict((_convert(k), _convert(v)) for k, v
                        in zip(node.keys, node.values))
        elif isinstance(node, Name):
            if node.id in _safe_names:
                return _safe_names[node.id]
        elif isinstance(node, BinOp) and \
             isinstance(node.op, (Add, Sub)) and \
             isinstance(node.right, Num) and \
             isinstance(node.right.n, complex) and \
             isinstance(node.left, Num) and \
             isinstance(node.left.n, (int, long, float)):
            left = node.left.n
            right = node.right.n
            if isinstance(node.op, Add):
                return left + right
            else:
                return left - right
        raise ValueError('malformed string')
    return _convert(node_or_string)

Ответ 2

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

Кроме того, имейте в виду, вы действительно не можете сделать это безопасным для этого в CPython