Skip to content

Commit 2450678

Browse files
committed
ENH/CLN: track scope instead of hard coding minimal stack level
1 parent fc84fde commit 2450678

File tree

6 files changed

+305
-163
lines changed

6 files changed

+305
-163
lines changed

pandas/computation/engines.py

+31-5
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,34 @@
44
import abc
55

66
from pandas import compat
7-
from pandas.compat import DeepChainMap
7+
from pandas.compat import DeepChainMap, map
88
from pandas.core import common as com
99
from pandas.computation.align import _align, _reconstruct_object
10-
from pandas.computation.ops import UndefinedVariableError
10+
from pandas.computation.ops import UndefinedVariableError, _mathops, _reductions
11+
12+
13+
_ne_builtins = frozenset(_mathops + _reductions)
14+
15+
16+
class NumExprClobberingError(NameError):
17+
pass
18+
19+
20+
def _check_ne_builtin_clash(expr):
21+
"""Attempt to prevent foot-shooting in a helpful way.
22+
23+
Parameters
24+
----------
25+
terms : Term
26+
Terms can contain
27+
"""
28+
names = expr.names
29+
overlap = names & _ne_builtins
30+
31+
if overlap:
32+
s = ', '.join(map(repr, overlap))
33+
raise NumExprClobberingError('Variables in expression "%s" overlap with '
34+
'numexpr builtins: (%s)' % (expr, s))
1135

1236

1337
class AbstractEngine(object):
@@ -89,9 +113,10 @@ def _evaluate(self):
89113

90114
try:
91115
env = self.expr.env
92-
full_scope = DeepChainMap(*(env.resolvers.maps + env.scope.maps))
93-
return ne.evaluate(s, local_dict=full_scope,
94-
truediv=env.scope['truediv'])
116+
scope = env.full_scope
117+
truediv = scope['truediv']
118+
_check_ne_builtin_clash(self.expr)
119+
return ne.evaluate(s, local_dict=scope, truediv=truediv)
95120
except KeyError as e:
96121
# python 3 compat kludge
97122
try:
@@ -101,6 +126,7 @@ def _evaluate(self):
101126
raise UndefinedVariableError(msg)
102127

103128

129+
104130
class PythonEngine(AbstractEngine):
105131

106132
"""Evaluate an expression in Python space.

pandas/computation/pytables.py

+19-21
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,24 @@
77
from datetime import datetime, timedelta
88
import numpy as np
99
import pandas as pd
10-
from pandas.compat import u, string_types, PY3
10+
from pandas.compat import u, string_types, PY3, DeepChainMap
1111
from pandas.core.base import StringMixin
1212
import pandas.core.common as com
1313
from pandas.computation import expr, ops
1414
from pandas.computation.ops import is_term
15+
from pandas.computation.scope import _ensure_scope
1516
from pandas.computation.expr import BaseExprVisitor
1617
from pandas.computation.common import _ensure_decoded
1718
from pandas.tseries.timedeltas import _coerce_scalar_to_timedelta_type
1819

1920

2021
class Scope(expr.Scope):
21-
__slots__ = 'globals', 'locals', 'queryables'
22-
23-
def __init__(self, gbls=None, lcls=None, queryables=None, level=1):
24-
super(
25-
Scope,
26-
self).__init__(gbls=gbls,
27-
lcls=lcls,
28-
level=level)
22+
__slots__ = 'queryables',
23+
24+
def __init__(self, level, global_dict=None, local_dict=None,
25+
queryables=None):
26+
super(Scope, self).__init__(level + 1, global_dict=global_dict,
27+
local_dict=local_dict)
2928
self.queryables = queryables or dict()
3029

3130

@@ -48,9 +47,8 @@ def _resolve_name(self):
4847
raise NameError('name {0!r} is not defined'.format(self.name))
4948
return self.name
5049

51-
# resolve the rhs (and allow to be None)
52-
return self.env.locals.get(self.name,
53-
self.env.globals.get(self.name, self.name))
50+
# resolve the rhs (and allow it to be None)
51+
return self.env.resolve(self.name, is_local=False)
5452

5553
@property
5654
def value(self):
@@ -478,7 +476,7 @@ class Expr(expr.Expr):
478476
"""
479477

480478
def __init__(self, where, op=None, value=None, queryables=None,
481-
encoding=None, scope_level=None):
479+
encoding=None, scope_level=0):
482480

483481
# try to be back compat
484482
where = self.parse_back_compat(where, op, value)
@@ -488,25 +486,25 @@ def __init__(self, where, op=None, value=None, queryables=None,
488486
self.filter = None
489487
self.terms = None
490488
self._visitor = None
491-
# capture the environement if needed
492-
lcls = dict()
493-
if isinstance(where, Expr):
494489

495-
lcls.update(where.env.locals)
490+
# capture the environment if needed
491+
local_dict = dict()
492+
493+
if isinstance(where, Expr):
494+
local_dict.update(where.env.scope)
496495
where = where.expr
497496

498497
elif isinstance(where, (list, tuple)):
499498
for idx, w in enumerate(where):
500499
if isinstance(w, Expr):
501-
lcls.update(w.env.locals)
500+
local_dict.update(w.env.scope)
502501
else:
503502
w = self.parse_back_compat(w)
504503
where[idx] = w
505504
where = ' & ' .join(["(%s)" % w for w in where])
506505

507506
self.expr = where
508-
self.env = Scope(lcls=lcls)
509-
self.env.update(scope_level)
507+
self.env = Scope(scope_level + 1, local_dict=local_dict)
510508

511509
if queryables is not None and isinstance(self.expr, string_types):
512510
self.env.queryables.update(queryables)
@@ -535,7 +533,7 @@ def parse_back_compat(self, w, op=None, value=None):
535533
warnings.warn("passing a tuple into Expr is deprecated, "
536534
"pass the where as a single string",
537535
DeprecationWarning)
538-
536+
539537
if op is not None:
540538
if not isinstance(w, string_types):
541539
raise TypeError(

0 commit comments

Comments
 (0)