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
.
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
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With