1

The following program throws UnboundLocalError but I don't understand why. It's a very condensed version of a nasty bug found in one of my scripts.

EDIT: I don't accept the explanation of "variable hoisting". I don't think this actually happens in Python. If the function namespace is printed, it shows that pickle does not exist locally. Going by the LEGB lookup rules, Python should find the pickle name in the global namespace.

Sometimes it takes a while to frame the question. I guess the real question is why. I understand what is happening, but why does Python do this? Why does Python break its own LEGB lookup rules?

import pickle

def func(number):
    print("Global variables=%s" % repr(globals()))
    print("Local namespace=%s" % repr(dir()))
    dir(pickle) # This line will fail
    import pickle   # This line is the cause of the failure.
    return number

func(5)

Program output:

Global variables={'__builtins__': <module '__builtin__' (built-in)>, '__file__': 'c:\\python27\\globaltext.py', '__package__': None, 'func': <function func at 0x02320E70>, '__name__': '__main__', 'pickle': <module 'pickle' from 'C:\Python27\lib\pickle.pyc'>, '__doc__': None}
Local namespace=['number']
Traceback (most recent call last):
  File "test.py", line 8, in <module>
    func(5)
  File "test.py", line 4, in func
    dir(pickle) # This line will fail
UnboundLocalError: local variable 'pickle' referenced before assignment
  • You should make this a [mcve] though. Most of the code (like the `number` stuff) isn't relevant to the problem. – Carcigenicate Nov 06 '19 at 23:37
  • 2
    Does this answer your question? [UnBoundLocalError in Python while printing global variable](https://stackoverflow.com/questions/44645109/unboundlocalerror-in-python-while-printing-global-variable) – Blorgbeard Nov 06 '19 at 23:39
  • I think this is the same as the linked issue: you're shadowing the global `pickle` with your local `pickle`, and the local declaration is hoisted to the top of the function. – Blorgbeard Nov 06 '19 at 23:41
  • I don't think Python "hoists" variables. If I add a `print(dir())` statement at the start of the function, it shows the local namespace only contains `number` and there is no `pickle`. Going by the LEGB look-up rules, when it doesn't find `pickle` it should look in the global namespace. – user5541269 Nov 08 '19 at 05:01
  • Maybe hoisting is kind of a red herring. The point is that because you assign to `pickle` *somewhere* inside the function, python considers every reference to it in the function as a local reference. – Blorgbeard Nov 08 '19 at 22:06

2 Answers2

0

The import pickle in the function causes the name pickle to be marked as a local. The code then fails at runtime when it attempts to access a local that has not yet had a value assigned.

The top level import pickle isn't required for this to happen. This is not even specific to import. The following has the same issue:

def func():
    x # This line will fail
    x = 1  # This line is the cause of the failure.

func()

The error is UnboundLocalError: local variable 'x' referenced before assignment without the assignment to mark the name as a local the error is NameError: global name 'x' is not defined.

Dan D.
  • 73,243
  • 15
  • 104
  • 123
  • That explains what is happening, but not why. Why doesn't Python apply LEGB lookup rules just because the name is also used locally later on? In an interpreted language I wouldn't expect this to happen. I guess that's my real question. – user5541269 Nov 10 '19 at 20:41
0

It is shadowing of one variable to another variable. Variable declared within a function can overshadow another variable declared outside of it. You can use another word for it SCOPE

Here you can find explanation and examples as well LINK.

Similar question on stack1 , stack2

Adam Strauss
  • 1,889
  • 2
  • 15
  • 45
  • In this case, I expect shadowing to work, but it's not. Those other explanations talk about *hoisting*, but printing the local namespace shows that hoisting is not happening. This question has been asked before but I don't think it has been answered correctly. – user5541269 Nov 08 '19 at 14:04
  • In your case it is shadowing. "UnboundLocalError: local variable 'pickle' referenced before assignment", you are importing pickle after calling pickle. – Adam Strauss Nov 08 '19 at 14:12