繁体   English   中英

元组中列表的Python变异

[英]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) ,而cd仍然引用旧的元组对象。 现在,由于它们是元组,因此无法像列表一样就地更改它们。

至于“硬拷贝”一词,我不会使用。 它甚至在官方文档中都没有出现过,而在Python SO问题旁边提到的问题在其他上下文中也出现过。 它是模棱两可的(与“浅”和“深”相反,它们为其含义提供了很好的线索)。 我认为与您描述的术语“硬拷贝”(同一对象的其他名称/引用/指针)完全相反(对象副本)。 当然,最终有很多方法可以说出同样的话。 我们之所以说“复制”是因为它较短,对于不可变的对象,复制是否发生无关紧要(无论如何都不能更改它们)。 对于易变的人,“复制”通常表示“浅复制”,因为如果要“深复制”,则必须在代码中“更进一步”。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM