8000 Unexpected behaviour of "compile" function · Issue #103375 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

Unexpected behaviour of "compile" function #103375

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wrongnull opened this issue Apr 8, 2023 · 5 comments
Closed

Unexpected behaviour of "compile" function #103375

wrongnull opened this issue Apr 8, 2023 · 5 comments
Labels
interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error

Comments

@wrongnull
Copy link
Contributor

As already mentioned in #100185 builtin function compile does not take into account their enclosing scope. For instance:

x = 0

def a():
    print(x)
    x = 1

def b():
    source = "print(x)"
    co = compile(source, "", "exec")
    exec(co, globals(), locals())
    x = 1

Invocation of the first function works correctly and generates an exception, what can not be said about the second one. This example and one in issue above are also relevant for all currently supported python versions. I bet this can be fixed by promoting the symbol table of the scope to the function.

@wrongnull wrongnull added the type-bug An unexpected behavior, bug, or error label Apr 8, 2023
@stevendaprano
Copy link
Member

The second one is correct. Your first example has print inside a function, where it attempts to print the value of an unbound local variable: the local variable x is not bound until after print(x) is attempted, so a() fails.

The second example is not executed inside a function, it is just the top-level (module level) code print(x) where the global x has already been bound to the value 0. Inside the helper function b(), the local variable x is irrelevant.

To be clear: just because you are calling compile from inside a helper function, on a string defined inside that function, that doesn't make the code represented by the string execute inside that helper function's scope. There's no bug here, and compile is working correctly.

@arhadthedev arhadthedev added the interpreter-core (Objects, Python, Grammar, and Parser dirs) label Apr 8, 2023
@stevendaprano
Copy link
Member

To get the same result as your a() function, you need to actually pass the source of a to compile like this helper:

def c():
    source = """
def a():
    print(x)
    x = 1

a()
"""
    co = compile(source, "", "exec")
    exec(co, globals(), locals())

To execute the source code print(x) inside the scope of your helper function b(), pass b's locals to compile:

def b1():
    # Intentionally fails with NameError
    source = "print(x)"
    co = compile(source, "", "exec")
    exec(co, locals())

def b2():
    source = "print(x)"
    x = 999
    co = compile(source, "", "exec")
    exec(co, locals())

@terryjreedy
Copy link
Member

@wrongnull The fact that you did not expect the behavior of compile does not make it a bug. Instead of reporting your surprise here, you should have first examined the compile documentation for a conflict with the behavior (the definition of a 'bug') or asked on discourse - help whether the behavior is a bug. Without at least an apparent conflict, one should assume that the behavior of something so basic and tested is either intended or at least accepted as the de facto intent. In any case, the 'mode' and 'flags' parameters are the context passed to compile. The context for execution is the pair of namespaces passed to exec.

@stevendaprano and everyone else: When two separate namespaces are passed to exec, the effect is as the code source were embedded in a class statement code that creates the passed in globals() and locals()

<code that results in globals() as passed to exec, here including 'x=0'>
class _:
    <code that results in locals as passed to exec, here not including x>
    print(x)

# Will print `0`

Adding x = 2 after the print statement in the class definition above has no effect on the printed 'x'. To understand 2 parameter exec, one must understand that class statements execute in two phases: 1) execute the body in a new anonymous namespace; 2) pass the class name, class bases (both from the header), and new namespace to type. Only the first phase is relevant to exec.

At the point of 'exec' in example b, locals() has two entries {'source': 'print(x)', 'co': }. When 'print(x)' is executed, 'x' is not found in the passed in locals(), but it is found in the passed in globals().

@wrongnull
Copy link
Contributor Author

@terryjreedy OK, I got it. But such a semantics of this function raises an exception if used in pdb. What to do with it?

@wrongnull
Copy link
Contributor Author
wrongnull commented Apr 8, 2023

BTW, talking of class definition...

x = 0
y = 0
def foo():
    x = 1
    y = 1
    class X:
        print(x, y)
        x = 2


foo()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

4 participants
0