From 94b631699f4c6b7cb563d0e16020f901e49f826b Mon Sep 17 00:00:00 2001 From: Anthony Kozar Date: Fri, 9 Dec 2022 23:45:00 -0500 Subject: [PATCH 1/2] Fix issue #10: Top-level functions and variables not visible in local scopes. Problem In both Pyonic 2 & 3, functions that are defined and variables that are assigned at the top-level of the interpreter are not visible as globals within other functions (or even themselves -- no recursion). A simple example: def foo(): print("foo") def bar(): foo() print("bar") bar() The call to foo() from bar() raises NameError with the message "global name 'foo' is not defined" in Python 2.7 and "name 'foo' is not defined" in Python 3.6. Solution The Python 3.11 documentation says this about the built-in exec() function: "If only globals is provided, it must be a dictionary (and not a subclass of dictionary), which will be used for both the global and the local variables ... Remember that at the module level, globals and locals are the same dictionary. If exec gets two separate objects as globals and locals, the code will be executed as if it were embedded in a class definition." So by passing two different objects as globals and locals to exec(), the Pyonic code was incorrectly asking Python to run it as part of a class definition. globals() and locals() return the same object in the top-level module. Therefore, only one dictionary should be passed to exec(). --- pyonic/interpreter_subprocess/interpreter.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyonic/interpreter_subprocess/interpreter.py b/pyonic/interpreter_subprocess/interpreter.py index a5a39f8..2acf4cb 100644 --- a/pyonic/interpreter_subprocess/interpreter.py +++ b/pyonic/interpreter_subprocess/interpreter.py @@ -40,7 +40,6 @@ def _exec_full(filepath): raw_input = input_replacement input = eval_input_replacement -user_locals = copy.copy(locals()) user_globals = copy.copy(globals()) import time @@ -221,14 +220,14 @@ def interpret_code(code): if len(components) > 1: for component in components[:-1]: c = compile(ast.Module([component]), '', mode='exec') - exec(c, user_locals, user_globals) + exec(c, user_globals) # if the last ast component is an Expr, compile in single mode to print it if isinstance(components[-1], ast.Expr): c = compile(ast.Interactive([components[-1]]), '', mode='single') else: c = compile(ast.Module([components[-1]]), '', mode='exec') - exec(c, user_locals, user_globals) + exec(c, user_globals) except KeyboardInterrupt as e: print('') From d976e97b76a8af7bd6462f783c53f6829d2dc057 Mon Sep 17 00:00:00 2001 From: Anthony Kozar Date: Fri, 9 Dec 2022 23:51:00 -0500 Subject: [PATCH 2/2] Avoid importing module copy. --- pyonic/interpreter_subprocess/interpreter.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyonic/interpreter_subprocess/interpreter.py b/pyonic/interpreter_subprocess/interpreter.py index 2acf4cb..edd78dc 100644 --- a/pyonic/interpreter_subprocess/interpreter.py +++ b/pyonic/interpreter_subprocess/interpreter.py @@ -1,4 +1,3 @@ -import copy from time import sleep __input = None @@ -40,7 +39,7 @@ def _exec_full(filepath): raw_input = input_replacement input = eval_input_replacement -user_globals = copy.copy(globals()) +user_globals = globals().copy() import time import ast