【问题标题】:Python mutation of lists in tuples元组中列表的 Python 突变
【发布时间】:2018-04-14 14:30:36
【问题描述】:

在了解了列表和方框和指针图之后,我决定为自己创建随机的东西并测试我的知识。我将使用浅拷贝和疑似浅拷贝这两个词,因为我不确定它们的定义是否正确。我的问题是提供此类代码行为的原因,请告诉我我是否在思考。

代码 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,但列表元素的突变仍然完成,并且跨越 ALL 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) 我们在第一层执行了这个(只有一个切片操作符),也许有某种规则阻止这些元素被修改。

输出 B 和输出 C 都支持这一点,尽管输出 B 可能被认为在第一层,但可以将其视为增加第二层中的元素,它符合上述观察。

2) 输出B出现TypeError,但仍然执行的原因是什么?我知道是否触发异常取决于您实际更改的最终顺序(本例中的列表),但为什么仍然存在TypeError: 'tuple' object does not support item assignment

我已就上述问题提出了我的看法。我很欣赏关于这个问题的任何想法(最好是理论解决方案),因为我对编程还是比较陌生。

【问题讨论】:

  • 使用id(your obj) 获得一个唯一的ID - 打印并比较它们 - 很容易确定事情是否相同。 a = x 只是 b 拥有的数据的另一个“名称”,它根本没有副本。
  • @PatrickArtner 我知道在实践考试中检查的方法。但是,这类题目喜欢出现在我的笔试中,所以我必须从理论的角度推导出一种理解它们的方法。

标签: python python-3.x list


【解决方案1】:

回答问题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 的评论,直接来自 here 中的文档,它说

许多操作都有“就地”版本。下面列出的函数比通常的语法提供了对就地运算符更原始的访问;例如,语句 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 问题旁边的这个问题中提到的,出现在其他上下文中。而且它是模棱两可的(与“浅”和“深”相反,它们为它们的含义提供了很好的线索)。对于您描述的术语“硬拷贝”(指向同一对象的附加名称/引用/指针),我认为正好相反(对象副本)。当然,最终有很多方法可以说同样的事情。我们说“复制”是因为它更短,对于不可变对象,复制是否发生并不重要(无论如何您都无法更改它们)。对于可变对象来说,“复制”通常意味着“浅复制”,因为如果您想要“深复制”,则必须在代码中“走得更远”。

【讨论】:

  • 为了澄清我的理解,你的意思是写a[0] += ['world'],不直接相当于单独做iadd吗?我认为 上的官方文档说 ,Implement and not return 这个词给出了一个很大的暗示,即分配不是自动完成的,而键入 += 实际上意味着先执行 iadd 再分配?跨度>
  • 对于 Q1 的回答,当您说“您会看到原始版本中的更改,而这些更改不会反映在其他副本中”时,您是指 [2,[3, [4]]]?因为浅拷贝意味着外部列表不同,但它们仍然包含具有相同引用的项目。如果浅拷贝是指[1,[2,[3,[4]]]]的那些,“你没有改变内部对象,而是表面级别列表”的解释。对我来说仍然不直观。
  • 因为那部分解释是错误的。我正在修复它。
  • @PrashinJeevaganth 刚刚修复,感谢您对错误的关注。
  • 嗯,我现在明白了。因此,对 x 的元素进行 += 或 .append 之类的就地更改可以更容易地将更改传递到其浅表副本,因为副本的某些元素也指向这个修改后的值。 x[1] = x[1] + [4] 只改变 x 的 1 个指针的值。但总的来说,要点是当在 seq A 中通过赋值/就地进行值更改时,是否还有来自可变对象的指针指向 A?如果有的话,即使像输出 C 这样的东西也可以传递更改。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-12
  • 1970-01-01
相关资源
最近更新 更多