Python: lazy submodule imports invalidate parent modules > UnboundLocalError - But why?

Viewed 93

Considering the following code: I'm using a submodule allover my code and a special submodule (might be uber heavy to load) in only one function so I'm lazy importing:

import xml.etree

def y():
    print(xml.etree.__name__)

def x():
    print(xml.etree.__name__)

    import xml.dom
    print(xml.dom.__name__)

if __name__ == "__main__":
    y()
    x()

This will result in xml being unbound:

UnboundLocalError: local variable 'xml' referenced before assignment

(yes to fix it I can move the import up within the function OR do from xml import dom)

I'd love to know what happens here.
Apparently the imports are evaluated just before Python is entering the function.

What's the reasoning behind this?

2 Answers

Imports are not evaluated before entering the function, but the function body is parsed for assignments before execution. Any name bound in an assignment is considered a name local to the function, since the beginning of the body. As an import is an implicit form of assignment, the name xml just becomes local to the function. But you are using it before the import assignment. So the error is actually quite obvious.

To fix it you can do as you say or simply use global:

import xml.etree

def y():
    print(xml.etree.__name__)

def x():
    global xml
    print(xml.etree.__name__)

    import xml.dom
    print(xml.dom.__name__)

if __name__ == "__main__":
    y()
    x()

Here is the same in a simpler example:

a = 1

def foo():
    print(a)  # works

def bar():
    print(a)  # gives error
    a = 2
    print(a)
    
foo()
bar()

This gives:

1
UnboundLocalError: local variable 'a' referenced before assignment

The reason is that local variables (like global variables) are determined at compile time, overwriting global variables of the same name, but bound to the value only at run-time, so if they are accessed before the assignment, they are already known, but not yet defined (e.g. bound).

Related