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

`