3
3
"""Top level ``eval`` module.
4
4
"""
5
5
6
+ import warnings
6
7
import tokenize
7
8
from pandas .core import common as com
8
9
from pandas .computation .expr import Expr , _parsers , tokenize_string
9
10
from pandas .computation .scope import _ensure_scope
10
- from pandas .compat import DeepChainMap , builtins
11
+ from pandas .compat import string_types
11
12
from pandas .computation .engines import _engines
12
13
from distutils .version import LooseVersion
13
14
@@ -138,7 +139,7 @@ def _check_for_locals(expr, stack_level, parser):
138
139
139
140
def eval (expr , parser = 'pandas' , engine = 'numexpr' , truediv = True ,
140
141
local_dict = None , global_dict = None , resolvers = (), level = 0 ,
141
- target = None ):
142
+ target = None , inplace = None ):
142
143
"""Evaluate a Python expression as a string using various backends.
143
144
144
145
The following arithmetic operations are supported: ``+``, ``-``, ``*``,
@@ -196,6 +197,13 @@ def eval(expr, parser='pandas', engine='numexpr', truediv=True,
196
197
scope. Most users will **not** need to change this parameter.
197
198
target : a target object for assignment, optional, default is None
198
199
essentially this is a passed in resolver
200
+ inplace : bool, default True
201
+ If expression mutates, whether to modify object inplace or return
202
+ copy with mutation.
203
+
204
+ WARNING: inplace=None currently falls back to to True, but
205
+ in a future version, will default to False. Use inplace=True
206
+ explicitly rather than relying on the default.
199
207
200
208
Returns
201
209
-------
@@ -214,29 +222,78 @@ def eval(expr, parser='pandas', engine='numexpr', truediv=True,
214
222
pandas.DataFrame.query
215
223
pandas.DataFrame.eval
216
224
"""
217
- expr = _convert_expression (expr )
218
- _check_engine (engine )
219
- _check_parser (parser )
220
- _check_resolvers (resolvers )
221
- _check_for_locals (expr , level , parser )
222
-
223
- # get our (possibly passed-in) scope
224
- level += 1
225
- env = _ensure_scope (level , global_dict = global_dict ,
226
- local_dict = local_dict , resolvers = resolvers ,
227
- target = target )
228
-
229
- parsed_expr = Expr (expr , engine = engine , parser = parser , env = env ,
230
- truediv = truediv )
231
-
232
- # construct the engine and evaluate the parsed expression
233
- eng = _engines [engine ]
234
- eng_inst = eng (parsed_expr )
235
- ret = eng_inst .evaluate ()
236
-
237
- # assign if needed
238
- if env .target is not None and parsed_expr .assigner is not None :
239
- env .target [parsed_expr .assigner ] = ret
240
- return None
225
+ first_expr = True
226
+ if isinstance (expr , string_types ):
227
+ exprs = [e for e in expr .splitlines () if e != '' ]
228
+ else :
229
+ exprs = [expr ]
230
+ multi_line = len (exprs ) > 1
231
+
232
+ if multi_line and target is None :
233
+ raise ValueError ("multi-line expressions are only valid in the "
234
+ "context of data, use DataFrame.eval" )
235
+
236
+ first_expr = True
237
+ for expr in exprs :
238
+ expr = _convert_expression (expr )
239
+ _check_engine (engine )
240
+ _check_parser (parser )
241
+ _check_resolvers (resolvers )
242
+ _check_for_locals (expr , level , parser )
243
+
244
+ # get our (possibly passed-in) scope
245
+ level += 1
246
+ env = _ensure_scope (level , global_dict = global_dict ,
247
+ local_dict = local_dict , resolvers = resolvers ,
248
+ target = target )
249
+
250
+ parsed_expr = Expr (expr , engine = engine , parser = parser , env = env ,
251
+ truediv = truediv )
252
+
253
+ # construct the engine and evaluate the parsed expression
254
+ eng = _engines [engine ]
255
+ eng_inst = eng (parsed_expr )
256
+ ret = eng_inst .evaluate ()
257
+
258
+ if parsed_expr .assigner is None and multi_line :
259
+ raise ValueError ("Multi-line expressions are only valid"
260
+ " if all expressions contain an assignment" )
261
+
262
+ # assign if needed
263
+ if env .target is not None and parsed_expr .assigner is not None :
264
+ if inplace is None :
265
+ warnings .warn (
266
+ "eval expressions containing an assignment currently"
267
+ "default to operating inplace.\n This will change in "
268
+ "a future version of pandas, use inplace=True to "
269
+ "avoid this warning." ,
270
+ FutureWarning , stacklevel = 3 )
271
+ inplace = True
272
+
273
+ # if returning a copy, copy only on the first assignment
274
+ if not inplace and first_expr :
275
+ target = env .target .copy ()
276
+ else :
277
+ target = env .target
278
+
279
+ target [parsed_expr .assigner ] = ret
280
+
281
+ if not resolvers :
282
+ resolvers = ({parsed_expr .assigner : ret },)
283
+ else :
284
+ # existing resolver needs updated to handle
285
+ # case of mutating existing column in copy
286
+ for resolver in resolvers :
287
+ if parsed_expr .assigner in resolver :
288
+ resolver [parsed_expr .assigner ] = ret
289
+ break
290
+ else :
291
+ resolvers += ({parsed_expr .assigner : ret },)
292
+
293
+ ret = None
294
+ first_expr = False
295
+
296
+ if not inplace and inplace is not None :
297
+ return target
241
298
242
299
return ret
0 commit comments