Skip to content

Commit 9012e5e

Browse files
committed
ENH: add boolean operators
1 parent b35406a commit 9012e5e

File tree

4 files changed

+40
-50
lines changed

4 files changed

+40
-50
lines changed

pandas/computation/expr.py

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ def _filter_nodes(superclass, all_nodes=_all_nodes):
151151
'IfExp', 'DictComp',
152152
'SetComp', 'Repr',
153153
'Lambda', 'Set', 'In',
154-
'NotIn', 'AST',
155-
'Is', 'IsNot'])) -
154+
'NotIn', 'AST', 'Is',
155+
'IsNot'])) -
156156
_hacked_nodes)
157157

158158
# we're adding a different assignment in some cases to be equality comparison
@@ -211,12 +211,12 @@ class BaseExprVisitor(ast.NodeVisitor):
211211
"""
212212
binary_ops = _cmp_ops_syms + _bool_ops_syms + _arith_ops_syms
213213
binary_op_nodes = ('Gt', 'Lt', 'GtE', 'LtE', 'Eq', 'NotEq', 'BitAnd',
214-
'BitOr', 'Add', 'Sub', 'Mult', 'Div', 'Pow', 'FloorDiv',
215-
'Mod')
214+
'BitOr', 'And', 'Or', 'Add', 'Sub', 'Mult', 'Div',
215+
'Pow', 'FloorDiv', 'Mod')
216216
binary_op_nodes_map = dict(itertools.izip(binary_ops, binary_op_nodes))
217217

218218
unary_ops = _unary_ops_syms
219-
unary_op_nodes = 'UAdd', 'USub', 'Invert'
219+
unary_op_nodes = 'UAdd', 'USub', 'Invert', 'Not'
220220
unary_op_nodes_map = dict(itertools.izip(unary_ops, unary_op_nodes))
221221

222222
def __init__(self, env, preparser=_preparse):
@@ -354,13 +354,31 @@ def visit_Compare(self, node, **kwargs):
354354
self.visit(comp, side='right'))
355355
return node
356356

357+
def visit_BoolOp(self, node, **kwargs):
358+
op = self.visit(node.op)
359+
def visitor(x, y):
360+
try:
361+
lhs = self.visit(x)
362+
except TypeError:
363+
lhs = x
364+
365+
try:
366+
rhs = self.visit(y)
367+
except TypeError:
368+
rhs = y
369+
370+
return op(lhs, rhs)
357371

358-
_python_not_supported = frozenset(['Assign', 'BoolOp', 'Not', 'Str', 'Slice',
359-
'Index', 'Subscript', 'Tuple', 'List',
360-
'Dict', 'Call'])
372+
operands = node.values
373+
return reduce(visitor, operands)
374+
375+
376+
_python_not_supported = frozenset(['Assign', 'Str', 'Slice', 'Index',
377+
'Subscript', 'Tuple', 'List', 'Dict',
378+
'Call'])
361379
_numexpr_supported_calls = frozenset(_reductions + _mathops)
362380

363-
@disallow(_unsupported_nodes | _python_not_supported)
381+
@disallow((_unsupported_nodes | _python_not_supported) - _boolop_nodes)
364382
class PandasExprVisitor(BaseExprVisitor):
365383
def __init__(self, env, preparser=_preparse):
366384
super(PandasExprVisitor, self).__init__(env, preparser)

pandas/computation/ops.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,15 @@ def _print_operand(opr):
107107
return opr.name if is_term(opr) else unicode(opr)
108108

109109

110+
def _get_op(op):
111+
return {'not': '~', 'and': '&', 'or': '|'}.get(op, op)
112+
113+
110114
class Op(StringMixin):
111115
"""Hold an operator of unknown arity
112116
"""
113117
def __init__(self, op, operands, *args, **kwargs):
114-
self.op = op
118+
self.op = _get_op(op)
115119
self.operands = operands
116120

117121
def __iter__(self):
@@ -137,8 +141,8 @@ def return_type(self):
137141
_cmp_ops_funcs = op.gt, op.lt, op.ge, op.le, op.eq, op.ne
138142
_cmp_ops_dict = dict(zip(_cmp_ops_syms, _cmp_ops_funcs))
139143

140-
_bool_ops_syms = '&', '|'
141-
_bool_ops_funcs = op.and_, op.or_
144+
_bool_ops_syms = '&', '|', 'and', 'or'
145+
_bool_ops_funcs = op.and_, op.or_, op.and_, op.or_
142146
_bool_ops_dict = dict(zip(_bool_ops_syms, _bool_ops_funcs))
143147

144148
_arith_ops_syms = '+', '-', '*', '/', '**', '//', '%'
@@ -237,8 +241,8 @@ def __init__(self, lhs, rhs, *args, **kwargs):
237241
_cast_inplace(self.operands, np.float_)
238242

239243

240-
_unary_ops_syms = '+', '-', '~'
241-
_unary_ops_funcs = op.pos, op.neg, op.invert
244+
_unary_ops_syms = '+', '-', '~', 'not'
245+
_unary_ops_funcs = op.pos, op.neg, op.invert, op.invert
242246
_unary_ops_dict = dict(zip(_unary_ops_syms, _unary_ops_funcs))
243247

244248

pandas/computation/pytables.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,6 @@ def prune(self, klass):
340340

341341
_op_classes = {'unary': UnaryOp}
342342

343-
344343
class ExprVisitor(BaseExprVisitor):
345344
def __init__(self, env, **kwargs):
346345
super(ExprVisitor, self).__init__(env)
@@ -366,6 +365,7 @@ def visit_USub(self, node, **kwargs):
366365
def visit_Index(self, node, **kwargs):
367366
return self.visit(node.value).value
368367

368+
369369
class Expr(expr.Expr):
370370

371371
""" hold a pytables like expression, comprised of possibly multiple 'terms'

