From 9d984b7175b6e7ad1b703a826268516a93526fce Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Fri, 22 Nov 2019 20:39:04 -0800 Subject: [PATCH 1/3] DEPR: deprecate truediv param in pd.eval --- pandas/core/computation/engines.py | 19 +++++-------------- pandas/core/computation/eval.py | 15 +++++++++++++-- pandas/core/computation/expr.py | 18 ++++++++++++------ pandas/core/computation/ops.py | 8 +------- pandas/tests/computation/test_eval.py | 16 ++++++++++++++++ 5 files changed, 47 insertions(+), 29 deletions(-) diff --git a/pandas/core/computation/engines.py b/pandas/core/computation/engines.py index 2f3c519d352c6..a4eaa897ca01e 100644 --- a/pandas/core/computation/engines.py +++ b/pandas/core/computation/engines.py @@ -5,7 +5,7 @@ import abc from pandas.core.computation.align import align_terms, reconstruct_object -from pandas.core.computation.ops import UndefinedVariableError, _mathops, _reductions +from pandas.core.computation.ops import _mathops, _reductions import pandas.io.formats.printing as printing @@ -114,19 +114,10 @@ def _evaluate(self): # convert the expression to a valid numexpr expression s = self.convert() - try: - env = self.expr.env - scope = env.full_scope - truediv = scope["truediv"] - _check_ne_builtin_clash(self.expr) - return ne.evaluate(s, local_dict=scope, truediv=truediv) - except KeyError as e: - # python 3 compat kludge - try: - msg = e.message - except AttributeError: - msg = str(e) - raise UndefinedVariableError(msg) + env = self.expr.env + scope = env.full_scope + _check_ne_builtin_clash(self.expr) + return ne.evaluate(s, local_dict=scope) class PythonEngine(AbstractEngine): diff --git a/pandas/core/computation/eval.py b/pandas/core/computation/eval.py index 72f2e1d8e23e5..598680ca6c2de 100644 --- a/pandas/core/computation/eval.py +++ b/pandas/core/computation/eval.py @@ -7,6 +7,7 @@ import tokenize import warnings +from pandas._libs.lib import _no_default from pandas.util._validators import validate_bool_kwarg from pandas.core.computation.engines import _engines @@ -169,7 +170,7 @@ def eval( expr, parser="pandas", engine=None, - truediv=True, + truediv=_no_default, local_dict=None, global_dict=None, resolvers=(), @@ -219,6 +220,8 @@ def eval( truediv : bool, optional Whether to use true division, like in Python >= 3. + deprecated:: 1.0.0 + local_dict : dict or None, optional A dictionary of local variables, taken from locals() by default. global_dict : dict or None, optional @@ -284,6 +287,14 @@ def eval( inplace = validate_bool_kwarg(inplace, "inplace") + if truediv is not _no_default: + warnings.warn( + "The `truediv` parameter in pd.eval is deprecated and will be " + "removed in a future version.", + FutureWarning, + stacklevel=2, + ) + if isinstance(expr, str): _check_expression(expr) exprs = [e.strip() for e in expr.splitlines() if e.strip() != ""] @@ -317,7 +328,7 @@ def eval( target=target, ) - parsed_expr = Expr(expr, engine=engine, parser=parser, env=env, truediv=truediv) + parsed_expr = Expr(expr, engine=engine, parser=parser, env=env) # construct the engine and evaluate the parsed expression eng = _engines[engine] diff --git a/pandas/core/computation/expr.py b/pandas/core/computation/expr.py index 253d64d50d0cd..95785af8dc5ea 100644 --- a/pandas/core/computation/expr.py +++ b/pandas/core/computation/expr.py @@ -7,7 +7,7 @@ import itertools as it import operator import tokenize -from typing import Type +from typing import Optional, Type import numpy as np @@ -564,8 +564,7 @@ def visit_BinOp(self, node, **kwargs): return self._maybe_evaluate_binop(op, op_class, left, right) def visit_Div(self, node, **kwargs): - truediv = self.env.scope["truediv"] - return lambda lhs, rhs: Div(lhs, rhs, truediv) + return lambda lhs, rhs: Div(lhs, rhs) def visit_UnaryOp(self, node, **kwargs): op = self.visit(node.op) @@ -813,18 +812,25 @@ class Expr: engine : str, optional, default 'numexpr' parser : str, optional, default 'pandas' env : Scope, optional, default None - truediv : bool, optional, default True level : int, optional, default 2 """ + env: Scope + engine: str + parser: str + def __init__( - self, expr, engine="numexpr", parser="pandas", env=None, truediv=True, level=0 + self, + expr, + engine: str = "numexpr", + parser: str = "pandas", + env: Optional[Scope] = None, + level: int = 0, ): self.expr = expr self.env = env or Scope(level=level + 1) self.engine = engine self.parser = parser - self.env.scope["truediv"] = truediv self._visitor = _parsers[parser](self.env, self.engine, self.parser) self.terms = self.parse() diff --git a/pandas/core/computation/ops.py b/pandas/core/computation/ops.py index 41d7f96f5e96d..983382dce717a 100644 --- a/pandas/core/computation/ops.py +++ b/pandas/core/computation/ops.py @@ -391,9 +391,6 @@ def __call__(self, env): object The result of an evaluated expression. """ - # handle truediv - if self.op == "/" and env.scope["truediv"]: - self.func = operator.truediv # recurse over the left/right nodes left = self.lhs(env) @@ -505,12 +502,9 @@ class Div(BinOp): ---------- lhs, rhs : Term or Op The Terms or Ops in the ``/`` expression. - truediv : bool - Whether or not to use true division. With Python 3 this happens - regardless of the value of ``truediv``. """ - def __init__(self, lhs, rhs, truediv: bool, **kwargs): + def __init__(self, lhs, rhs, **kwargs): super().__init__("/", lhs, rhs, **kwargs) if not isnumeric(lhs.return_type) or not isnumeric(rhs.return_type): diff --git a/pandas/tests/computation/test_eval.py b/pandas/tests/computation/test_eval.py index 66e8e1bebfe98..c52c1fbcd8669 100644 --- a/pandas/tests/computation/test_eval.py +++ b/pandas/tests/computation/test_eval.py @@ -2006,6 +2006,22 @@ def test_inf(engine, parser): assert result == expected +def test_truediv_deprecated(engine, parser): + match = "The `truediv` parameter in pd.eval is deprecated" + + with tm.assert_produces_warning(FutureWarning) as m: + pd.eval("1+1", engine=engine, parser=parser, truediv=True) + + assert len(m) == 1 + assert match in str(m[0].message) + + with tm.assert_produces_warning(FutureWarning) as m: + pd.eval("1+1", engine=engine, parser=parser, truediv=False) + + assert len(m) == 1 + assert match in str(m[0].message) + + def test_negate_lt_eq_le(engine, parser): df = pd.DataFrame([[0, 10], [1, 20]], columns=["cat", "count"]) expected = df[~(df.cat > 0)] From d080caa28199afed186884dce98e1e99fafb21b6 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Sat, 23 Nov 2019 15:41:07 -0800 Subject: [PATCH 2/3] whatsnew --- doc/source/whatsnew/v1.0.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index 77eb0b9fd9914..f1087ac9a3feb 100644 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -339,7 +339,7 @@ Deprecations value in ``idx`` of ``idx_val`` and a new value of ``val``, ``idx.set_value(arr, idx_val, val)`` is equivalent to ``arr[idx.get_loc(idx_val)] = val``, which should be used instead (:issue:`28621`). - :func:`is_extension_type` is deprecated, :func:`is_extension_array_dtype` should be used instead (:issue:`29457`) - +- :func:`eval` keyword argument "truediv" is deprecated and will be removed in a future version (:issue:`29812`) .. _whatsnew_1000.prior_deprecations: From 1d04aad035937f63baded744ac80c099b4f98569 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Sat, 23 Nov 2019 16:58:10 -0800 Subject: [PATCH 3/3] add GH ref --- pandas/tests/computation/test_eval.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/tests/computation/test_eval.py b/pandas/tests/computation/test_eval.py index c52c1fbcd8669..1146b486a3eb4 100644 --- a/pandas/tests/computation/test_eval.py +++ b/pandas/tests/computation/test_eval.py @@ -2007,6 +2007,7 @@ def test_inf(engine, parser): def test_truediv_deprecated(engine, parser): + # GH#29182 match = "The `truediv` parameter in pd.eval is deprecated" with tm.assert_produces_warning(FutureWarning) as m: