Skip to content

Commit 34259fb

Browse files
Backport PR pandas-dev#47085 on branch 1.4.x (BUG: Eval scopes ignoring empty dictionaries (pandas-dev#47084)) (pandas-dev#47250)
Backport PR pandas-dev#47085: BUG: Eval scopes ignoring empty dictionaries (pandas-dev#47084) Co-authored-by: Alex-Blade <[email protected]>
1 parent 75a799c commit 34259fb

File tree

4 files changed

+21
-3
lines changed

4 files changed

+21
-3
lines changed

doc/source/whatsnew/v1.4.3.rst

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Fixed regressions
3131

3232
Bug fixes
3333
~~~~~~~~~
34+
- Bug in :meth:`pd.eval`, :meth:`DataFrame.eval` and :meth:`DataFrame.query` where passing empty ``local_dict`` or ``global_dict`` was treated as passing ``None`` (:issue:`47084`)
3435
- Most I/O methods do no longer suppress ``OSError`` and ``ValueError`` when closing file handles (:issue:`47136`)
3536
-
3637

pandas/core/computation/pytables.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -563,7 +563,7 @@ def __init__(
563563
self._visitor = None
564564

565565
# capture the environment if needed
566-
local_dict: DeepChainMap[Any, Any] = DeepChainMap()
566+
local_dict: DeepChainMap[Any, Any] | None = None
567567

568568
if isinstance(where, PyTablesExpr):
569569
local_dict = where.env.scope

pandas/core/computation/scope.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,13 @@ def __init__(
133133
# shallow copy here because we don't want to replace what's in
134134
# scope when we align terms (alignment accesses the underlying
135135
# numpy array of pandas objects)
136-
scope_global = self.scope.new_child((global_dict or frame.f_globals).copy())
136+
scope_global = self.scope.new_child(
137+
(global_dict if global_dict is not None else frame.f_globals).copy()
138+
)
137139
self.scope = DeepChainMap(scope_global)
138140
if not isinstance(local_dict, Scope):
139141
scope_local = self.scope.new_child(
140-
(local_dict or frame.f_locals).copy()
142+
(local_dict if local_dict is not None else frame.f_locals).copy()
141143
)
142144
self.scope = DeepChainMap(scope_local)
143145
finally:

pandas/tests/computation/test_eval.py

+15
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
from pandas.core.computation.ops import (
4444
ARITH_OPS_SYMS,
4545
SPECIAL_CASE_ARITH_OPS_SYMS,
46+
UndefinedVariableError,
4647
_binary_math_ops,
4748
_binary_ops_dict,
4849
_unary_math_ops,
@@ -1747,6 +1748,20 @@ def test_no_new_globals(self, engine, parser):
17471748
gbls2 = globals().copy()
17481749
assert gbls == gbls2
17491750

1751+
def test_empty_locals(self, engine, parser):
1752+
# GH 47084
1753+
x = 1 # noqa: F841
1754+
msg = "name 'x' is not defined"
1755+
with pytest.raises(UndefinedVariableError, match=msg):
1756+
pd.eval("x + 1", engine=engine, parser=parser, local_dict={})
1757+
1758+
def test_empty_globals(self, engine, parser):
1759+
# GH 47084
1760+
msg = "name '_var_s' is not defined"
1761+
e = "_var_s * 2"
1762+
with pytest.raises(UndefinedVariableError, match=msg):
1763+
pd.eval(e, engine=engine, parser=parser, global_dict={})
1764+
17501765

17511766
@td.skip_if_no_ne
17521767
def test_invalid_engine():

0 commit comments

Comments
 (0)