3
3
"""Top level ``eval`` module.
4
4
"""
5
5
6
- import warnings
7
6
import tokenize
8
7
from pandas .io .formats .printing import pprint_thing
9
8
from pandas .core .computation import _NUMEXPR_INSTALLED
@@ -148,7 +147,7 @@ def _check_for_locals(expr, stack_level, parser):
148
147
149
148
def eval (expr , parser = 'pandas' , engine = None , truediv = True ,
150
149
local_dict = None , global_dict = None , resolvers = (), level = 0 ,
151
- target = None , inplace = None ):
150
+ target = None , inplace = False ):
152
151
"""Evaluate a Python expression as a string using various backends.
153
152
154
153
The following arithmetic operations are supported: ``+``, ``-``, ``*``,
@@ -205,20 +204,40 @@ def eval(expr, parser='pandas', engine=None, truediv=True,
205
204
level : int, optional
206
205
The number of prior stack frames to traverse and add to the current
207
206
scope. Most users will **not** need to change this parameter.
208
- target : a target object for assignment , optional, default is None
209
- essentially this is a passed in resolver
210
- inplace : bool, default True
211
- If expression mutates, whether to modify object inplace or return
212
- copy with mutation .
213
-
214
- WARNING: inplace=None currently falls back to to True, but
215
- in a future version, will default to False. Use inplace=True
216
- explicitly rather than relying on the default .
207
+ target : object, optional, default None
208
+ This is the target object for assignment. It is used when there is
209
+ variable assignment in the expression. If so, then `target` must
210
+ support item assignment with string keys, and if a copy is being
211
+ returned, it must also support `.copy()` .
212
+ inplace : bool, default False
213
+ If `target` is provided, and the expression mutates `target`, whether
214
+ to modify `target` inplace. Otherwise, return a copy of `target` with
215
+ the mutation .
217
216
218
217
Returns
219
218
-------
220
219
ndarray, numeric scalar, DataFrame, Series
221
220
221
+ Raises
222
+ ------
223
+ ValueError
224
+ There are many instances where such an error can be raised:
225
+
226
+ - `target=None`, but the expression is multiline.
227
+ - The expression is multiline, but not all them have item assignment.
228
+ An example of such an arrangement is this:
229
+
230
+ a = b + 1
231
+ a + 2
232
+
233
+ Here, there are expressions on different lines, making it multiline,
234
+ but the last line has no variable assigned to the output of `a + 2`.
235
+ - `inplace=True`, but the expression is missing item assignment.
236
+ - Item assignment is provided, but the `target` does not support
237
+ string item assignment.
238
+ - Item assignment is provided and `inplace=False`, but the `target`
239
+ does not support the `.copy()` method
240
+
222
241
Notes
223
242
-----
224
243
The ``dtype`` of any objects involved in an arithmetic ``%`` operation are
@@ -232,8 +251,9 @@ def eval(expr, parser='pandas', engine=None, truediv=True,
232
251
pandas.DataFrame.query
233
252
pandas.DataFrame.eval
234
253
"""
235
- inplace = validate_bool_kwarg (inplace , 'inplace' )
236
- first_expr = True
254
+
255
+ inplace = validate_bool_kwarg (inplace , "inplace" )
256
+
237
257
if isinstance (expr , string_types ):
238
258
_check_expression (expr )
239
259
exprs = [e .strip () for e in expr .splitlines () if e .strip () != '' ]
@@ -245,7 +265,10 @@ def eval(expr, parser='pandas', engine=None, truediv=True,
245
265
raise ValueError ("multi-line expressions are only valid in the "
246
266
"context of data, use DataFrame.eval" )
247
267
268
+ ret = None
248
269
first_expr = True
270
+ target_modified = False
271
+
249
272
for expr in exprs :
250
273
expr = _convert_expression (expr )
251
274
engine = _check_engine (engine )
@@ -266,28 +289,33 @@ def eval(expr, parser='pandas', engine=None, truediv=True,
266
289
eng_inst = eng (parsed_expr )
267
290
ret = eng_inst .evaluate ()
268
291
269
- if parsed_expr .assigner is None and multi_line :
270
- raise ValueError ("Multi-line expressions are only valid"
271
- " if all expressions contain an assignment" )
292
+ if parsed_expr .assigner is None :
293
+ if multi_line :
294
+ raise ValueError ("Multi-line expressions are only valid"
295
+ " if all expressions contain an assignment" )
296
+ elif inplace :
297
+ raise ValueError ("Cannot operate inplace "
298
+ "if there is no assignment" )
272
299
273
300
# assign if needed
274
301
if env .target is not None and parsed_expr .assigner is not None :
275
- if inplace is None :
276
- warnings .warn (
277
- "eval expressions containing an assignment currently"
278
- "default to operating inplace.\n This will change in "
279
- "a future version of pandas, use inplace=True to "
280
- "avoid this warning." ,
281
- FutureWarning , stacklevel = 3 )
282
- inplace = True
302
+ target_modified = True
283
303
284
304
# if returning a copy, copy only on the first assignment
285
305
if not inplace and first_expr :
286
- target = env .target .copy ()
306
+ try :
307
+ target = env .target .copy ()
308
+ except AttributeError :
309
+ raise ValueError ("Cannot return a copy of the target" )
287
310
else :
288
311
target = env .target
289
312
290
- target [parsed_expr .assigner ] = ret
313
+ # TypeError is most commonly raised (e.g. int, list), but you
314
+ # get IndexError if you try to do this assignment on np.ndarray.
315
+ try :
316
+ target [parsed_expr .assigner ] = ret
317
+ except (TypeError , IndexError ):
318
+ raise ValueError ("Cannot assign expression output to target" )
291
319
292
320
if not resolvers :
293
321
resolvers = ({parsed_expr .assigner : ret },)
@@ -304,7 +332,6 @@ def eval(expr, parser='pandas', engine=None, truediv=True,
304
332
ret = None
305
333
first_expr = False
306
334
307
- if not inplace and inplace is not None :
308
- return target
309
-
310
- return ret
335
+ # We want to exclude `inplace=None` as being False.
336
+ if inplace is False :
337
+ return target if target_modified else ret
0 commit comments