Edward Garemo
Edward Garemo

Reputation: 474

Change in interpreted behaviour upon introduction of del keyword

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?)

Upvotes: 1

Views: 53

Answers (2)

Edward Garemo
Edward Garemo

Reputation: 474

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)

Upvotes: 1

norok2
norok2

Reputation: 26886

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:  []

Upvotes: 1

Related Questions