Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Best way to evaluate boolean expressions from strings?

15 views
Skip to first unread message

Gustavo Narea

unread,
Apr 27, 2009, 1:03:01 PM4/27/09
to
Hello, everybody.

I need to evaluate boolean expressions like "foo == 1" or "foo ==1 and
(bar > 2 or bar == 0)" which are defined as strings (in a database or
a plain text file, for example). How would you achieve this?

These expressions will contain placeholders for Python objects (like
"foo" and "bar" in the examples above). Also, the Python objects that
will get injected in the expression will support at least one of the
following operations: "==", "!=", ">", "<", ">=", "<=", "&", "|",
"in".

I don't need the ability to import modules, define classes, define
functions, etc. I just need to evaluate boolean expressions defined as
strings (using the Python syntax is fine, or even desirable).

Here's a complete example:

I have the "user_ip" and "valid_ips" placeholders defined in Python as
follows:
"""
user_ip = '111.111.111.111'

class IPCollection(object):
def __init__(self, *valid_ips):
self.valid_ips = valid_ips
def __contains__(self, value):
return value in self.valid_ips

valid_ips = IPCollection('222.222.222.222', '111.111.111.111')
"""

So the following boolean expressions given as strings should be
evaluated as:
* "user_ip == '127.0.0.1'" ---> False
* "user_ip == '127.0.0.1' or user_ip in valid_ips" ---> True
* "user_ip not in valid_ips" ---> False

That's it. How would you deal with this? I would love to re-use
existing stuff as much as possible, that works in Python 2.4-2.6 and
also that has a simple syntax (these expressions may not be written by
technical people; hence I'm not sure about using TALES).

Thanks in advance!

Gabriel Genellina

unread,
Apr 27, 2009, 1:24:04 PM4/27/09
to pytho...@python.org
En Mon, 27 Apr 2009 14:03:01 -0300, Gustavo Narea <m...@gustavonarea.net>
escribiᅵ:

> I need to evaluate boolean expressions like "foo == 1" or "foo ==1 and
> (bar > 2 or bar == 0)" which are defined as strings (in a database or
> a plain text file, for example). How would you achieve this?

This recent thread may be of interest:

Safe eval of moderately simple math expressions
http://groups.google.com/group/comp.lang.python/t/c1aff28494ab5b59/

--
Gabriel Genellina

Peter Otten

unread,
Apr 27, 2009, 1:30:31 PM4/27/09
to
Gustavo Narea wrote:

exprs = [
"user_ip == '127.0.0.1'",
"user_ip == '127.0.0.1' or user_ip in valid_ips",
"user_ip not in valid_ips"]
for expr in exprs:
print expr, "-->", eval(expr)

Be warned that a malicious user can make your program execute arbitrary
python code; if you put the input form for expressions on the internet
you're toast.

Peter

Gustavo Narea

unread,
Apr 28, 2009, 10:51:43 AM4/28/09
to
Thank you very much, Gabriela and Peter!

I'm going for Pyparsing. :)

-- Gustavo.

Arnaud Delobelle

unread,
Apr 28, 2009, 4:09:36 PM4/28/09
to
Gustavo Narea <m...@gustavonarea.net> writes:

Here is a proof of concept using the ast module (Python >= 2.6):

----------------------------------------
import ast

class UnsafeError(Exception): pass

class SafetyChecker(ast.NodeVisitor):
def __init__(self, allowed_nodes, allowed_names):
self.allowed_nodes = allowed_nodes
self.allowed_names = allowed_names
def visit(self, node):
if isinstance(node, ast.Name):
if node.id in self.allowed_names:
return
raise UnsafeError('unsafe name: %s' % node.id)
if type(node) not in self.allowed_nodes:
if not any(tp in self.allowed_nodes for tp in type(node).__bases__):
raise UnsafeError('unsafe node: %s' % type(node).__name__)
ast.NodeVisitor.visit(self, node)

node_whitelist = [
'Expression', 'Load',
'operator', 'unaryop', 'UnaryOp', 'BoolOp', 'BinOp',
'boolop', 'cmpop', # Operators
'Num', 'Str', 'List', 'Tuple', # Literals
]

node_whitelist = [getattr(ast, name) for name in node_whitelist]

checker = SafetyChecker(node_whitelist, ['a', 'b', 'foo', 'bar'])

def safe_eval(expr, checker=checker):
t = ast.parse(expr, 'test.py', 'eval')
checker.visit(t)
return eval(expr)
----------------------------------------
Example:

>>> safe_eval('2*a - bar')
-4
>>> safe_eval('[1, 2] + [3, 4]')
[1, 2, 3, 4]
>>> safe_eval('f(foo)')
Traceback (most recent call last):
[...]
__main__.UnsafeError: unsafe node: Call
>>> safe_eval('x + 1')
Traceback (most recent call last):
[...]
__main__.UnsafeError: unsafe name: x
>>> safe_eval('lambda: x')
Traceback (most recent call last):
[...]
__main__.UnsafeError: unsafe node: Lambda
>>> safe_eval('foo or not foo')
'hello'


You'd have to tweak the node_whitelist using the info at

http://docs.python.org/library/ast.html#abstract-grammar

--
Arnaud

0 new messages