[英]Python mutation of lists in tuples
在学习了列表和箱形指针图之后,我决定为自己创建随机的东西并测试我的知识。 我将使用浅表副本和可疑的浅表副本这两个词,因为我不确定该定义是否正确。 我的查询是出于提供此类代码行为的原因,请告诉我我是否在认真考虑。
代码A
from copy import *
x=[1,[2,[3,[4]]]] #normal copy/hardcopy
a=x
v=list(x) #suspected shallow copy
y=x.copy() #shallow copy
z=deepcopy(x) #theoretical deep copy
w=x[:] #suspected shallow copy
def test():
print("Original:",x)
print("hardcopy:",a)
print("suspected shallow copy",v)
print("shallow copy",y)
print("deep copy:",z)
print("suspected shallow copy",w)
x[1]=x[1]+[4]
test()
输出A:
Original: [1, [2, [3, [4]], 4]]
hardcopy: [1, [2, [3, [4]], 4]]
suspected shallow copy [1, [2, [3, [4]]]]
shallow copy [1, [2, [3, [4]]]]
deep copy: [1, [2, [3, [4]]]]
suspected shallow copy [1, [2, [3, [4]]]]
代码B
a=(1,2,[1,2,3])
def shallow_copy(x):
tup=()
for i in x:
tup+=(i,)
return tup
def hardcopy(x):
return x
b=hardcopy(a)
c=shallow_copy(a)
a[2]+=[3]
输出B:我在IDLE中看到TypeError,但是list元素的突变仍然完成,并且遍及所有a,b,c
从输出B继续:
a[2][0]=a[2][0]+99
a,b,c
输出C:
((1, 2, [100, 2, 3, 3]), (1, 2, [100, 2, 3, 3]), (1, 2, [100, 2, 3, 3]))
代码D:
a=[1,2,(1,2,3)]
def shallow_copy(x):
tup=[]
for i in x:
tup+=[i]
return tup
def hardcopy(x):
return x
b=hardcopy(a)
c=shallow_copy(a)
d=a.copy()
a[2]=a[2]+(4,)
a,b,c,d
输出D:
[1, 2, (1, 2, 3, 4)], [1, 2, (1, 2, 3, 4)],
[1, 2, (1, 2, 3)], [1, 2, (1, 2, 3)]
从输出A中,我们观察到以下情况:1)对于具有浅表副本的列表,执行
x[1]=x[1]+[4]
不会影响浅表副本。 我的原因可能是a)
=
后跟+
是__add__
而不是__iadd__
(即+=
),并且做__add__
不应修改对象,仅更改一个指针的值(在这种情况下为x及其硬拷贝)
这在输出B中得到进一步的支持,但是在输出C中与之有些矛盾,可能部分是由于下面的原因(b),但是不能太确定。
b)我们在第一层(只有一个slice运算符)中执行了此操作,也许有某种规则可以防止这些元素被修改。
输出B和输出C都支持这一点,尽管可以说输出B位于第一层,可以将其视为增加第二层中的元素,这也符合上述观察结果。
2)TypeError出现在输出B中但仍被执行的原因是什么? 我知道是否可以根据实际更改的最终顺序(在这种情况下为列表)触发异常,但是为什么还存在
TypeError: 'tuple' object does not support item assignment
?
对于以上问题,我已经发表了自己的看法。 我对这个问题有任何想法(最好是理论解决方案),因为我对编程还是比较陌生的。
要回答问题1,看起来很复杂,但答案很简单:
当您有另一个名称引用原始对象时,您将看到原始对象中的更改。 如果您使用x[1] = x[1] + [4]
的形式更改对象,则这些更改将不会反映在其他副本中(浅或深的副本)。 这是因为您要向x[1]
分配一个新对象,而不是像x[1].append(4)
那样进行就地更改。
您可以使用id()
函数进行检查。
要回答您的问题2,并改编自官方文档:
让我们做
a = (['hello'],)
然后
a[0] += [' world']
这和
a[0] = operator.iadd(a[0],[' world'])
iadd
将列表更改到适当的位置,但是分配失败,因为您无法分配给元组(不可变类型)索引。
如果你做
a[0] = a[0] + [' world']
串联会进入新的列表对象,然后对元组索引的分配也会失败。 但是新对象丢失了。 a[0]
位置未更改。
为了澄清OP的评论,直接从此处的文档中可以得知:
许多操作都有“就地”版本。 下面列出的函数比通常的语法提供对原始操作符的更原始的访问; 例如,语句x + = y等效于x = operator.iadd(x,y)。 另一种表达方式是说z = operator.iadd(x,y)等效于复合语句z = x; z + = y。
在这些示例中,请注意,当调用就地方法时,计算和分配是在两个单独的步骤中执行的。 下面列出的就地函数仅执行第一步,即调用就地方法。 第二步,分配,不处理。
至于你的输出D:写作
b = hardcopy(a)
无非就是写作
b = a
真的, b
是一个新的名字引用相同的对象, a
引用。 这是因为a
是可变的,因此将指向原始对象的引用传递到本地函数名称x
。 返回x
只是将相同的引用返回给b
。
这就是为什么你看到的进一步变化a
反映b
。 再次通过赋值使a[2]
一个新的不同对象元组,所以现在a[2]
和b[2]
引用一个新的元组(1,2,3,4)
,而c
和d
仍然引用旧的元组对象。 现在,由于它们是元组,因此无法像列表一样就地更改它们。
至于“硬拷贝”一词,我不会使用。 它甚至在官方文档中都没有出现过,而在Python SO问题旁边提到的问题在其他上下文中也出现过。 它是模棱两可的(与“浅”和“深”相反,它们为其含义提供了很好的线索)。 我认为与您描述的术语“硬拷贝”(同一对象的其他名称/引用/指针)完全相反(对象副本)。 当然,最终有很多方法可以说出同样的话。 我们之所以说“复制”是因为它较短,对于不可变的对象,复制是否发生无关紧要(无论如何都不能更改它们)。 对于易变的人,“复制”通常表示“浅复制”,因为如果要“深复制”,则必须在代码中“更进一步”。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.