1

I wrote the following toy:

def foo():
    x = 5
    def foo2():
        print("Locals: ", locals())
        print("Vars: ", vars())
        print("dir: ", dir())
        print("CP 1")
        print("x =", x)
        print("CP 2")
        print("Locals: ", locals())
        print("Vars: ", vars())
        print("dir: ", dir())
    foo2()

foo()

print("-----------------------")

def foo():
    x = 5
    def foo2():
        print("Locals: ", locals())
        print("Vars: ", vars())
        print("dir: ", dir())
        print("CP 1")
        print("x =", x)
        print("CP 2")
        del x
        print("Locals: ", locals())
        print("Vars: ", vars())
        print("dir: ", dir())
    foo2()

foo()

which produces the following output:

Locals:  {'x': 5}
Vars:  {'x': 5}
dir:  ['x']
CP 1
x = 5
CP 2
Locals:  {'x': 5}
Vars:  {'x': 5}
dir:  ['x']
-----------------------
Locals:  {}
Vars:  {}
dir:  []
CP 1
Traceback (most recent call last):
  File "testing.py", line 34, in <module>
    foo()
  File "testing.py", line 32, in foo
    foo2()
  File "testing.py", line 26, in foo2
    print("x =", x)
UnboundLocalError: local variable 'x' referenced before assignment
>>> 

Notice how behaviour of the second edition is modified even in the areas of the code up to which the two editions are identical (and thus should produce identical outcomes). Since x, according to the first edition, does exist in the local namespace, the del statement shouldn't be an issue.

Questions:

1) Is the first or second edition 'right'? Should x exist in the namespace or not?

2) Is there an explanation for this behaviour or is it a bug?

(Follow up to 2: 3) Should the second edition run without an error or is it supposed to crash?)

Edward Garemo
  • 434
  • 3
  • 13
  • Related: [UnboundLocalError on local variable when reassigned after first use](https://stackoverflow.com/q/370357/4518341) – wjandrea May 27 '20 at 04:48
  • Does this answer your question? [UnboundLocalError on local variable when reassigned after first use](https://stackoverflow.com/questions/370357/unboundlocalerror-on-local-variable-when-reassigned-after-first-use) – norok2 May 27 '20 at 05:44

2 Answers2

1

The del x triggers the interpreter into shadowing the non-local x variable defined outside foo2()'s frame.

The same effect would be obtained if you were to replace del x with x = ... at the very same position.

The reason for this is that x is actually on the same level as foo2() and when del x is reached during foo2(), the interpreter decides not that the name x should be reserved for a local variable x, and hence it does not update your locals() with the name from it's outer name. Moving the x assignment inside foo2() would let x be truly local and hence appear in locals():

def foo():
    def foo2():
        x = 5
        print("Locals: ", locals())
        print("Vars: ", vars())
        print("dir: ", dir())
        print("CP 1")
        print("x =", x)
        print("CP 2")
        del x
        print("Locals: ", locals())
        print("Vars: ", vars())
        print("dir: ", dir())
    foo2()

foo()
Locals:  {'x': 5}
Vars:  {'x': 5}
dir:  ['x']
CP 1
x = 5
CP 2
Locals:  {}
Vars:  {}
dir:  []

as well as declaring x to refer to the nonlocal variable explicitly inside foo2():

def foo():
    x = 5
    def foo2():
        nonlocal x
        print("Locals: ", locals())
        print("Vars: ", vars())
        print("dir: ", dir())
        print("CP 1")
        print("x =", x)
        print("CP 2")
        del x
        print("Locals: ", locals())
        print("Vars: ", vars())
        print("dir: ", dir())
    foo2()

foo()
Locals:  {'x': 5}
Vars:  {'x': 5}
dir:  ['x']
CP 1
x = 5
CP 2
Locals:  {}
Vars:  {}
dir:  []
norok2
  • 25,683
  • 4
  • 73
  • 99
  • This is somewhat similar to the scoping behaviors observed [here](https://stackoverflow.com/q/57691284/5218354). – norok2 May 27 '20 at 00:16
  • I don't understand your explanation. Why would the del be executed at definition time? If it were, then something like >>> def g(): ... l = [1, 2, 3] ... print(l) ... del l ... >>> g() [1, 2, 3] wouldn't work and there'd be no point to the keyword. – Edward Garemo May 27 '20 at 00:46
  • @EdwardGaremo Sorry, the explanation was misleading. I have now improved it. – norok2 May 27 '20 at 05:39
  • I still don't understand thought. I understand that assigning x within foo2 shadows the one in foo, but shouldn't this only be evident AFTER the relevant line, x = ... or del x is executed? (I think I have a misconceived notion of the execution model: I thought that, as an interpreted language, each line of python is executed one after the other - but your answer (and the behaviour) implies a sort of lookahead) – Edward Garemo May 27 '20 at 10:22
  • Is it not possible to do something like def foo(): x = 'Non-local x' def foo2(): print(x) x = 'Local x' print(x) foo2() foo() ? – Edward Garemo May 27 '20 at 10:23
  • 1
    @EdwardGaremo [This answer](https://stackoverflow.com/a/370380/5218354) from the proposed duplicate should be clearing the underlying mechanisms for you. – norok2 May 27 '20 at 10:26
  • I'm not sure I understand but it's probably worth making a separate post asking for clarification. Thanks! – Edward Garemo May 27 '20 at 11:06
1

The following post solved it for me:

https://amir.rachum.com/blog/2013/07/09/python-common-newbie-mistakes-part-2/

I would like highlight:

"The first misconception is that Python, being an interpreted language (which is awesome, I think we can all agree), is executed line-by-line. In truth, Python is being executed statement-by-statement. To get a feel of what I mean, go to your favorite shell (you aren’t using the default one, I hope) and type the following:

def foo():

Press Enter. As you can see, the shell didn’t offer any output and it’s clearly waiting for you to continue with your function definition."

This was the source of my confusion.

Thanks to @norok2 for pointing me to a post which pointed me to this.

(https://docs.python.org/3.1/faq/programming.html?highlight=nonlocal#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value

was also helpful)

Edward Garemo
  • 434
  • 3
  • 13