53
53
params = (
54
54
pytest .param (
55
55
engine ,
56
- marks = pytest .mark .skipif (
57
- engine == "numexpr" and not USE_NUMEXPR ,
58
- reason = f"numexpr enabled->{ USE_NUMEXPR } , "
59
- f"installed->{ NUMEXPR_INSTALLED } " ,
60
- ),
56
+ marks = [
57
+ pytest .mark .skipif (
58
+ engine == "numexpr" and not USE_NUMEXPR ,
59
+ reason = f"numexpr enabled->{ USE_NUMEXPR } , "
60
+ f"installed->{ NUMEXPR_INSTALLED } " ,
61
+ ),
62
+ td .skip_if_no_ne ,
63
+ ],
61
64
)
62
65
for engine in ENGINES
63
66
)
@@ -145,10 +148,12 @@ def lhs(request):
145
148
midhs = lhs
146
149
147
150
148
- @td .skip_if_no_ne
149
- class TestEvalNumexprPandas :
150
- engine = "numexpr"
151
- parser = "pandas"
151
+ class TestEval :
152
+ @pytest .fixture (autouse = True )
153
+ def set_engine_parser_attrs (self , engine , parser ):
154
+ # Older tests look for these as attributes, so we set them here.
155
+ self .engine = engine
156
+ self .parser = parser
152
157
153
158
@classmethod
154
159
def setup_class (cls ):
@@ -184,47 +189,18 @@ def test_complex_cmp_ops(self, cmp1, cmp2, binop, lhs, rhs):
184
189
self .check_equal (result , expected )
185
190
186
191
@pytest .mark .parametrize ("cmp_op" , expr .CMP_OPS_SYMS )
187
- def test_simple_cmp_ops (self , cmp_op ):
188
- bool_lhses = (
189
- DataFrame (tm .randbool (size = (10 , 5 ))),
190
- Series (tm .randbool ((5 ,))),
191
- tm .randbool (),
192
- )
193
- bool_rhses = (
194
- DataFrame (tm .randbool (size = (10 , 5 ))),
195
- Series (tm .randbool ((5 ,))),
196
- tm .randbool (),
197
- )
192
+ def test_simple_cmp_ops (self , cmp_op , lhs , rhs ):
193
+ lhs = lhs < 0
194
+ rhs = rhs < 0
198
195
199
196
if self .parser == "python" and cmp_op in ["in" , "not in" ]:
200
197
msg = "'(In|NotIn)' nodes are not implemented"
201
- for lhs , rhs in product (bool_lhses , bool_rhses ):
202
198
203
- with pytest .raises (NotImplementedError , match = msg ):
204
- self .check_simple_cmp_op (lhs , cmp_op , rhs )
199
+ with pytest .raises (NotImplementedError , match = msg ):
200
+ self .check_simple_cmp_op (lhs , cmp_op , rhs )
205
201
return
206
202
207
- for lhs , rhs in product (bool_lhses , bool_rhses ):
208
- self .check_simple_cmp_op (lhs , cmp_op , rhs )
209
-
210
- @pytest .mark .parametrize ("op" , _good_arith_ops )
211
- def test_binary_arith_ops (self , op , lhs , rhs , request ):
212
- self .check_binary_arith_op (lhs , op , rhs )
213
-
214
- def test_modulus (self , lhs , rhs ):
215
- self .check_modulus (lhs , "%" , rhs )
216
-
217
- def test_floor_division (self , lhs , rhs ):
218
- self .check_floor_division (lhs , "//" , rhs )
219
-
220
- @td .skip_if_windows
221
- def test_pow (self , lhs , rhs ):
222
- # odd failure on win32 platform, so skip
223
- self .check_pow (lhs , "**" , rhs )
224
-
225
- @pytest .mark .parametrize ("op" , expr .CMP_OPS_SYMS )
226
- def test_single_invert_op (self , op , lhs ):
227
- self .check_single_invert_op (lhs , op )
203
+ self .check_simple_cmp_op (lhs , cmp_op , rhs )
228
204
229
205
@pytest .mark .parametrize ("op" , expr .CMP_OPS_SYMS )
230
206
def test_compound_invert_op (self , op , lhs , rhs , request ):
@@ -266,11 +242,16 @@ def check_equal(self, result, expected):
266
242
assert result == expected
267
243
268
244
def check_chained_cmp_op (self , lhs , cmp1 , mid , cmp2 , rhs ):
269
- def check_operands (left , right , cmp_op ):
270
- return _eval_single_bin (left , cmp_op , right , self .engine )
271
245
272
- lhs_new = check_operands (lhs , mid , cmp1 )
273
- rhs_new = check_operands (mid , rhs , cmp2 )
246
+ if self .parser == "python" :
247
+ ex1 = f"lhs { cmp1 } mid { cmp2 } rhs"
248
+ msg = "'BoolOp' nodes are not implemented"
249
+ with pytest .raises (NotImplementedError , match = msg ):
250
+ pd .eval (ex1 , engine = self .engine , parser = self .parser )
251
+ return
252
+
253
+ lhs_new = _eval_single_bin (lhs , cmp1 , mid , self .engine )
254
+ rhs_new = _eval_single_bin (mid , cmp2 , rhs , self .engine )
274
255
275
256
if lhs_new is not None and rhs_new is not None :
276
257
ex1 = f"lhs { cmp1 } mid { cmp2 } rhs"
@@ -306,7 +287,8 @@ def check_simple_cmp_op(self, lhs, cmp1, rhs):
306
287
result = pd .eval (ex , engine = self .engine , parser = self .parser )
307
288
self .check_equal (result , expected )
308
289
309
- def check_binary_arith_op (self , lhs , arith1 , rhs ):
290
+ @pytest .mark .parametrize ("arith1" , _good_arith_ops )
291
+ def test_binary_arith_ops (self , arith1 , lhs , rhs ):
310
292
ex = f"lhs { arith1 } rhs"
311
293
result = pd .eval (ex , engine = self .engine , parser = self .parser )
312
294
expected = _eval_single_bin (lhs , arith1 , rhs , self .engine )
@@ -339,20 +321,24 @@ def check_alignment(self, result, nlhs, ghs, op):
339
321
340
322
# modulus, pow, and floor division require special casing
341
323
342
- def check_modulus (self , lhs , arith1 , rhs ):
343
- ex = f "lhs { arith1 } rhs"
324
+ def test_modulus (self , lhs , rhs ):
325
+ ex = r "lhs % rhs"
344
326
result = pd .eval (ex , engine = self .engine , parser = self .parser )
345
327
expected = lhs % rhs
346
-
347
328
tm .assert_almost_equal (result , expected )
348
- expected = self .ne .evaluate (f"expected { arith1 } rhs" )
349
- if isinstance (result , (DataFrame , Series )):
350
- tm .assert_almost_equal (result .values , expected )
329
+
330
+ if self .engine == "numexpr" :
331
+ expected = self .ne .evaluate (r"expected % rhs" )
332
+ if isinstance (result , (DataFrame , Series )):
333
+ tm .assert_almost_equal (result .values , expected )
334
+ else :
335
+ tm .assert_almost_equal (result , expected .item ())
351
336
else :
352
- tm .assert_almost_equal (result , expected .item ())
337
+ expected = _eval_single_bin (expected , "%" , rhs , self .engine )
338
+ tm .assert_almost_equal (result , expected )
353
339
354
- def check_floor_division (self , lhs , arith1 , rhs ):
355
- ex = f "lhs { arith1 } rhs"
340
+ def test_floor_division (self , lhs , rhs ):
341
+ ex = "lhs // rhs"
356
342
357
343
if self .engine == "python" :
358
344
res = pd .eval (ex , engine = self .engine , parser = self .parser )
@@ -371,8 +357,10 @@ def check_floor_division(self, lhs, arith1, rhs):
371
357
parser = self .parser ,
372
358
)
373
359
374
- def check_pow (self , lhs , arith1 , rhs ):
375
- ex = f"lhs { arith1 } rhs"
360
+ @td .skip_if_windows
361
+ def test_pow (self , lhs , rhs ):
362
+ # odd failure on win32 platform, so skip
363
+ ex = "lhs ** rhs"
376
364
expected = _eval_single_bin (lhs , "**" , rhs , self .engine )
377
365
result = pd .eval (ex , engine = self .engine , parser = self .parser )
378
366
@@ -387,19 +375,19 @@ def check_pow(self, lhs, arith1, rhs):
387
375
else :
388
376
tm .assert_almost_equal (result , expected )
389
377
390
- ex = f "(lhs { arith1 } rhs) { arith1 } rhs"
378
+ ex = "(lhs ** rhs) ** rhs"
391
379
result = pd .eval (ex , engine = self .engine , parser = self .parser )
392
380
393
381
middle = _eval_single_bin (lhs , "**" , rhs , self .engine )
394
382
expected = _eval_single_bin (middle , "**" , rhs , self .engine )
395
383
tm .assert_almost_equal (result , expected )
396
384
397
- def check_single_invert_op (self , elem , cmp1 ):
385
+ def check_single_invert_op (self , lhs ):
398
386
# simple
399
387
try :
400
- elb = elem .astype (bool )
388
+ elb = lhs .astype (bool )
401
389
except AttributeError :
402
- elb = np .array ([bool (elem )])
390
+ elb = np .array ([bool (lhs )])
403
391
expected = ~ elb
404
392
result = pd .eval ("~elb" , engine = self .engine , parser = self .parser )
405
393
tm .assert_almost_equal (expected , result )
@@ -766,43 +754,6 @@ def test_disallow_python_keywords(self):
766
754
df .query ("lambda == 0" )
767
755
768
756
769
- @td .skip_if_no_ne
770
- class TestEvalNumexprPython (TestEvalNumexprPandas ):
771
- engine = "numexpr"
772
- parser = "python"
773
-
774
- def check_chained_cmp_op (self , lhs , cmp1 , mid , cmp2 , rhs ):
775
- ex1 = f"lhs { cmp1 } mid { cmp2 } rhs"
776
- msg = "'BoolOp' nodes are not implemented"
777
- with pytest .raises (NotImplementedError , match = msg ):
778
- pd .eval (ex1 , engine = self .engine , parser = self .parser )
779
-
780
-
781
- class TestEvalPythonPython (TestEvalNumexprPython ):
782
- engine = "python"
783
- parser = "python"
784
-
785
- def check_modulus (self , lhs , arith1 , rhs ):
786
- ex = f"lhs { arith1 } rhs"
787
- result = pd .eval (ex , engine = self .engine , parser = self .parser )
788
-
789
- expected = lhs % rhs
790
- tm .assert_almost_equal (result , expected )
791
-
792
- expected = _eval_single_bin (expected , arith1 , rhs , self .engine )
793
- tm .assert_almost_equal (result , expected )
794
-
795
-
796
- class TestEvalPythonPandas (TestEvalPythonPython ):
797
- engine = "python"
798
- parser = "pandas"
799
-
800
- def check_chained_cmp_op (self , lhs , cmp1 , mid , cmp2 , rhs ):
801
- # FIXME: by calling this parent class method, we are using the parent
802
- # class's "engine" and "parser", which I don't think is what we want.
803
- TestEvalNumexprPandas .check_chained_cmp_op (self , lhs , cmp1 , mid , cmp2 , rhs )
804
-
805
-
806
757
f = lambda * args , ** kwargs : np .random .randn ()
807
758
808
759
0 commit comments