Python元组不可变但集合类型属性可以改变
我明白在Python中,命名元组(namedtuple)是不可变的,也就是说它的属性值不能直接被重新赋值。
N = namedtuple("N",['ind','set','v'])
def solve()
items=[]
R = set(range(0,8))
for i in range(0,8):
items.append(N(i,R,8))
items[0].set.remove(1)
items[0].v+=1
在最后一行,我试图给属性 'v'赋一个新值,这样做是行不通的。但是,从items[0]的集合属性中移除元素'1'却是可以的。
这是为什么呢?如果集合属性是列表类型,这种情况也会成立吗?
2 个回答
你是在改变集合,而不是元组。集合是可以被修改的。
>>> s = set()
>>> t = (s,)
>>> l = [s]
>>> d = {42: s}
>>> t
(set([]),)
>>> l
[set([])]
>>> d
{42: set([])}
>>> s.add('foo')
>>> t
(set(['foo']),)
>>> l
[set(['foo'])]
>>> d
{42: set(['foo'])}
不可变性并不意味着元组内部的可变对象也变得不可变。不可变性的意思是你不能改变存储的具体对象,比如说你不能重新指定 items[0].set
。这个限制无论变量是什么类型都是一样的——如果它是一个列表,像 items[0].list = items[0].list + [1,2,3]
这样的操作会失败(因为不能把它重新指定为一个新对象),但 items[0].list.extend([1,2,3])
这样的操作是可以的。
可以这样理解:如果你把代码改成:
new_item = N(i,R,8)
那么 new_item.set
现在就是 R 的别名(在 Python 中,重新指定变量时不会复制对象)。如果元组让可变成员变得不可变,那么你会期待 R.remove(1)
会发生什么呢?因为它和 new_item.set
是同一个集合,你对其中一个的任何改动都会在另一个上显示出来。如果这个集合因为成为元组的成员而变得不可变,那么 R.remove(1)
就会突然失败。在 Python 中,所有的方法调用都是根据对象本身来决定是否成功,而不是根据变量——R.remove(1)
和 new_item.set.remove(1)
必须表现得一样。
这也意味着:
R = set(range(0,8))
for i in range(0,8):
items.append(N(i,R,8))
可能存在一个微妙的错误。R
在这里从未被重新指定,所以 items
中的每个命名元组都得到了同一个集合。你可以通过注意到 items[0].set is items[1].set
为真来确认这一点。因此,每当你修改其中任何一个,或者 R 的时候,修改都会在所有地方显示出来(它们只是同一个对象的不同名称)。
这个问题通常出现在你做类似于
a = [[]] * 3
a[0].append(2)
的操作时,结果会变成 [[2], [2], [2]]
。解决这个问题有两种方法:
首先,要非常小心在赋值时创建一个新的可变对象,除非你确实想要一个别名。在嵌套列表的例子中,通常的解决方案是使用 a = [[] for _ in range(3)]
。对于你在元组中的集合,把 R = ...
这一行移动到循环内部,这样它就会为每个 namedtuple
重新指定为一个新的集合。
第二种解决方法是使用不可变类型。把 R
变成 frozenset
,这样就无法添加或删除元素了。