2

The following code works as expected:

from os import name

def function():
    # if False:
    #     from os import name
    print(name)

function()

However, when uncommenting the if False block, the code stops working:

UnboundLocalError: cannot access local variable name where it is not associated with a value

I wonder why that is. I have since found https://github.com/python/cpython/issues/79250#issuecomment-1093803068 which says that this is "documented behavior" - I wonder where this is documented, though.

bers
  • 4,817
  • 2
  • 40
  • 59
  • 7
    If a function assigns to a name, then that name is assumed to be a local variable, even if there is a global one of the same name. This is true even if the assignment never actually executes. Python "magically knows" that the function is **theoretically capable** of assigning that name, therefore it is treated as local. – John Gordon Jan 04 '23 at 19:36
  • It's not that magical; names are marked as local during code generation, which doesn't concern itself with whether a particular assignment (or import) is reachable. – chepner Jan 04 '23 at 19:48
  • Without the assignment, `name` is a *free* variable, looked up in the closest enclosing scope (which in this case is the global scope). – chepner Jan 04 '23 at 19:51
  • @JohnGordon actually, I decided to use the disassembler and confirmed that _no assignment_ actually happens (i.e. no `STORE_FAST` bytecode instructions gets generated - actually the whole `if False` block gets `NOP`ed out), what actually happens is that names with local assignments will get put into `co_varnames` at an even earlier stage - not exactly magic. I even had to correct my answer. – metatoaster Apr 20 '23 at 00:37

1 Answers1

3

This behavior regarding UnboundLocalError is documented most clearly under the official documentation in the Programming FAQ section, under the heading why am I getting an UnboundLocalError when the variable has a value?. The full behavior regarding this is broadly covered under the "naming and binding" section under "execution model" in the The Python Language Reference manual. Specifically, the following paragraph in section 4.2.2. Resolution of names, fully describes the issue at hand:

When a name is not found at all, a NameError exception is raised. If the current scope is a function scope, and the name refers to a local variable that has not yet been bound to a value at the point where the name is used, an UnboundLocalError exception is raised.

As an import inside a function scope (even if hasn't happened yet) will provide an assignment under name, resulting in local variable name, and given that it isn't bounded where it gets used (via print), the UnboundLocalError will be the result.

As for the "why" - Python does not fully ignore unreachable branches, all code behind if False blocks are considered by the interpreter, so any assignments to some name within a function block, even if "unreachable", will render the given name a valid local variable (specifically, while the generated bytecode as seen by the disassembler may NOP out that block, all assignments to some names within will render all those names as entries in co_varnames).

As an aside, the FAQ entry has been present since Python 2.7 (or perhaps even earlier).

metatoaster
  • 17,419
  • 5
  • 55
  • 66