Strange discrepency between local and non-local scope

I have the following example:

def outer():
    def inner():
        x = 10
        dummy()
        
    def dummy():
        print("I am dummy func")
        
    inner()

which works well, however below code does not work:

def outer():
    def inner():
        x = 10
        dummy()
        
        def dummy():
            print("I am dummy func")
        
    inner()

If calling of function should be before the declaration then why is declaration allowed before usage if the declaration is in non-local scope to the function where the call is made. From what I understand the situation is same when traversing the AST, in both the situations the dummy function declaration node will come after the dummy call node. If python handles the first case, why is it that it throws error in second case.

The def statement is not a declaration, it is an actual assignment. Imagine it sorta-kinda like dummy = function() with a magic function constructor that gives you the new function object. We can get somewhat close to that using a lambda function:

def outer():
    def inner():
        x = 10
        dummy()
    dummy = lambda: print("I am a dummy func")
    inner()

This works, because the assignment to dummy happens before the call to inner and thus the call to dummy.

def outer():
    def inner():
        x = 10
        dummy()
        dummy = lambda: print("I am dummy func")
    inner()

In this example, it’s assigning to dummy after trying to call the function. The def statement behaves exactly the same way; it’s an executable statement that does an assignment.

2 Likes

Thanks for the illustrative explanation. I want to know if the name resolution in python is done at runtime, while the code is running because then the scope table would deviate from the one which it would have generated while it was created at static time.

A name becomes local, nonlocal, or global, based on declarations; but it doesn’t actually get assigned until something assigns to it. So there’s a small element of static name resolution, but all actual name bindings happen at run time. For example:

spam = "ham"
def outer():
    print(spam) # UnboundLocalError
    spam = "no ham for you"

Because, at some point in the function, the name spam is assigned to, it is local - even at the point where it hasn’t yet been assigned to. That part is static, locked in when the function is compiled. Everything else is at run time.

1 Like

Ohh okay, understood. So can you please explain the process of name resolution completely, what is done at static time and what is done at runtime and is this same for class declarations also or is there something different for it

The scope of the name is decided statically. The name is resolved, within that chosen scope, at runtime.

Just as explained in your previous question.

Also relevant:

(especially see my answer on this one ^)