diff --git a/pandas/tests/computation/test_eval.py b/pandas/tests/computation/test_eval.py index c0117d9b162ad..b59e74eda0553 100644 --- a/pandas/tests/computation/test_eval.py +++ b/pandas/tests/computation/test_eval.py @@ -119,16 +119,6 @@ def lhs(request): class TestEval: - @pytest.fixture(autouse=True) - def set_engine_parser_attrs(self, engine, parser): - # Older tests look for these as attributes, so we set them here. - self.engine = engine - self.parser = parser - - @property - def current_engines(self): - return (engine for engine in ENGINES if engine != self.engine) - @pytest.mark.parametrize( "cmp1", ["!=", "==", "<=", ">=", "<", ">"], @@ -136,33 +126,33 @@ def current_engines(self): ) @pytest.mark.parametrize("cmp2", [">", "<"], ids=["gt", "lt"]) @pytest.mark.parametrize("binop", expr.BOOL_OPS_SYMS) - def test_complex_cmp_ops(self, cmp1, cmp2, binop, lhs, rhs): - if self.parser == "python" and binop in ["and", "or"]: + def test_complex_cmp_ops(self, cmp1, cmp2, binop, lhs, rhs, engine, parser): + if parser == "python" and binop in ["and", "or"]: msg = "'BoolOp' nodes are not implemented" with pytest.raises(NotImplementedError, match=msg): ex = f"(lhs {cmp1} rhs) {binop} (lhs {cmp2} rhs)" - pd.eval(ex, engine=self.engine, parser=self.parser) + pd.eval(ex, engine=engine, parser=parser) return - lhs_new = _eval_single_bin(lhs, cmp1, rhs, self.engine) - rhs_new = _eval_single_bin(lhs, cmp2, rhs, self.engine) - expected = _eval_single_bin(lhs_new, binop, rhs_new, self.engine) + lhs_new = _eval_single_bin(lhs, cmp1, rhs, engine) + rhs_new = _eval_single_bin(lhs, cmp2, rhs, engine) + expected = _eval_single_bin(lhs_new, binop, rhs_new, engine) ex = f"(lhs {cmp1} rhs) {binop} (lhs {cmp2} rhs)" - result = pd.eval(ex, engine=self.engine, parser=self.parser) + result = pd.eval(ex, engine=engine, parser=parser) tm.assert_equal(result, expected) @pytest.mark.parametrize("cmp_op", expr.CMP_OPS_SYMS) - def test_simple_cmp_ops(self, cmp_op, lhs, rhs): + def test_simple_cmp_ops(self, cmp_op, lhs, rhs, engine, parser): lhs = lhs < 0 rhs = rhs < 0 - if self.parser == "python" and cmp_op in ["in", "not in"]: + if parser == "python" and cmp_op in ["in", "not in"]: msg = "'(In|NotIn)' nodes are not implemented" with pytest.raises(NotImplementedError, match=msg): ex = f"lhs {cmp_op} rhs" - pd.eval(ex, engine=self.engine, parser=self.parser) + pd.eval(ex, engine=engine, parser=parser) return ex = f"lhs {cmp_op} rhs" @@ -178,31 +168,31 @@ def test_simple_cmp_ops(self, cmp_op, lhs, rhs): with pytest.raises(TypeError, match=msg): pd.eval( ex, - engine=self.engine, - parser=self.parser, + engine=engine, + parser=parser, local_dict={"lhs": lhs, "rhs": rhs}, ) else: - expected = _eval_single_bin(lhs, cmp_op, rhs, self.engine) - result = pd.eval(ex, engine=self.engine, parser=self.parser) + expected = _eval_single_bin(lhs, cmp_op, rhs, engine) + result = pd.eval(ex, engine=engine, parser=parser) tm.assert_equal(result, expected) @pytest.mark.parametrize("op", expr.CMP_OPS_SYMS) - def test_compound_invert_op(self, op, lhs, rhs, request): - if self.parser == "python" and op in ["in", "not in"]: + def test_compound_invert_op(self, op, lhs, rhs, request, engine, parser): + if parser == "python" and op in ["in", "not in"]: msg = "'(In|NotIn)' nodes are not implemented" with pytest.raises(NotImplementedError, match=msg): ex = f"~(lhs {op} rhs)" - pd.eval(ex, engine=self.engine, parser=self.parser) + pd.eval(ex, engine=engine, parser=parser) return if ( is_float(lhs) and not is_float(rhs) and op in ["in", "not in"] - and self.engine == "python" - and self.parser == "pandas" + and engine == "python" + and parser == "pandas" ): mark = pytest.mark.xfail( reason="Looks like expected is negative, unclear whether " @@ -224,64 +214,59 @@ def test_compound_invert_op(self, op, lhs, rhs, request): with pytest.raises(TypeError, match=msg): pd.eval( ex, - engine=self.engine, - parser=self.parser, + engine=engine, + parser=parser, local_dict={"lhs": lhs, "rhs": rhs}, ) else: # compound if is_scalar(lhs) and is_scalar(rhs): lhs, rhs = map(lambda x: np.array([x]), (lhs, rhs)) - expected = _eval_single_bin(lhs, op, rhs, self.engine) + expected = _eval_single_bin(lhs, op, rhs, engine) if is_scalar(expected): expected = not expected else: expected = ~expected - result = pd.eval(ex, engine=self.engine, parser=self.parser) + result = pd.eval(ex, engine=engine, parser=parser) tm.assert_almost_equal(expected, result) - # make sure the other engines work the same as this one - for engine in self.current_engines: - ev = pd.eval(ex, engine=self.engine, parser=self.parser) - tm.assert_almost_equal(ev, result) - @pytest.mark.parametrize("cmp1", ["<", ">"]) @pytest.mark.parametrize("cmp2", ["<", ">"]) - def test_chained_cmp_op(self, cmp1, cmp2, lhs, midhs, rhs): + def test_chained_cmp_op(self, cmp1, cmp2, lhs, midhs, rhs, engine, parser): mid = midhs - if self.parser == "python": + if parser == "python": ex1 = f"lhs {cmp1} mid {cmp2} rhs" msg = "'BoolOp' nodes are not implemented" with pytest.raises(NotImplementedError, match=msg): - pd.eval(ex1, engine=self.engine, parser=self.parser) + pd.eval(ex1, engine=engine, parser=parser) return - lhs_new = _eval_single_bin(lhs, cmp1, mid, self.engine) - rhs_new = _eval_single_bin(mid, cmp2, rhs, self.engine) + lhs_new = _eval_single_bin(lhs, cmp1, mid, engine) + rhs_new = _eval_single_bin(mid, cmp2, rhs, engine) if lhs_new is not None and rhs_new is not None: ex1 = f"lhs {cmp1} mid {cmp2} rhs" ex2 = f"lhs {cmp1} mid and mid {cmp2} rhs" ex3 = f"(lhs {cmp1} mid) & (mid {cmp2} rhs)" - expected = _eval_single_bin(lhs_new, "&", rhs_new, self.engine) + expected = _eval_single_bin(lhs_new, "&", rhs_new, engine) for ex in (ex1, ex2, ex3): - result = pd.eval(ex, engine=self.engine, parser=self.parser) + result = pd.eval(ex, engine=engine, parser=parser) tm.assert_almost_equal(result, expected) @pytest.mark.parametrize( "arith1", sorted(set(ARITH_OPS_SYMS).difference(SPECIAL_CASE_ARITH_OPS_SYMS)) ) - def test_binary_arith_ops(self, arith1, lhs, rhs): + def test_binary_arith_ops(self, arith1, lhs, rhs, engine, parser): ex = f"lhs {arith1} rhs" - result = pd.eval(ex, engine=self.engine, parser=self.parser) - expected = _eval_single_bin(lhs, arith1, rhs, self.engine) + result = pd.eval(ex, engine=engine, parser=parser) + expected = _eval_single_bin(lhs, arith1, rhs, engine) tm.assert_almost_equal(result, expected) ex = f"lhs {arith1} rhs {arith1} rhs" - result = pd.eval(ex, engine=self.engine, parser=self.parser) - nlhs = _eval_single_bin(lhs, arith1, rhs, self.engine) + result = pd.eval(ex, engine=engine, parser=parser) + nlhs = _eval_single_bin(lhs, arith1, rhs, engine) try: nlhs, ghs = nlhs.align(rhs) except (ValueError, TypeError, AttributeError): @@ -289,7 +274,7 @@ def test_binary_arith_ops(self, arith1, lhs, rhs): # TypeError, AttributeError: series or frame with scalar align return else: - if self.engine == "numexpr": + if engine == "numexpr": import numexpr as ne # direct numpy comparison @@ -305,13 +290,13 @@ def test_binary_arith_ops(self, arith1, lhs, rhs): # modulus, pow, and floor division require special casing - def test_modulus(self, lhs, rhs): + def test_modulus(self, lhs, rhs, engine, parser): ex = r"lhs % rhs" - result = pd.eval(ex, engine=self.engine, parser=self.parser) + result = pd.eval(ex, engine=engine, parser=parser) expected = lhs % rhs tm.assert_almost_equal(result, expected) - if self.engine == "numexpr": + if engine == "numexpr": import numexpr as ne expected = ne.evaluate(r"expected % rhs") @@ -320,14 +305,14 @@ def test_modulus(self, lhs, rhs): else: tm.assert_almost_equal(result, expected.item()) else: - expected = _eval_single_bin(expected, "%", rhs, self.engine) + expected = _eval_single_bin(expected, "%", rhs, engine) tm.assert_almost_equal(result, expected) - def test_floor_division(self, lhs, rhs): + def test_floor_division(self, lhs, rhs, engine, parser): ex = "lhs // rhs" - if self.engine == "python": - res = pd.eval(ex, engine=self.engine, parser=self.parser) + if engine == "python": + res = pd.eval(ex, engine=engine, parser=parser) expected = lhs // rhs tm.assert_equal(res, expected) else: @@ -339,16 +324,16 @@ def test_floor_division(self, lhs, rhs): pd.eval( ex, local_dict={"lhs": lhs, "rhs": rhs}, - engine=self.engine, - parser=self.parser, + engine=engine, + parser=parser, ) @td.skip_if_windows - def test_pow(self, lhs, rhs): + def test_pow(self, lhs, rhs, engine, parser): # odd failure on win32 platform, so skip ex = "lhs ** rhs" - expected = _eval_single_bin(lhs, "**", rhs, self.engine) - result = pd.eval(ex, engine=self.engine, parser=self.parser) + expected = _eval_single_bin(lhs, "**", rhs, engine) + result = pd.eval(ex, engine=engine, parser=parser) if ( is_scalar(lhs) @@ -363,101 +348,96 @@ def test_pow(self, lhs, rhs): tm.assert_almost_equal(result, expected) ex = "(lhs ** rhs) ** rhs" - result = pd.eval(ex, engine=self.engine, parser=self.parser) + result = pd.eval(ex, engine=engine, parser=parser) - middle = _eval_single_bin(lhs, "**", rhs, self.engine) - expected = _eval_single_bin(middle, "**", rhs, self.engine) + middle = _eval_single_bin(lhs, "**", rhs, engine) + expected = _eval_single_bin(middle, "**", rhs, engine) tm.assert_almost_equal(result, expected) - def check_single_invert_op(self, lhs): + def check_single_invert_op(self, lhs, engine, parser): # simple try: elb = lhs.astype(bool) except AttributeError: elb = np.array([bool(lhs)]) expected = ~elb - result = pd.eval("~elb", engine=self.engine, parser=self.parser) + result = pd.eval("~elb", engine=engine, parser=parser) tm.assert_almost_equal(expected, result) - for engine in self.current_engines: - tm.assert_almost_equal( - result, pd.eval("~elb", engine=engine, parser=self.parser) - ) - - def test_frame_invert(self): + def test_frame_invert(self, engine, parser): expr = "~lhs" # ~ ## # frame # float always raises lhs = DataFrame(np.random.randn(5, 2)) - if self.engine == "numexpr": + if engine == "numexpr": msg = "couldn't find matching opcode for 'invert_dd'" with pytest.raises(NotImplementedError, match=msg): - pd.eval(expr, engine=self.engine, parser=self.parser) + pd.eval(expr, engine=engine, parser=parser) else: msg = "ufunc 'invert' not supported for the input types" with pytest.raises(TypeError, match=msg): - pd.eval(expr, engine=self.engine, parser=self.parser) + pd.eval(expr, engine=engine, parser=parser) # int raises on numexpr lhs = DataFrame(np.random.randint(5, size=(5, 2))) - if self.engine == "numexpr": + if engine == "numexpr": msg = "couldn't find matching opcode for 'invert" with pytest.raises(NotImplementedError, match=msg): - pd.eval(expr, engine=self.engine, parser=self.parser) + pd.eval(expr, engine=engine, parser=parser) else: expect = ~lhs - result = pd.eval(expr, engine=self.engine, parser=self.parser) + result = pd.eval(expr, engine=engine, parser=parser) tm.assert_frame_equal(expect, result) # bool always works lhs = DataFrame(np.random.rand(5, 2) > 0.5) expect = ~lhs - result = pd.eval(expr, engine=self.engine, parser=self.parser) + result = pd.eval(expr, engine=engine, parser=parser) tm.assert_frame_equal(expect, result) # object raises lhs = DataFrame({"b": ["a", 1, 2.0], "c": np.random.rand(3) > 0.5}) - if self.engine == "numexpr": + if engine == "numexpr": with pytest.raises(ValueError, match="unknown type object"): - pd.eval(expr, engine=self.engine, parser=self.parser) + pd.eval(expr, engine=engine, parser=parser) else: msg = "bad operand type for unary ~: 'str'" with pytest.raises(TypeError, match=msg): - pd.eval(expr, engine=self.engine, parser=self.parser) + pd.eval(expr, engine=engine, parser=parser) - def test_series_invert(self): + def test_series_invert(self, engine, parser): # ~ #### expr = "~lhs" # series # float raises lhs = Series(np.random.randn(5)) - if self.engine == "numexpr": + if engine == "numexpr": msg = "couldn't find matching opcode for 'invert_dd'" with pytest.raises(NotImplementedError, match=msg): - result = pd.eval(expr, engine=self.engine, parser=self.parser) + result = pd.eval(expr, engine=engine, parser=parser) else: msg = "ufunc 'invert' not supported for the input types" with pytest.raises(TypeError, match=msg): - result = pd.eval(expr, engine=self.engine, parser=self.parser) + pd.eval(expr, engine=engine, parser=parser) # int raises on numexpr lhs = Series(np.random.randint(5, size=5)) - if self.engine == "numexpr": + if engine == "numexpr": msg = "couldn't find matching opcode for 'invert" with pytest.raises(NotImplementedError, match=msg): - result = pd.eval(expr, engine=self.engine, parser=self.parser) + pd.eval(expr, engine=engine, parser=parser) else: expect = ~lhs - result = pd.eval(expr, engine=self.engine, parser=self.parser) + result = pd.eval(expr, engine=engine, parser=parser) tm.assert_series_equal(expect, result) # bool lhs = Series(np.random.rand(5) > 0.5) expect = ~lhs - result = pd.eval(expr, engine=self.engine, parser=self.parser) + result = pd.eval(expr, engine=engine, parser=parser) tm.assert_series_equal(expect, result) # float @@ -466,64 +446,64 @@ def test_series_invert(self): # object lhs = Series(["a", 1, 2.0]) - if self.engine == "numexpr": + if engine == "numexpr": with pytest.raises(ValueError, match="unknown type object"): - pd.eval(expr, engine=self.engine, parser=self.parser) + pd.eval(expr, engine=engine, parser=parser) else: msg = "bad operand type for unary ~: 'str'" with pytest.raises(TypeError, match=msg): - pd.eval(expr, engine=self.engine, parser=self.parser) + pd.eval(expr, engine=engine, parser=parser) - def test_frame_negate(self): + def test_frame_negate(self, engine, parser): expr = "-lhs" # float lhs = DataFrame(np.random.randn(5, 2)) expect = -lhs - result = pd.eval(expr, engine=self.engine, parser=self.parser) + result = pd.eval(expr, engine=engine, parser=parser) tm.assert_frame_equal(expect, result) # int lhs = DataFrame(np.random.randint(5, size=(5, 2))) expect = -lhs - result = pd.eval(expr, engine=self.engine, parser=self.parser) + result = pd.eval(expr, engine=engine, parser=parser) tm.assert_frame_equal(expect, result) # bool doesn't work with numexpr but works elsewhere lhs = DataFrame(np.random.rand(5, 2) > 0.5) - if self.engine == "numexpr": + if engine == "numexpr": msg = "couldn't find matching opcode for 'neg_bb'" with pytest.raises(NotImplementedError, match=msg): - pd.eval(expr, engine=self.engine, parser=self.parser) + pd.eval(expr, engine=engine, parser=parser) else: expect = -lhs - result = pd.eval(expr, engine=self.engine, parser=self.parser) + result = pd.eval(expr, engine=engine, parser=parser) tm.assert_frame_equal(expect, result) - def test_series_negate(self): + def test_series_negate(self, engine, parser): expr = "-lhs" # float lhs = Series(np.random.randn(5)) expect = -lhs - result = pd.eval(expr, engine=self.engine, parser=self.parser) + result = pd.eval(expr, engine=engine, parser=parser) tm.assert_series_equal(expect, result) # int lhs = Series(np.random.randint(5, size=5)) expect = -lhs - result = pd.eval(expr, engine=self.engine, parser=self.parser) + result = pd.eval(expr, engine=engine, parser=parser) tm.assert_series_equal(expect, result) # bool doesn't work with numexpr but works elsewhere lhs = Series(np.random.rand(5) > 0.5) - if self.engine == "numexpr": + if engine == "numexpr": msg = "couldn't find matching opcode for 'neg_bb'" with pytest.raises(NotImplementedError, match=msg): - pd.eval(expr, engine=self.engine, parser=self.parser) + pd.eval(expr, engine=engine, parser=parser) else: expect = -lhs - result = pd.eval(expr, engine=self.engine, parser=self.parser) + result = pd.eval(expr, engine=engine, parser=parser) tm.assert_series_equal(expect, result) @pytest.mark.parametrize( @@ -537,11 +517,11 @@ def test_series_negate(self): DataFrame(np.random.rand(5, 2) > 0.5), ], ) - def test_frame_pos(self, lhs): + def test_frame_pos(self, lhs, engine, parser): expr = "+lhs" expect = lhs - result = pd.eval(expr, engine=self.engine, parser=self.parser) + result = pd.eval(expr, engine=engine, parser=parser) tm.assert_frame_equal(expect, result) @pytest.mark.parametrize( @@ -555,29 +535,29 @@ def test_frame_pos(self, lhs): Series(np.random.rand(5) > 0.5), ], ) - def test_series_pos(self, lhs): + def test_series_pos(self, lhs, engine, parser): expr = "+lhs" expect = lhs - result = pd.eval(expr, engine=self.engine, parser=self.parser) + result = pd.eval(expr, engine=engine, parser=parser) tm.assert_series_equal(expect, result) - def test_scalar_unary(self): + def test_scalar_unary(self, engine, parser): msg = "bad operand type for unary ~: 'float'" with pytest.raises(TypeError, match=msg): - pd.eval("~1.0", engine=self.engine, parser=self.parser) - - assert pd.eval("-1.0", parser=self.parser, engine=self.engine) == -1.0 - assert pd.eval("+1.0", parser=self.parser, engine=self.engine) == +1.0 - assert pd.eval("~1", parser=self.parser, engine=self.engine) == ~1 - assert pd.eval("-1", parser=self.parser, engine=self.engine) == -1 - assert pd.eval("+1", parser=self.parser, engine=self.engine) == +1 - assert pd.eval("~True", parser=self.parser, engine=self.engine) == ~True - assert pd.eval("~False", parser=self.parser, engine=self.engine) == ~False - assert pd.eval("-True", parser=self.parser, engine=self.engine) == -True - assert pd.eval("-False", parser=self.parser, engine=self.engine) == -False - assert pd.eval("+True", parser=self.parser, engine=self.engine) == +True - assert pd.eval("+False", parser=self.parser, engine=self.engine) == +False + pd.eval("~1.0", engine=engine, parser=parser) + + assert pd.eval("-1.0", parser=parser, engine=engine) == -1.0 + assert pd.eval("+1.0", parser=parser, engine=engine) == +1.0 + assert pd.eval("~1", parser=parser, engine=engine) == ~1 + assert pd.eval("-1", parser=parser, engine=engine) == -1 + assert pd.eval("+1", parser=parser, engine=engine) == +1 + assert pd.eval("~True", parser=parser, engine=engine) == ~True + assert pd.eval("~False", parser=parser, engine=engine) == ~False + assert pd.eval("-True", parser=parser, engine=engine) == -True + assert pd.eval("-False", parser=parser, engine=engine) == -False + assert pd.eval("+True", parser=parser, engine=engine) == +True + assert pd.eval("+False", parser=parser, engine=engine) == +False def test_unary_in_array(self): # GH 11235 @@ -630,58 +610,58 @@ def test_float_comparison_bin_op(self, dtype, expr): "2 * df > 3 and 1 or a", ), ) - def test_disallow_scalar_bool_ops(self, ex): + def test_disallow_scalar_bool_ops(self, ex, engine, parser): x, a, b = np.random.randn(3), 1, 2 # noqa:F841 df = DataFrame(np.random.randn(3, 2)) # noqa:F841 msg = "cannot evaluate scalar only bool ops|'BoolOp' nodes are not" with pytest.raises(NotImplementedError, match=msg): - pd.eval(ex, engine=self.engine, parser=self.parser) + pd.eval(ex, engine=engine, parser=parser) - def test_identical(self): + def test_identical(self, engine, parser): # see gh-10546 x = 1 - result = pd.eval("x", engine=self.engine, parser=self.parser) + result = pd.eval("x", engine=engine, parser=parser) assert result == 1 assert is_scalar(result) x = 1.5 - result = pd.eval("x", engine=self.engine, parser=self.parser) + result = pd.eval("x", engine=engine, parser=parser) assert result == 1.5 assert is_scalar(result) x = False - result = pd.eval("x", engine=self.engine, parser=self.parser) + result = pd.eval("x", engine=engine, parser=parser) assert not result assert is_bool(result) assert is_scalar(result) x = np.array([1]) - result = pd.eval("x", engine=self.engine, parser=self.parser) + result = pd.eval("x", engine=engine, parser=parser) tm.assert_numpy_array_equal(result, np.array([1])) assert result.shape == (1,) x = np.array([1.5]) - result = pd.eval("x", engine=self.engine, parser=self.parser) + result = pd.eval("x", engine=engine, parser=parser) tm.assert_numpy_array_equal(result, np.array([1.5])) assert result.shape == (1,) x = np.array([False]) # noqa:F841 - result = pd.eval("x", engine=self.engine, parser=self.parser) + result = pd.eval("x", engine=engine, parser=parser) tm.assert_numpy_array_equal(result, np.array([False])) assert result.shape == (1,) - def test_line_continuation(self): + def test_line_continuation(self, engine, parser): # GH 11149 exp = """1 + 2 * \ 5 - 1 + 2 """ - result = pd.eval(exp, engine=self.engine, parser=self.parser) + result = pd.eval(exp, engine=engine, parser=parser) assert result == 12 - def test_float_truncation(self): + def test_float_truncation(self, engine, parser): # GH 14241 exp = "1000000000.006" - result = pd.eval(exp, engine=self.engine, parser=self.parser) + result = pd.eval(exp, engine=engine, parser=parser) expected = np.float64(exp) assert result == expected @@ -1002,21 +982,13 @@ def test_performance_warning_for_poor_alignment(self, engine, parser): class TestOperations: - @pytest.fixture(autouse=True) - def set_engine_parser_attrs(self, engine, parser): - # Older tests look for these as attributes, so we set them here. - self.engine = engine - self.parser = parser - def eval(self, *args, **kwargs): - kwargs["engine"] = self.engine - kwargs["parser"] = self.parser kwargs["level"] = kwargs.pop("level", 0) + 1 return pd.eval(*args, **kwargs) - def test_simple_arith_ops(self): + def test_simple_arith_ops(self, engine, parser): exclude_arith = [] - if self.parser == "python": + if parser == "python": exclude_arith = ["in", "not in"] arith_ops = [ @@ -1035,22 +1007,18 @@ def test_simple_arith_ops(self): if op in ("in", "not in"): msg = "argument of type 'int' is not iterable" with pytest.raises(TypeError, match=msg): - pd.eval(ex, engine=self.engine, parser=self.parser) + pd.eval(ex, engine=engine, parser=parser) else: - expec = _eval_single_bin(1, op, 1, self.engine) - x = self.eval(ex, engine=self.engine, parser=self.parser) + expec = _eval_single_bin(1, op, 1, engine) + x = self.eval(ex, engine=engine, parser=parser) assert x == expec - expec = _eval_single_bin(x, op, 1, self.engine) - y = self.eval( - ex2, local_dict={"x": x}, engine=self.engine, parser=self.parser - ) + expec = _eval_single_bin(x, op, 1, engine) + y = self.eval(ex2, local_dict={"x": x}, engine=engine, parser=parser) assert y == expec - expec = _eval_single_bin(1, op, x + 1, self.engine) - y = self.eval( - ex3, local_dict={"x": x}, engine=self.engine, parser=self.parser - ) + expec = _eval_single_bin(1, op, x + 1, engine) + y = self.eval(ex3, local_dict={"x": x}, engine=engine, parser=parser) assert y == expec @pytest.mark.parametrize("rhs", [True, False]) @@ -1059,7 +1027,7 @@ def test_simple_arith_ops(self): def test_simple_bool_ops(self, rhs, lhs, op): ex = f"{lhs} {op} {rhs}" - if self.parser == "python" and op in ["and", "or"]: + if parser == "python" and op in ["and", "or"]: msg = "'BoolOp' nodes are not implemented" with pytest.raises(NotImplementedError, match=msg): self.eval(ex) @@ -1075,7 +1043,7 @@ def test_simple_bool_ops(self, rhs, lhs, op): def test_bool_ops_with_constants(self, rhs, lhs, op): ex = f"{lhs} {op} {rhs}" - if self.parser == "python" and op in ["and", "or"]: + if parser == "python" and op in ["and", "or"]: msg = "'BoolOp' nodes are not implemented" with pytest.raises(NotImplementedError, match=msg): self.eval(ex) @@ -1455,78 +1423,70 @@ def test_nested_period_index_subscript_expression(self): e = df[df[df < 2] < 2] + df * 2 tm.assert_frame_equal(r, e) - def test_date_boolean(self): + def test_date_boolean(self, engine, parser): df = DataFrame(np.random.randn(5, 3)) df["dates1"] = date_range("1/1/2012", periods=5) res = self.eval( "df.dates1 < 20130101", local_dict={"df": df}, - engine=self.engine, - parser=self.parser, + engine=engine, + parser=parser, ) expec = df.dates1 < "20130101" tm.assert_series_equal(res, expec, check_names=False) - def test_simple_in_ops(self): - if self.parser != "python": - res = pd.eval("1 in [1, 2]", engine=self.engine, parser=self.parser) + def test_simple_in_ops(self, engine, parser): + if parser != "python": + res = pd.eval("1 in [1, 2]", engine=engine, parser=parser) assert res - res = pd.eval("2 in (1, 2)", engine=self.engine, parser=self.parser) + res = pd.eval("2 in (1, 2)", engine=engine, parser=parser) assert res - res = pd.eval("3 in (1, 2)", engine=self.engine, parser=self.parser) + res = pd.eval("3 in (1, 2)", engine=engine, parser=parser) assert not res - res = pd.eval("3 not in (1, 2)", engine=self.engine, parser=self.parser) + res = pd.eval("3 not in (1, 2)", engine=engine, parser=parser) assert res - res = pd.eval("[3] not in (1, 2)", engine=self.engine, parser=self.parser) + res = pd.eval("[3] not in (1, 2)", engine=engine, parser=parser) assert res - res = pd.eval("[3] in ([3], 2)", engine=self.engine, parser=self.parser) + res = pd.eval("[3] in ([3], 2)", engine=engine, parser=parser) assert res - res = pd.eval("[[3]] in [[[3]], 2]", engine=self.engine, parser=self.parser) + res = pd.eval("[[3]] in [[[3]], 2]", engine=engine, parser=parser) assert res - res = pd.eval("(3,) in [(3,), 2]", engine=self.engine, parser=self.parser) + res = pd.eval("(3,) in [(3,), 2]", engine=engine, parser=parser) assert res - res = pd.eval( - "(3,) not in [(3,), 2]", engine=self.engine, parser=self.parser - ) + res = pd.eval("(3,) not in [(3,), 2]", engine=engine, parser=parser) assert not res - res = pd.eval( - "[(3,)] in [[(3,)], 2]", engine=self.engine, parser=self.parser - ) + res = pd.eval("[(3,)] in [[(3,)], 2]", engine=engine, parser=parser) assert res else: msg = "'In' nodes are not implemented" with pytest.raises(NotImplementedError, match=msg): - pd.eval("1 in [1, 2]", engine=self.engine, parser=self.parser) + pd.eval("1 in [1, 2]", engine=engine, parser=parser) with pytest.raises(NotImplementedError, match=msg): - pd.eval("2 in (1, 2)", engine=self.engine, parser=self.parser) + pd.eval("2 in (1, 2)", engine=engine, parser=parser) with pytest.raises(NotImplementedError, match=msg): - pd.eval("3 in (1, 2)", engine=self.engine, parser=self.parser) + pd.eval("3 in (1, 2)", engine=engine, parser=parser) with pytest.raises(NotImplementedError, match=msg): - pd.eval( - "[(3,)] in (1, 2, [(3,)])", engine=self.engine, parser=self.parser - ) + pd.eval("[(3,)] in (1, 2, [(3,)])", engine=engine, parser=parser) msg = "'NotIn' nodes are not implemented" with pytest.raises(NotImplementedError, match=msg): - pd.eval("3 not in (1, 2)", engine=self.engine, parser=self.parser) + pd.eval("3 not in (1, 2)", engine=engine, parser=parser) with pytest.raises(NotImplementedError, match=msg): - pd.eval( - "[3] not in (1, 2, [[3]])", engine=self.engine, parser=self.parser - ) + pd.eval("[3] not in (1, 2, [[3]])", engine=engine, parser=parser) - def test_check_many_exprs(self): + def test_check_many_exprs(self, engine, parser): a = 1 # noqa:F841 expr = " * ".join("a" * 33) expected = 1 - res = pd.eval(expr, engine=self.engine, parser=self.parser) + res = pd.eval(expr, engine=engine, parser=parser) assert res == expected @pytest.mark.parametrize( @@ -1537,9 +1497,9 @@ def test_check_many_exprs(self): "not df > 2", ], ) - def test_fails_and_or_not(self, expr): + def test_fails_and_or_not(self, expr, engine, parser): df = DataFrame(np.random.randn(5, 3)) - if self.parser == "python": + if parser == "python": msg = "'BoolOp' nodes are not implemented" if "not" in expr: msg = "'Not' nodes are not implemented" @@ -1548,41 +1508,33 @@ def test_fails_and_or_not(self, expr): pd.eval( expr, local_dict={"df": df}, - parser=self.parser, - engine=self.engine, + parser=parser, + engine=engine, ) else: # smoke-test, should not raise pd.eval( expr, local_dict={"df": df}, - parser=self.parser, - engine=self.engine, + parser=parser, + engine=engine, ) @pytest.mark.parametrize("char", ["|", "&"]) - def test_fails_ampersand_pipe(self, char): + def test_fails_ampersand_pipe(self, char, engine, parser): df = DataFrame(np.random.randn(5, 3)) # noqa:F841 ex = f"(df + 2)[df > 1] > 0 {char} (df > 0)" - if self.parser == "python": + if parser == "python": msg = "cannot evaluate scalar only bool ops" with pytest.raises(NotImplementedError, match=msg): - pd.eval(ex, parser=self.parser, engine=self.engine) + pd.eval(ex, parser=parser, engine=engine) else: # smoke-test, should not raise - pd.eval(ex, parser=self.parser, engine=self.engine) + pd.eval(ex, parser=parser, engine=engine) class TestMath: - @pytest.fixture(autouse=True) - def set_engine_parser_attrs(self, engine, parser): - # Older tests look for these as attributes, so we set them here. - self.engine = engine - self.parser = parser - def eval(self, *args, **kwargs): - kwargs["engine"] = self.engine - kwargs["parser"] = self.parser kwargs["level"] = kwargs.pop("level", 0) + 1 return pd.eval(*args, **kwargs) @@ -1610,21 +1562,21 @@ def test_binary_functions(self, fn): expect = getattr(np, fn)(a, b) tm.assert_almost_equal(got, expect, check_names=False) - def test_df_use_case(self): + def test_df_use_case(self, engine, parser): df = DataFrame({"a": np.random.randn(10), "b": np.random.randn(10)}) df.eval( "e = arctan2(sin(a), b)", - engine=self.engine, - parser=self.parser, + engine=engine, + parser=parser, inplace=True, ) got = df.e expect = np.arctan2(np.sin(df.a), df.b) tm.assert_series_equal(got, expect, check_names=False) - def test_df_arithmetic_subexpression(self): + def test_df_arithmetic_subexpression(self, engine, parser): df = DataFrame({"a": np.random.randn(10), "b": np.random.randn(10)}) - df.eval("e = sin(a + b)", engine=self.engine, parser=self.parser, inplace=True) + df.eval("e = sin(a + b)", engine=engine, parser=parser, inplace=True) got = df.e expect = np.sin(df.a + df.b) tm.assert_series_equal(got, expect, check_names=False) @@ -1639,7 +1591,7 @@ def test_df_arithmetic_subexpression(self): pytest.param(np.complex128, np.complex128, marks=td.skip_if_windows), ], ) - def test_result_types(self, dtype, expect_dtype): + def test_result_types(self, dtype, expect_dtype, engine, parser): # xref https://github.com/pandas-dev/pandas/issues/12293 # this fails on Windows, apparently a floating point precision issue @@ -1647,26 +1599,26 @@ def test_result_types(self, dtype, expect_dtype): # complex128. Due to https://github.com/pandas-dev/pandas/issues/10952 df = DataFrame({"a": np.random.randn(10).astype(dtype)}) assert df.a.dtype == dtype - df.eval("b = sin(a)", engine=self.engine, parser=self.parser, inplace=True) + df.eval("b = sin(a)", engine=engine, parser=parser, inplace=True) got = df.b expect = np.sin(df.a) assert expect.dtype == got.dtype assert expect_dtype == got.dtype tm.assert_series_equal(got, expect, check_names=False) - def test_undefined_func(self): + def test_undefined_func(self, engine, parser): df = DataFrame({"a": np.random.randn(10)}) msg = '"mysin" is not a supported function' with pytest.raises(ValueError, match=msg): - df.eval("mysin(a)", engine=self.engine, parser=self.parser) + df.eval("mysin(a)", engine=engine, parser=parser) - def test_keyword_arg(self): + def test_keyword_arg(self, engine, parser): df = DataFrame({"a": np.random.randn(10)}) msg = 'Function "sin" does not support keyword arguments' with pytest.raises(TypeError, match=msg): - df.eval("sin(x=a)", engine=self.engine, parser=self.parser) + df.eval("sin(x=a)", engine=engine, parser=parser) _var_s = np.random.randn(10)