Skip to content

Commit 9d76560

Browse files
authored
Merge pull request #386 from datacamp/update_tasks_docs
Improve documentation to taskRunEval
2 parents dc07483 + 4ddc47d commit 9d76560

File tree

1 file changed

+42
-7
lines changed

1 file changed

+42
-7
lines changed

pythonwhat/tasks.py

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ def get_error(f, *args, **kwargs):
345345

346346
# General tasks to eval or exec code, with decorated counterparts -------------
347347

348-
# Eval an expression tree or node (with setting envs, pre_code and/or expr_code)
348+
349349
@process_task
350350
def taskRunEval(
351351
tree,
@@ -362,41 +362,74 @@ def taskRunEval(
362362
tempname="_evaluation_object_",
363363
call=None,
364364
):
365+
"""
366+
Eval an expression tree (with setting envs, pre_code and/or expr_code)
367+
Utility function later wrapped to extract either result (end state of a variable), output (stdout) or error
368+
369+
Args:
370+
tree (ast): current focused ast, used to get code to execute
371+
process: manages shell (see local.py)
372+
shell: link to to get process namespace from execution up until now
373+
env: update value in focused code by name
374+
extra_env: variables to be replaced in focused code by name from extra_env in has_expr
375+
context: sum of set_context in sct chain
376+
context_vals: extra context argument in has_expr
377+
pre_code: argument in has_expr to execute code before evaluating, for example to set a seed
378+
expr_code: code to execute instead of focused code
379+
name: extract value after executing focused expr_code (~post_code)
380+
copy: copy entire env because our expr_code could have side effects
381+
tempname: key for the result when it is added to context, only for v1 sct's
382+
call: only used in v1 sct's
383+
384+
Returns:
385+
str: output of the executed code
386+
"""
365387
try:
366388
# Prepare code and mode -----------------------------------------------
367-
if (expr_code and name) or (not expr_code and isinstance(tree, ast.Module)):
389+
# Verify if expr_code is expression code (returning a value) or just runnable code.
390+
if ( # expr_code returns nothing and then we will extract a value
391+
expr_code and name
392+
) or ( # No expr_code and the tree is of a node type that does not evaluate to have output
393+
not expr_code and isinstance(tree, ast.Module)
394+
):
395+
# We are not focused on an expression (no output)
368396
mode = "exec"
369397
else:
370398
mode = "eval"
399+
# Wrap the focused node in the tree so it can be run with eval()
371400
if not isinstance(tree, (ast.Module, ast.Expression, ast.Expr)):
372401
tree = ast.Expression(tree)
373402

374403
# Expression code takes precedence over tree code
375404
if expr_code:
376405
code = expr_code
377406
tree = ast.parse(code, mode=mode)
378-
else:
407+
else: # Compile the tree to Python code
379408
code = compile(tree, "<script>", mode)
380409

381410
# Set up environment --------------------------------------------------
382-
# avoid deep copy if specified, or if just looking up variable by name
383-
# unpack 'container nodes' first
411+
# Unpack 'container nodes' before checking if a deepcopy is needed
384412
if isinstance(tree, ast.Module):
385413
tree = tree.body
386414
if isinstance(tree, ast.Expression):
387415
tree = tree.body
388416
if isinstance(tree, ast.Expr):
389417
tree = tree.value
418+
419+
# Avoid a deep copy if specified or if the ast node type indicates we are looking up a variable by name
420+
# ast.Name, ast.Subscript and ast.Load most of the time do not have side effects in the environment,
421+
# making a deepcopy unnecessary
390422
if not copy or (
391423
isinstance(tree, (ast.Name, ast.Subscript))
392424
and isinstance(tree.ctx, ast.Load)
393425
):
394-
new_env = dict(get_env(shell.user_ns))
426+
new_env = dict(get_env(shell.user_ns)) # shallow copy of env
395427
else:
396428
# might raise an error if object refuses pickle interface
397429
# used by deepcopy to restore class
398430
new_env = utils.copy_env(get_env(shell.user_ns))
399431

432+
# Apply additional env and context variables
400433
if env is not None:
401434
new_env.update(deepcopy(env))
402435
if extra_env is not None:
@@ -422,13 +455,15 @@ def taskRunEval(
422455
except NameError:
423456
return UndefinedValue()
424457

425-
# call object if dict with args and kwargs was passed
458+
# Backwards compatibility with v1 SCT's
459+
# If object is callable, args and kwargs can be passed in to be used in a call on object
426460
if call is not None:
427461
obj = obj(*call["args"], **call["kwargs"])
428462

429463
# Set object as temp variable in original environment, so we can
430464
# later get its class, etc.., in order to extract it from process
431465
get_env(shell.user_ns)[tempname] = obj
466+
432467
return str(obj)
433468

434469
except Exception as e:

0 commit comments

Comments
 (0)