expr.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776
  1. """:func:`~pandas.eval` parsers
  2. """
  3. import ast
  4. from functools import partial
  5. import tokenize
  6. import numpy as np
  7. from pandas.compat import StringIO, lmap, reduce, string_types, zip
  8. import pandas as pd
  9. from pandas import compat
  10. from pandas.core import common as com
  11. from pandas.core.base import StringMixin
  12. from pandas.core.computation.ops import (
  13. _LOCAL_TAG, BinOp, Constant, Div, FuncNode, Op, Term, UnaryOp,
  14. UndefinedVariableError, _arith_ops_syms, _bool_ops_syms, _cmp_ops_syms,
  15. _mathops, _reductions, _unary_ops_syms, is_term)
  16. from pandas.core.computation.scope import Scope
  17. import pandas.io.formats.printing as printing
  18. def tokenize_string(source):
  19. """Tokenize a Python source code string.
  20. Parameters
  21. ----------
  22. source : str
  23. A Python source code string
  24. """
  25. line_reader = StringIO(source).readline
  26. for toknum, tokval, _, _, _ in tokenize.generate_tokens(line_reader):
  27. yield toknum, tokval
  28. def _rewrite_assign(tok):
  29. """Rewrite the assignment operator for PyTables expressions that use ``=``
  30. as a substitute for ``==``.
  31. Parameters
  32. ----------
  33. tok : tuple of int, str
  34. ints correspond to the all caps constants in the tokenize module
  35. Returns
  36. -------
  37. t : tuple of int, str
  38. Either the input or token or the replacement values
  39. """
  40. toknum, tokval = tok
  41. return toknum, '==' if tokval == '=' else tokval
  42. def _replace_booleans(tok):
  43. """Replace ``&`` with ``and`` and ``|`` with ``or`` so that bitwise
  44. precedence is changed to boolean precedence.
  45. Parameters
  46. ----------
  47. tok : tuple of int, str
  48. ints correspond to the all caps constants in the tokenize module
  49. Returns
  50. -------
  51. t : tuple of int, str
  52. Either the input or token or the replacement values
  53. """
  54. toknum, tokval = tok
  55. if toknum == tokenize.OP:
  56. if tokval == '&':
  57. return tokenize.NAME, 'and'
  58. elif tokval == '|':
  59. return tokenize.NAME, 'or'
  60. return toknum, tokval
  61. return toknum, tokval
  62. def _replace_locals(tok):
  63. """Replace local variables with a syntactically valid name.
  64. Parameters
  65. ----------
  66. tok : tuple of int, str
  67. ints correspond to the all caps constants in the tokenize module
  68. Returns
  69. -------
  70. t : tuple of int, str
  71. Either the input or token or the replacement values
  72. Notes
  73. -----
  74. This is somewhat of a hack in that we rewrite a string such as ``'@a'`` as
  75. ``'__pd_eval_local_a'`` by telling the tokenizer that ``__pd_eval_local_``
  76. is a ``tokenize.OP`` and to replace the ``'@'`` symbol with it.
  77. """
  78. toknum, tokval = tok
  79. if toknum == tokenize.OP and tokval == '@':
  80. return tokenize.OP, _LOCAL_TAG
  81. return toknum, tokval
  82. def _compose2(f, g):
  83. """Compose 2 callables"""
  84. return lambda *args, **kwargs: f(g(*args, **kwargs))
  85. def _compose(*funcs):
  86. """Compose 2 or more callables"""
  87. assert len(funcs) > 1, 'At least 2 callables must be passed to compose'
  88. return reduce(_compose2, funcs)
  89. def _preparse(source, f=_compose(_replace_locals, _replace_booleans,
  90. _rewrite_assign)):
  91. """Compose a collection of tokenization functions
  92. Parameters
  93. ----------
  94. source : str
  95. A Python source code string
  96. f : callable
  97. This takes a tuple of (toknum, tokval) as its argument and returns a
  98. tuple with the same structure but possibly different elements. Defaults
  99. to the composition of ``_rewrite_assign``, ``_replace_booleans``, and
  100. ``_replace_locals``.
  101. Returns
  102. -------
  103. s : str
  104. Valid Python source code
  105. Notes
  106. -----
  107. The `f` parameter can be any callable that takes *and* returns input of the
  108. form ``(toknum, tokval)``, where ``toknum`` is one of the constants from
  109. the ``tokenize`` module and ``tokval`` is a string.
  110. """
  111. assert callable(f), 'f must be callable'
  112. return tokenize.untokenize(lmap(f, tokenize_string(source)))
  113. def _is_type(t):
  114. """Factory for a type checking function of type ``t`` or tuple of types."""
  115. return lambda x: isinstance(x.value, t)
  116. _is_list = _is_type(list)
  117. _is_str = _is_type(string_types)
  118. # partition all AST nodes
  119. _all_nodes = frozenset(filter(lambda x: isinstance(x, type) and
  120. issubclass(x, ast.AST),
  121. (getattr(ast, node) for node in dir(ast))))
  122. def _filter_nodes(superclass, all_nodes=_all_nodes):
  123. """Filter out AST nodes that are subclasses of ``superclass``."""
  124. node_names = (node.__name__ for node in all_nodes
  125. if issubclass(node, superclass))
  126. return frozenset(node_names)
  127. _all_node_names = frozenset(map(lambda x: x.__name__, _all_nodes))
  128. _mod_nodes = _filter_nodes(ast.mod)
  129. _stmt_nodes = _filter_nodes(ast.stmt)
  130. _expr_nodes = _filter_nodes(ast.expr)
  131. _expr_context_nodes = _filter_nodes(ast.expr_context)
  132. _slice_nodes = _filter_nodes(ast.slice)
  133. _boolop_nodes = _filter_nodes(ast.boolop)
  134. _operator_nodes = _filter_nodes(ast.operator)
  135. _unary_op_nodes = _filter_nodes(ast.unaryop)
  136. _cmp_op_nodes = _filter_nodes(ast.cmpop)
  137. _comprehension_nodes = _filter_nodes(ast.comprehension)
  138. _handler_nodes = _filter_nodes(ast.excepthandler)
  139. _arguments_nodes = _filter_nodes(ast.arguments)
  140. _keyword_nodes = _filter_nodes(ast.keyword)
  141. _alias_nodes = _filter_nodes(ast.alias)
  142. # nodes that we don't support directly but are needed for parsing
  143. _hacked_nodes = frozenset(['Assign', 'Module', 'Expr'])
  144. _unsupported_expr_nodes = frozenset(['Yield', 'GeneratorExp', 'IfExp',
  145. 'DictComp', 'SetComp', 'Repr', 'Lambda',
  146. 'Set', 'AST', 'Is', 'IsNot'])
  147. # these nodes are low priority or won't ever be supported (e.g., AST)
  148. _unsupported_nodes = ((_stmt_nodes | _mod_nodes | _handler_nodes |
  149. _arguments_nodes | _keyword_nodes | _alias_nodes |
  150. _expr_context_nodes | _unsupported_expr_nodes) -
  151. _hacked_nodes)
  152. # we're adding a different assignment in some cases to be equality comparison
  153. # and we don't want `stmt` and friends in their so get only the class whose
  154. # names are capitalized
  155. _base_supported_nodes = (_all_node_names - _unsupported_nodes) | _hacked_nodes
  156. _msg = 'cannot both support and not support {intersection}'.format(
  157. intersection=_unsupported_nodes & _base_supported_nodes)
  158. assert not _unsupported_nodes & _base_supported_nodes, _msg
  159. def _node_not_implemented(node_name, cls):
  160. """Return a function that raises a NotImplementedError with a passed node
  161. name.
  162. """
  163. def f(self, *args, **kwargs):
  164. raise NotImplementedError("{name!r} nodes are not "
  165. "implemented".format(name=node_name))
  166. return f
  167. def disallow(nodes):
  168. """Decorator to disallow certain nodes from parsing. Raises a
  169. NotImplementedError instead.
  170. Returns
  171. -------
  172. disallowed : callable
  173. """
  174. def disallowed(cls):
  175. cls.unsupported_nodes = ()
  176. for node in nodes:
  177. new_method = _node_not_implemented(node, cls)
  178. name = 'visit_{node}'.format(node=node)
  179. cls.unsupported_nodes += (name,)
  180. setattr(cls, name, new_method)
  181. return cls
  182. return disallowed
  183. def _op_maker(op_class, op_symbol):
  184. """Return a function to create an op class with its symbol already passed.
  185. Returns
  186. -------
  187. f : callable
  188. """
  189. def f(self, node, *args, **kwargs):
  190. """Return a partial function with an Op subclass with an operator
  191. already passed.
  192. Returns
  193. -------
  194. f : callable
  195. """
  196. return partial(op_class, op_symbol, *args, **kwargs)
  197. return f
  198. _op_classes = {'binary': BinOp, 'unary': UnaryOp}
  199. def add_ops(op_classes):
  200. """Decorator to add default implementation of ops."""
  201. def f(cls):
  202. for op_attr_name, op_class in compat.iteritems(op_classes):
  203. ops = getattr(cls, '{name}_ops'.format(name=op_attr_name))
  204. ops_map = getattr(cls, '{name}_op_nodes_map'.format(
  205. name=op_attr_name))
  206. for op in ops:
  207. op_node = ops_map[op]
  208. if op_node is not None:
  209. made_op = _op_maker(op_class, op)
  210. setattr(cls, 'visit_{node}'.format(node=op_node), made_op)
  211. return cls
  212. return f
  213. @disallow(_unsupported_nodes)
  214. @add_ops(_op_classes)
  215. class BaseExprVisitor(ast.NodeVisitor):
  216. """Custom ast walker. Parsers of other engines should subclass this class
  217. if necessary.
  218. Parameters
  219. ----------
  220. env : Scope
  221. engine : str
  222. parser : str
  223. preparser : callable
  224. """
  225. const_type = Constant
  226. term_type = Term
  227. binary_ops = _cmp_ops_syms + _bool_ops_syms + _arith_ops_syms
  228. binary_op_nodes = ('Gt', 'Lt', 'GtE', 'LtE', 'Eq', 'NotEq', 'In', 'NotIn',
  229. 'BitAnd', 'BitOr', 'And', 'Or', 'Add', 'Sub', 'Mult',
  230. None, 'Pow', 'FloorDiv', 'Mod')
  231. binary_op_nodes_map = dict(zip(binary_ops, binary_op_nodes))
  232. unary_ops = _unary_ops_syms
  233. unary_op_nodes = 'UAdd', 'USub', 'Invert', 'Not'
  234. unary_op_nodes_map = dict(zip(unary_ops, unary_op_nodes))
  235. rewrite_map = {
  236. ast.Eq: ast.In,
  237. ast.NotEq: ast.NotIn,
  238. ast.In: ast.In,
  239. ast.NotIn: ast.NotIn
  240. }
  241. def __init__(self, env, engine, parser, preparser=_preparse):
  242. self.env = env
  243. self.engine = engine
  244. self.parser = parser
  245. self.preparser = preparser
  246. self.assigner = None
  247. def visit(self, node, **kwargs):
  248. if isinstance(node, string_types):
  249. clean = self.preparser(node)
  250. try:
  251. node = ast.fix_missing_locations(ast.parse(clean))
  252. except SyntaxError as e:
  253. from keyword import iskeyword
  254. if any(iskeyword(x) for x in clean.split()):
  255. e.msg = ("Python keyword not valid identifier"
  256. " in numexpr query")
  257. raise e
  258. method = 'visit_' + node.__class__.__name__
  259. visitor = getattr(self, method)
  260. return visitor(node, **kwargs)
  261. def visit_Module(self, node, **kwargs):
  262. if len(node.body) != 1:
  263. raise SyntaxError('only a single expression is allowed')
  264. expr = node.body[0]
  265. return self.visit(expr, **kwargs)
  266. def visit_Expr(self, node, **kwargs):
  267. return self.visit(node.value, **kwargs)
  268. def _rewrite_membership_op(self, node, left, right):
  269. # the kind of the operator (is actually an instance)
  270. op_instance = node.op
  271. op_type = type(op_instance)
  272. # must be two terms and the comparison operator must be ==/!=/in/not in
  273. if is_term(left) and is_term(right) and op_type in self.rewrite_map:
  274. left_list, right_list = map(_is_list, (left, right))
  275. left_str, right_str = map(_is_str, (left, right))
  276. # if there are any strings or lists in the expression
  277. if left_list or right_list or left_str or right_str:
  278. op_instance = self.rewrite_map[op_type]()
  279. # pop the string variable out of locals and replace it with a list
  280. # of one string, kind of a hack
  281. if right_str:
  282. name = self.env.add_tmp([right.value])
  283. right = self.term_type(name, self.env)
  284. if left_str:
  285. name = self.env.add_tmp([left.value])
  286. left = self.term_type(name, self.env)
  287. op = self.visit(op_instance)
  288. return op, op_instance, left, right
  289. def _maybe_transform_eq_ne(self, node, left=None, right=None):
  290. if left is None:
  291. left = self.visit(node.left, side='left')
  292. if right is None:
  293. right = self.visit(node.right, side='right')
  294. op, op_class, left, right = self._rewrite_membership_op(node, left,
  295. right)
  296. return op, op_class, left, right
  297. def _maybe_downcast_constants(self, left, right):
  298. f32 = np.dtype(np.float32)
  299. if left.is_scalar and not right.is_scalar and right.return_type == f32:
  300. # right is a float32 array, left is a scalar
  301. name = self.env.add_tmp(np.float32(left.value))
  302. left = self.term_type(name, self.env)
  303. if right.is_scalar and not left.is_scalar and left.return_type == f32:
  304. # left is a float32 array, right is a scalar
  305. name = self.env.add_tmp(np.float32(right.value))
  306. right = self.term_type(name, self.env)
  307. return left, right
  308. def _maybe_eval(self, binop, eval_in_python):
  309. # eval `in` and `not in` (for now) in "partial" python space
  310. # things that can be evaluated in "eval" space will be turned into
  311. # temporary variables. for example,
  312. # [1,2] in a + 2 * b
  313. # in that case a + 2 * b will be evaluated using numexpr, and the "in"
  314. # call will be evaluated using isin (in python space)
  315. return binop.evaluate(self.env, self.engine, self.parser,
  316. self.term_type, eval_in_python)
  317. def _maybe_evaluate_binop(self, op, op_class, lhs, rhs,
  318. eval_in_python=('in', 'not in'),
  319. maybe_eval_in_python=('==', '!=', '<', '>',
  320. '<=', '>=')):
  321. res = op(lhs, rhs)
  322. if res.has_invalid_return_type:
  323. raise TypeError("unsupported operand type(s) for {op}:"
  324. " '{lhs}' and '{rhs}'".format(op=res.op,
  325. lhs=lhs.type,
  326. rhs=rhs.type))
  327. if self.engine != 'pytables':
  328. if (res.op in _cmp_ops_syms and
  329. getattr(lhs, 'is_datetime', False) or
  330. getattr(rhs, 'is_datetime', False)):
  331. # all date ops must be done in python bc numexpr doesn't work
  332. # well with NaT
  333. return self._maybe_eval(res, self.binary_ops)
  334. if res.op in eval_in_python:
  335. # "in"/"not in" ops are always evaluated in python
  336. return self._maybe_eval(res, eval_in_python)
  337. elif self.engine != 'pytables':
  338. if (getattr(lhs, 'return_type', None) == object or
  339. getattr(rhs, 'return_type', None) == object):
  340. # evaluate "==" and "!=" in python if either of our operands
  341. # has an object return type
  342. return self._maybe_eval(res, eval_in_python +
  343. maybe_eval_in_python)
  344. return res
  345. def visit_BinOp(self, node, **kwargs):
  346. op, op_class, left, right = self._maybe_transform_eq_ne(node)
  347. left, right = self._maybe_downcast_constants(left, right)
  348. return self._maybe_evaluate_binop(op, op_class, left, right)
  349. def visit_Div(self, node, **kwargs):
  350. truediv = self.env.scope['truediv']
  351. return lambda lhs, rhs: Div(lhs, rhs, truediv)
  352. def visit_UnaryOp(self, node, **kwargs):
  353. op = self.visit(node.op)
  354. operand = self.visit(node.operand)
  355. return op(operand)
  356. def visit_Name(self, node, **kwargs):
  357. return self.term_type(node.id, self.env, **kwargs)
  358. def visit_NameConstant(self, node, **kwargs):
  359. return self.const_type(node.value, self.env)
  360. def visit_Num(self, node, **kwargs):
  361. return self.const_type(node.n, self.env)
  362. def visit_Str(self, node, **kwargs):
  363. name = self.env.add_tmp(node.s)
  364. return self.term_type(name, self.env)
  365. def visit_List(self, node, **kwargs):
  366. name = self.env.add_tmp([self.visit(e)(self.env) for e in node.elts])
  367. return self.term_type(name, self.env)
  368. visit_Tuple = visit_List
  369. def visit_Index(self, node, **kwargs):
  370. """ df.index[4] """
  371. return self.visit(node.value)
  372. def visit_Subscript(self, node, **kwargs):
  373. value = self.visit(node.value)
  374. slobj = self.visit(node.slice)
  375. result = pd.eval(slobj, local_dict=self.env, engine=self.engine,
  376. parser=self.parser)
  377. try:
  378. # a Term instance
  379. v = value.value[result]
  380. except AttributeError:
  381. # an Op instance
  382. lhs = pd.eval(value, local_dict=self.env, engine=self.engine,
  383. parser=self.parser)
  384. v = lhs[result]
  385. name = self.env.add_tmp(v)
  386. return self.term_type(name, env=self.env)
  387. def visit_Slice(self, node, **kwargs):
  388. """ df.index[slice(4,6)] """
  389. lower = node.lower
  390. if lower is not None:
  391. lower = self.visit(lower).value
  392. upper = node.upper
  393. if upper is not None:
  394. upper = self.visit(upper).value
  395. step = node.step
  396. if step is not None:
  397. step = self.visit(step).value
  398. return slice(lower, upper, step)
  399. def visit_Assign(self, node, **kwargs):
  400. """
  401. support a single assignment node, like
  402. c = a + b
  403. set the assigner at the top level, must be a Name node which
  404. might or might not exist in the resolvers
  405. """
  406. if len(node.targets) != 1:
  407. raise SyntaxError('can only assign a single expression')
  408. if not isinstance(node.targets[0], ast.Name):
  409. raise SyntaxError('left hand side of an assignment must be a '
  410. 'single name')
  411. if self.env.target is None:
  412. raise ValueError('cannot assign without a target object')
  413. try:
  414. assigner = self.visit(node.targets[0], **kwargs)
  415. except UndefinedVariableError:
  416. assigner = node.targets[0].id
  417. self.assigner = getattr(assigner, 'name', assigner)
  418. if self.assigner is None:
  419. raise SyntaxError('left hand side of an assignment must be a '
  420. 'single resolvable name')
  421. return self.visit(node.value, **kwargs)
  422. def visit_Attribute(self, node, **kwargs):
  423. attr = node.attr
  424. value = node.value
  425. ctx = node.ctx
  426. if isinstance(ctx, ast.Load):
  427. # resolve the value
  428. resolved = self.visit(value).value
  429. try:
  430. v = getattr(resolved, attr)
  431. name = self.env.add_tmp(v)
  432. return self.term_type(name, self.env)
  433. except AttributeError:
  434. # something like datetime.datetime where scope is overridden
  435. if isinstance(value, ast.Name) and value.id == attr:
  436. return resolved
  437. raise ValueError("Invalid Attribute context {name}"
  438. .format(name=ctx.__name__))
  439. def visit_Call_35(self, node, side=None, **kwargs):
  440. """ in 3.5 the starargs attribute was changed to be more flexible,
  441. #11097 """
  442. if isinstance(node.func, ast.Attribute):
  443. res = self.visit_Attribute(node.func)
  444. elif not isinstance(node.func, ast.Name):
  445. raise TypeError("Only named functions are supported")
  446. else:
  447. try:
  448. res = self.visit(node.func)
  449. except UndefinedVariableError:
  450. # Check if this is a supported function name
  451. try:
  452. res = FuncNode(node.func.id)
  453. except ValueError:
  454. # Raise original error
  455. raise
  456. if res is None:
  457. raise ValueError("Invalid function call {func}"
  458. .format(func=node.func.id))
  459. if hasattr(res, 'value'):
  460. res = res.value
  461. if isinstance(res, FuncNode):
  462. new_args = [self.visit(arg) for arg in node.args]
  463. if node.keywords:
  464. raise TypeError("Function \"{name}\" does not support keyword "
  465. "arguments".format(name=res.name))
  466. return res(*new_args, **kwargs)
  467. else:
  468. new_args = [self.visit(arg).value for arg in node.args]
  469. for key in node.keywords:
  470. if not isinstance(key, ast.keyword):
  471. raise ValueError("keyword error in function call "
  472. "'{func}'".format(func=node.func.id))
  473. if key.arg:
  474. # TODO: bug?
  475. kwargs.append(ast.keyword(
  476. keyword.arg, self.visit(keyword.value))) # noqa
  477. return self.const_type(res(*new_args, **kwargs), self.env)
  478. def visit_Call_legacy(self, node, side=None, **kwargs):
  479. # this can happen with: datetime.datetime
  480. if isinstance(node.func, ast.Attribute):
  481. res = self.visit_Attribute(node.func)
  482. elif not isinstance(node.func, ast.Name):
  483. raise TypeError("Only named functions are supported")
  484. else:
  485. try:
  486. res = self.visit(node.func)
  487. except UndefinedVariableError:
  488. # Check if this is a supported function name
  489. try:
  490. res = FuncNode(node.func.id)
  491. except ValueError:
  492. # Raise original error
  493. raise
  494. if res is None:
  495. raise ValueError("Invalid function call {func}"
  496. .format(func=node.func.id))
  497. if hasattr(res, 'value'):
  498. res = res.value
  499. if isinstance(res, FuncNode):
  500. args = [self.visit(targ) for targ in node.args]
  501. if node.starargs is not None:
  502. args += self.visit(node.starargs)
  503. if node.keywords or node.kwargs:
  504. raise TypeError("Function \"{name}\" does not support keyword "
  505. "arguments".format(name=res.name))
  506. return res(*args, **kwargs)
  507. else:
  508. args = [self.visit(targ).value for targ in node.args]
  509. if node.starargs is not None:
  510. args += self.visit(node.starargs).value
  511. keywords = {}
  512. for key in node.keywords:
  513. if not isinstance(key, ast.keyword):
  514. raise ValueError("keyword error in function call "
  515. "'{func}'".format(func=node.func.id))
  516. keywords[key.arg] = self.visit(key.value).value
  517. if node.kwargs is not None:
  518. keywords.update(self.visit(node.kwargs).value)
  519. return self.const_type(res(*args, **keywords), self.env)
  520. def translate_In(self, op):
  521. return op
  522. def visit_Compare(self, node, **kwargs):
  523. ops = node.ops
  524. comps = node.comparators
  525. # base case: we have something like a CMP b
  526. if len(comps) == 1:
  527. op = self.translate_In(ops[0])
  528. binop = ast.BinOp(op=op, left=node.left, right=comps[0])
  529. return self.visit(binop)
  530. # recursive case: we have a chained comparison, a CMP b CMP c, etc.
  531. left = node.left
  532. values = []
  533. for op, comp in zip(ops, comps):
  534. new_node = self.visit(ast.Compare(comparators=[comp], left=left,
  535. ops=[self.translate_In(op)]))
  536. left = comp
  537. values.append(new_node)
  538. return self.visit(ast.BoolOp(op=ast.And(), values=values))
  539. def _try_visit_binop(self, bop):
  540. if isinstance(bop, (Op, Term)):
  541. return bop
  542. return self.visit(bop)
  543. def visit_BoolOp(self, node, **kwargs):
  544. def visitor(x, y):
  545. lhs = self._try_visit_binop(x)
  546. rhs = self._try_visit_binop(y)
  547. op, op_class, lhs, rhs = self._maybe_transform_eq_ne(
  548. node, lhs, rhs)
  549. return self._maybe_evaluate_binop(op, node.op, lhs, rhs)
  550. operands = node.values
  551. return reduce(visitor, operands)
  552. # ast.Call signature changed on 3.5,
  553. # conditionally change which methods is named
  554. # visit_Call depending on Python version, #11097
  555. if compat.PY35:
  556. BaseExprVisitor.visit_Call = BaseExprVisitor.visit_Call_35
  557. else:
  558. BaseExprVisitor.visit_Call = BaseExprVisitor.visit_Call_legacy
  559. _python_not_supported = frozenset(['Dict', 'BoolOp', 'In', 'NotIn'])
  560. _numexpr_supported_calls = frozenset(_reductions + _mathops)
  561. @disallow((_unsupported_nodes | _python_not_supported) -
  562. (_boolop_nodes | frozenset(['BoolOp', 'Attribute', 'In', 'NotIn',
  563. 'Tuple'])))
  564. class PandasExprVisitor(BaseExprVisitor):
  565. def __init__(self, env, engine, parser,
  566. preparser=partial(_preparse, f=_compose(_replace_locals,
  567. _replace_booleans))):
  568. super(PandasExprVisitor, self).__init__(env, engine, parser, preparser)
  569. @disallow(_unsupported_nodes | _python_not_supported | frozenset(['Not']))
  570. class PythonExprVisitor(BaseExprVisitor):
  571. def __init__(self, env, engine, parser, preparser=lambda x: x):
  572. super(PythonExprVisitor, self).__init__(env, engine, parser,
  573. preparser=preparser)
  574. class Expr(StringMixin):
  575. """Object encapsulating an expression.
  576. Parameters
  577. ----------
  578. expr : str
  579. engine : str, optional, default 'numexpr'
  580. parser : str, optional, default 'pandas'
  581. env : Scope, optional, default None
  582. truediv : bool, optional, default True
  583. level : int, optional, default 2
  584. """
  585. def __init__(self, expr, engine='numexpr', parser='pandas', env=None,
  586. truediv=True, level=0):
  587. self.expr = expr
  588. self.env = env or Scope(level=level + 1)
  589. self.engine = engine
  590. self.parser = parser
  591. self.env.scope['truediv'] = truediv
  592. self._visitor = _parsers[parser](self.env, self.engine, self.parser)
  593. self.terms = self.parse()
  594. @property
  595. def assigner(self):
  596. return getattr(self._visitor, 'assigner', None)
  597. def __call__(self):
  598. return self.terms(self.env)
  599. def __unicode__(self):
  600. return printing.pprint_thing(self.terms)
  601. def __len__(self):
  602. return len(self.expr)
  603. def parse(self):
  604. """Parse an expression"""
  605. return self._visitor.visit(self.expr)
  606. @property
  607. def names(self):
  608. """Get the names in an expression"""
  609. if is_term(self.terms):
  610. return frozenset([self.terms.name])
  611. return frozenset(term.name for term in com.flatten(self.terms))
  612. _parsers = {'python': PythonExprVisitor, 'pandas': PandasExprVisitor}