0

Here is an example code that I have made to try to understand the mechanics of "nonlocal" keyword. `

# Outer fuction
def func1():
  var1 = 2
  print("---ID of var1 in func1---")
  print(id(var1))
  print(locals())
# Inner function
  def func2():
    nonlocal var1
    var1 += 1
    print("---ID of var1 in func2---")
    print(id(var1))
    print(locals())

  # Call inner function
  func2()
  print("---ID of var1 in func1 after func2 execution---")
  print(id(var1))
  print(locals())

func1()

` We define var1 in func1() namespace. After calling 'nonlocal' in func2(), var1 changes its id and start to appear in local dictionary of func2(). After execution of func2(), it is still exists in local dictionary of func1().

Does it mean that a variable can exist in several namespaces in the same time? Why did id(var1) changed?

I expected that var1 will be only in local dictionary of func1() and 'nonlocal' keyword will just give a possibility to change it in func2().

  • integers are non mutable any assignment will result in a new id ... non-local means it is not in the local scope and also not in the global scope (although perhaps nonlocal would work to find global scope ... it looks in the enclosing scopes to find the variable – Joran Beasley Nov 15 '22 at 04:24
  • Q. Does it mean that a variable can exist in several namespaces in the same time? A. The nonlocal statement causes the listed identifiers to refer to previously bound variables in the nearest enclosing scope excluding globals. Q. Why did id(var1) changed? A. Since integer is immutable, each time you reassign it, it'll produce a new id. – 0x5961736972 Nov 15 '22 at 04:30
  • `id` is really not relevant here at all. Whether or not the *identity of the object* changed doesn't really have anything to do with issues of scope like this – juanpa.arrivillaga Nov 15 '22 at 04:58
  • Note, `locals()` does not return the local namespace, exactly, more like *a dict* that has the local and nonlocal variables in it. But this variables actually exists in a closure. `print(func1.__closure__)` – juanpa.arrivillaga Nov 15 '22 at 05:03
  • @0x5961736972 your claim about integers is not correct. – juanpa.arrivillaga Nov 15 '22 at 05:10
  • 2
    @JoranBeasley no, not any assignment, I wouldn't say even most assignments. The only thing you can say given immutability is that *if two integer objects have different values then they have different identities*. – juanpa.arrivillaga Nov 15 '22 at 05:11
  • @juanpa.arrivillaga Yes, my bad there. I should rephrase it something like this, "each time you assign it a different value, it'll produce a new id." – 0x5961736972 Nov 15 '22 at 09:55

1 Answers1

1

locals() is a very weird function that doesn't do what anyone would reasonably expect it to, with some bizarre undocumented quirks. This particular quirk happens to be documented, but it's still really weird. Generally, you shouldn't use locals() unless you really have no other choice.

The dict returned by locals() is

  • usually not the actual local variable namespace, and
  • may contain variables that aren't actually part of the current local variable namespace.

When called inside a function, the dict returned by locals() will include nonlocal variables:

Free variables are returned by locals() when it is called in function blocks, but not in class blocks.

That doesn't mean the variable actually exists in multiple namespaces at the same time. It means locals() is weird.


As for the ID change, IDs are associated with objects, not variables. The integer object var1 refers to after var1 += 1 is a different object with a different ID than the previous integer object.

user2357112
  • 260,549
  • 28
  • 431
  • 505