Python 中列表的状态信息和不变性

State information and immutability of lists in Python

我是 Python 的初学者,使用 Mark Lutz 的书来学习 Python 的基础知识。

这是作者用来演示使用列表存储状态信息的示例:

def tester(start):
    def nested(label):
        print(label,state[0])
        state[0] += 1
    state = [start]
    return nested

下面是测试状态信息的代码:

F = tester(3)
F('sam')
F('sam')

您会看到计数器从 3 开始增加,然后继续增加。本质上,上面的代码将初始状态 start(在对象初始化期间传递)存储在 [state] 中,并在每次调用 label 时递增它。

但是,我不确定为什么 Python 不会在 nested 块中抛出错误。具体来说,[state]tester 本地的,而不是 nested.

为了证明我的意思,我将用 state.

替换 state[0]
def tester(start):
    def nested(label):
        print(label,state) #Replaced state[0] with state
        state += 1         #Replaced state[0] with state
        print("after:",state)
    state = start          #Replaced state[0] with state
    return nested

从技术上讲,上面的代码也应该可以正常工作,因为我所做的只是用变量替换了列表。但是,PyCharm 甚至不会 运行 这段代码。我得到一个错误 nboundLocalError: local variable 'state' referenced before assignment

谁能解释一下为什么带有 list 的版本可以正常工作?作者表示"this leverages the mutability of lists, and relies on the fact that in-place object do not classify a name as local."

我不太确定那是什么意思。有人可以帮帮我吗?感谢您对我的帮助。

You should read this section of the documentation

基本上,在两个版本中,嵌套块的作用域都允许它与包含块的名称空间进行交互。不同之处在于您没有在第一个示例中重新分配 state ,而是在改变它。

在第二个示例中,Python 知道您稍后将在函数中为该引用赋值,因此它被视为来自本地 nested 命名空间的名称,而不是比外部 tester 命名空间。

您可以使用 nonlocal 关键字来规避此问题并使用来自其他名称空间的其他引用

def tester(start):
    def nested(label):
        nonlocal state
        print(label,state) 
        state += 1         
        print("after:",state)
    state = start          
    return nested

据我了解,因为 nested 嵌套在 tester 下,它可以访问属于 tester 的任何对象和变量,因为 tester 是在这种情况下,父函数和 nested 是子函数。 Python 不会因为 inheritance.

而产生错误

关于将 state[0] 替换为 state,Python 会自动假定 state 是一个 integer,因为您正试图添加它。虽然 state 是一个列表,但你不能添加到 is 除非你 append 是它的一个元素 - 这不是你的情况。 state[0] 起作用而不是 state 的原因是因为 state[0]state 列表中的一个元素,它向它添加了 0。

它是 1) Python 变量赋值实际上只是为内存中的基础值创建别名(指针)的函数,以及如何处理可变类型和不可变类型之间的区别;和 2) 一些 Python "magic" 与闭包相关。你问题的症结实际上是第一点。

为了解决这个问题,举个例子:

a = 3
b = 3

a 和 b 都指向同一个底层对象:

assert hex(id(a)) == hex(id(b)) 

是真的。但是,然后设置 b = 4 将导致 b 指向内存中的不同对象(表明 int 是不可变的)。

但是,列表是可变的(可修改 "in place")。例如:c = [2] 在像 c[0] = 3.

这样的操作前后会有相同的内存位置

这个非常基本的解释有很多含义,需要一些时间来解释。例如,变量不能“指向”其他变量,而是仍然指向底层对象。

因此,列表可以表现出 "weird" 行为(另一个常见的相关混淆是将默认参数值设置为列表,然后在函数中对其进行修改),但也可以利用您的示例显示的方式。