Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

create a lambda function from a string **properly**

Given a string such as

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

I want to generate the corresponding lambda function,

fn = lambda i,j: 2*(i+j) <= 100
  • I can do this with eval, but I am seeking a less evil method.

  • I have found

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

    but I haven't figure out how to execute the result!

  • Ideally, I would like to automatically pull out the parameter list ('i','j') as well - right now, I am just using re.findall('\w+'), but I would love to be able to properly use existing functions like cos instead of shadowing them as 'keywords'.


I was looking at Is there a Python library for handling complicated mathematical sets (constructed using mathematical set-builder notation)? and trying to figure out how best to parse the set-builder notation into the lambdas to feed to the constraint-solver.

I'm basically wishing for ast.literal_eval which would also recognize variables.

Ideally, given i >= 20 I would like to get back ((lambda x: x >= 20), ['i']) which I could then feed directly to constraint.

like image 549
Hugh Bothwell Avatar asked Jun 20 '12 02:06

Hugh Bothwell


2 Answers

You're looking for an alternative to eval, but why? You're accepting arbitrary code and executing it anyway, so why not use eval? The only reason to avoid eval is because it's dangerous, but the lambda you end up creating will be just as dangerous.

Also, keep in mind, you really can't make it safe to do this in CPython

like image 124
Ned Batchelder Avatar answered Oct 16 '22 19:10

Ned Batchelder


If your input is from a trusted source, the eval() is the easiest, clearest, and most reliable way to go.

If your input is untrusted, then it needs to be sanitized.

One reasonable approach is the use of a regex. Make sure there are no function calls, attribute lookups, or double underscores in the string.

Alternatively, a more sophisticated approach is to walk the AST parse tree to determine whether there are any objectionable calls.

A third approach is to walk the AST parse tree and execute it directly. That puts you in complete control over what gets calls. The ast.literal_eval function takes this approach. Perhaps you start with its source and do some buildouts for any operations you want to support:

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)
like image 4
Raymond Hettinger Avatar answered Oct 16 '22 19:10

Raymond Hettinger