33"""Top level ``eval`` module.
44"""
55
6- import warnings
76import tokenize
87from pandas .io .formats .printing import pprint_thing
98from pandas .core .computation import _NUMEXPR_INSTALLED
@@ -148,7 +147,7 @@ def _check_for_locals(expr, stack_level, parser):
148147
149148def eval (expr , parser = 'pandas' , engine = None , truediv = True ,
150149 local_dict = None , global_dict = None , resolvers = (), level = 0 ,
151- target = None , inplace = None ):
150+ target = None , inplace = False ):
152151 """Evaluate a Python expression as a string using various backends.
153152
154153 The following arithmetic operations are supported: ``+``, ``-``, ``*``,
@@ -207,18 +206,24 @@ def eval(expr, parser='pandas', engine=None, truediv=True,
207206 scope. Most users will **not** need to change this parameter.
208207 target : a target object for assignment, optional, default is None
209208 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.
209+ inplace : bool, default False
210+ If `target` is provided, and the expression mutates `target`, whether
211+ to modify `target` inplace. Otherwise, return a copy of `target` with
212+ the mutation.
213213
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.
214+ If `inplace=True`, but `target` cannot be modified inplace, a
215+ ValueError will be raised. Examples of targets that cannot be
216+ modified inplace are integers and strings. Examples of targets
217+ that can be modified inplace are lists and class instances.
217218
218219 Returns
219220 -------
220221 ndarray, numeric scalar, DataFrame, Series
221222
223+ Raises
224+ ------
225+ ValueError : `inplace=True`, but the provided `target` could not be
226+ modified inplace.
222227 Notes
223228 -----
224229 The ``dtype`` of any objects involved in an arithmetic ``%`` operation are
@@ -232,8 +237,13 @@ def eval(expr, parser='pandas', engine=None, truediv=True,
232237 pandas.DataFrame.query
233238 pandas.DataFrame.eval
234239 """
235- inplace = validate_bool_kwarg (inplace , 'inplace' )
236- first_expr = True
240+
241+ inplace = validate_bool_kwarg (inplace , "inplace" )
242+ modifiable = hasattr (target , "__setitem__" )
243+
244+ if inplace and not modifiable :
245+ raise ValueError ("Cannot modify the provided target inplace" )
246+
237247 if isinstance (expr , string_types ):
238248 _check_expression (expr )
239249 exprs = [e .strip () for e in expr .splitlines () if e .strip () != '' ]
@@ -245,7 +255,10 @@ def eval(expr, parser='pandas', engine=None, truediv=True,
245255 raise ValueError ("multi-line expressions are only valid in the "
246256 "context of data, use DataFrame.eval" )
247257
258+ ret = None
248259 first_expr = True
260+ target_modified = False
261+
249262 for expr in exprs :
250263 expr = _convert_expression (expr )
251264 engine = _check_engine (engine )
@@ -269,21 +282,21 @@ def eval(expr, parser='pandas', engine=None, truediv=True,
269282 if parsed_expr .assigner is None and multi_line :
270283 raise ValueError ("Multi-line expressions are only valid"
271284 " if all expressions contain an assignment" )
285+ elif not (parsed_expr .assigner is None
286+ or target is None or modifiable ):
287+ raise ValueError ("Cannot assign expression output to target" )
272288
273289 # assign if needed
274290 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
291+ target_modified = True
283292
293+ # Cannot assign to the target if it is not assignable.
284294 # if returning a copy, copy only on the first assignment
285295 if not inplace and first_expr :
286- target = env .target .copy ()
296+ try :
297+ target = env .target .copy ()
298+ except AttributeError :
299+ raise ValueError ("Cannot return a copy of the target" )
287300 else :
288301 target = env .target
289302
@@ -304,7 +317,6 @@ def eval(expr, parser='pandas', engine=None, truediv=True,
304317 ret = None
305318 first_expr = False
306319
307- if not inplace and inplace is not None :
308- return target
309-
310- return ret
320+ # We want to exclude `inplace=None` as being False.
321+ if inplace is False :
322+ return target if target_modified else ret
0 commit comments