pandas/computation/tests/test_eval.py

Lines changed: 3 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from pandas.computation.expr import PythonExprVisitor, PandasExprVisitor
2424
from pandas.computation.ops import (_binary_ops_dict, _unary_ops_dict,
2525
_special_case_arith_ops_syms,
26-
_arith_ops_syms)
26+
_arith_ops_syms, Constant)
2727
import pandas.computation.expr as expr
2828
from pandas.computation import pytables
2929
from pandas.computation.expressions import _USE_NUMEXPR
@@ -599,39 +599,6 @@ def test_is_expr():
599599
check_is_expr(engine)
600600

601601

602-
def check_not_fails(engine):
603-
x = True
604-
assert_raises(NotImplementedError, pd.eval, 'not x', engine=engine,
605-
local_dict={'x': x})
606-
607-
608-
def test_not_fails():
609-
for engine in _engines:
610-
check_not_fails(engine)
611-
612-
613-
def check_and_fails(engine):
614-
x, y = False, True
615-
assert_raises(NotImplementedError, pd.eval, 'x and y', engine=engine,
616-
local_dict={'x': x, 'y': y})
617-
618-
619-
def test_and_fails():
620-
for engine in _engines:
621-
check_and_fails(engine)
622-
623-
624-
def check_or_fails(engine):
625-
x, y = True, False
626-
assert_raises(NotImplementedError, pd.eval, 'x or y', engine=engine,
627-
local_dict={'x': x, 'y': y})
628-
629-
630-
def test_or_fails():
631-
for engine in _engines:
632-
check_or_fails(engine)
633-
634-
635602
_parsers = {'python': PythonExprVisitor, 'pytables': pytables.ExprVisitor,
636603
'pandas': PandasExprVisitor}
637604

@@ -641,8 +608,9 @@ def check_disallowed_nodes(visitor):
641608
VisitorClass = _parsers[visitor]
642609
uns_ops = VisitorClass.unsupported_nodes
643610
inst = VisitorClass('x + 1')
611+
644612
for ops in uns_ops:
645-
assert_raises(NotImplementedError, getattr(inst, ops), inst, ast.AST())
613+
assert_raises(NotImplementedError, getattr(inst, ops))
646614

647615

648616
def test_disallowed_nodes():

0 commit comments

Comments
 (0)