Why does Python 3.8.0 allow to change mutable types from enclosing function scope without using "nonlocal" variable?

884 Views Asked by At

In the following Python 3.8.0.0 script, its is not allowed to change immutable variables from enclosing function scope from sub/nested function, however, modifying mutable types elements works perfectly fine without using nonlocal declaration from sub/nested function. Can someone please explain, why it is like this?

def func():
        func_var1 = 18
        func_var2 = 'Python'
        func_var3 = {1,2,3,4,5,6}
        func_var4 = [1,2,3,4,5,6]
        func_var5 = {'P': 'Python', 'J': 'Java'}
        
        def sub_func():
            nonlocal func_var1
            func_var1 = 20
            print(func_var1)
    
            nonlocal func_var2
            func_var2 = 'Java'
            
            # For mutable types, why does it allow to update variable from enclosing function scope without nonlocal declaration? 
            func_var3.add(7) 
            print(func_var3)
    
            func_var4.append(7) 
            print(func_var4)
    
            func_var5.update({'G':'Go'})
            func_var5['R'] = 'Ruby'
            print(func_var5)
    
        sub_func()
    
func()

Output

20
{1, 2, 3, 4, 5, 6, 7}
[1, 2, 3, 4, 5, 6, 7]
{'P': 'Python', 'J': 'Java', 'G': 'Go', 'R': 'Ruby'}
2

There are 2 best solutions below

0
On

You do not change the value of the variables. But the varibales are references to mutable objects and you change these objects. For example func_var4 is a reference to a list which is a mutable object. You add one element to the list. This changes the list but func_var4 still points to the same list. The variable func_var4 has not been changed by this operation.

0
On

The rules about global/nonlocal names and (im)mutable objects are not symmetric in Python regarding reading and writing. Note that in Python there's a (sometimes) important difference between a name of a variable and the object/value pointed to by the name, and so in the following I shall try to be explicit about this and refrain from using the ambiguous term "variable":

  • All names are what we wight call "read-global" (and "read-nonlocal"), in the sense that they can always be looked up without use of the global/nonlocal keyword, and their object retrieved.
  • In contrast, names in outer scope cannot be reassigned, i.e. they cannot be overwritten to point to some new object. However, in rare cases we really do need this ability, and so Python makes it possible through the global/nonlocal keywords. We might say that the code
    global x
    
    makes x "write-global" (while still retaining its status as "read-global").
  • What you've discovered is the fact that while a name from the outer scope cannot be reassigned a new object from within the inner scope, the object itself may in fact be mutated from within the inner scope (if the object in question is mutable, of course). In effect, the mutable object itself does not belong to any given scope. Names have scope, objects do not.

Note that

  • This is not special to Python 3.8 or even Python 3. It is however special to the Python language.
  • This is not special to nested functions but also applies to e.g. module level variables referenced from a deeper scope (like a function or class).