为什么Python 3.8.0允许在不使用“ nonlocal”变量的情况下从函数范围更改可变类型?

问题描述

在以下Python 3.8.0.0脚本中,不允许从子/嵌套函数的封闭函数范围更改不可变变量,但是,修改可变类型元素的工作原理非常好,而无需使用子/嵌套函数的非本地声明。有人可以解释一下,为什么会这样吗?

def func():
        func_var1 = 18
        func_var2 = 'Python'
        func_var3 = {1,2,3,4,5,6}
        func_var4 = [1,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()

输出

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

解决方法

您不更改变量的值。但是变量是对可变对象的引用,您可以更改这些对象。例如func_var4是对作为可变对象的列表的引用。您将一个元素添加到列表中。这会更改列表,但是func_var4仍指向同一列表。变量func_var4尚未通过此操作更改。

,

关于全局/非本地名称和(im)可变对象的规则在Python中在读写方面并不对称。请注意,在Python中,变量的名称和名称所指向的 object /值之间(有时)存在重要区别,因此在下文中,我将尝试对此进行明确说明,并避免使用模糊的术语“变量”:

  • 所有名称就是我们所称的“ read -global”(和“ read -nonlocal”)始终可以在不使用global / nonlocal关键字的情况下进行查找,并检索其对象。
  • 相反,不能重新分配外部作用域中的名称,即,不能覆盖它们以指向某些新对象。但是,在极少数情况下,我们确实确实需要此功能,因此Python可以通过global / nonlocal关键字来实现。我们可以说代码
    global x
    
    使x为“ 写入-全局”(同时仍保持其状态为“ 读取-全局”)。
  • 您发现的事实是,虽然不能从内部范围内重新分配来自外部范围的名称,但是实际上该对象本身可能是从内部范围内被突变的范围(当然,如果所讨论的对象是可变的)。实际上,可变对象本身不属于任何给定范围。名称有作用域,对象没有作用域。

请注意

  • 这对于Python 3.8甚至Python 3都不是特殊的。但是对于Python语言却不是特殊的。
  • 这对嵌套函数并不特殊,但也适用于从更深的范围(例如函数或类)中引用的模块级变量。