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" 行为(另一个常见的相关混淆是将默认参数值设置为列表,然后在函数中对其进行修改),但也可以利用您的示例显示的方式。
我是 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" 行为(另一个常见的相关混淆是将默认参数值设置为列表,然后在函数中对其进行修改),但也可以利用您的示例显示的方式。