API: multi-line, not inplace eval · pandas-dev/pandas@99c2d93 (original) (raw)
`@@ -3,11 +3,12 @@
`
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.\nThis 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
`