如何在 Python list 中保存数组元素的引用并反映更新?
结论
普通 Python list 无法让某个元素动态引用另一个列表的位置。[listB[0], listB[1]] 保存的是当时取到的对象的引用,之后执行 listB[0] = 0,listA[0] 不会跟着更新。
如果希望保留 print(listA[0]) 的写法,可以用自定义视图保存原列表和索引,每次访问时再读取原列表对应位置的当前值。
为什么原来的代码仍然输出 1?
Python 列表保存的是对象的引用。执行以下代码后,listA[0] 和 listB[0] 都引用整数对象 1:
listB = [1, 2, 3, 4, 5]
listA = [listB[0], listB[1]]
随后执行:
listB[0] = 0
这会让 listB 第一个位置的引用指向整数对象 0。listA 中的引用没有变化,所以 listA[0] 仍然是 1。整数是不可变对象,这次赋值没有原地修改整数 1。
保留 listA[0] 写法的实现
通过 __getitem__ 方法,可以让 listA[0] 读取 listB 中对应位置的值:
class ListView:
def __init__(self, source, indices):
self._source = source
self._indices = tuple(indices)
def __getitem__(self, index):
return self._source[self._indices[index]]
def __len__(self):
return len(self._indices)
listB = [1, 2, 3, 4, 5]
listA = ListView(listB, [0, 1])
print(listA[0]) # 1
listB[0] = 0
print(listA[0]) # 0
print(listA[1]) # 2
ListView(listB, [0, 1]) 保存了原列表和需要访问的索引。每次读取 listA[0],都会重新读取 listB[0],因此能拿到更新后的值。
这里的 listA 是 ListView 实例,支持示例中的下标读取和 len(),但没有实现完整的列表接口,不支持切片、append() 或 listA[0] = ...。
如果 listA 必须是普通列表
可以把原列表和索引组成元组,存入列表,读取时再根据索引取值:
listB = [1, 2, 3, 4, 5]
listA = [(listB, 0), (listB, 1)]
source, index = listA[0]
print(source[index]) # 1
listB[0] = 0
source, index = listA[0]
print(source[index]) # 0
这种方式需要改变读取表达式:listA[0] 返回的是保存的元组,不会自动执行第二次下标访问。
使用时需要注意什么?
这两种方案都按索引读取当前位置的值。如果在 listB 前面插入或删除元素,索引对应的内容可能改变;如果对应位置已不存在,读取时会抛出 IndexError。
视图保存的是创建时传入的列表对象。之后执行 listB = [9, 8, 7],只会让变量 listB 指向新列表,已有视图仍然读取原来的列表。
如果元素本身是可变对象,例如嵌套列表,两个列表共享该对象时,双方都能看到对其内容的原地修改。但执行 listB[0] = 新对象 仍然只会替换 listB 中的引用。使用 listB[:2] 或浅拷贝,也不能让副本自动跟随这种替换。
备注:内容仅供参考。
