当前位置: 代码迷 >> python >> 为什么翻转元组赋值可以改变它的行为?
  详细解决方案

为什么翻转元组赋值可以改变它的行为?

热度:53   发布时间:2023-07-14 09:50:05.0

一直以来,我的印象是a, b, c = c, a, ba, c, b = c, b, a ......我认为这是一种分配变量的方法同时,因此您不必创建一堆临时变量。 但显然它们是不同的,因为一个破坏了我的代码。

这是我的原始/工作实现:

class Node:
    def __init__(self, v = None, next = None):
        self.v = v
        self.next = next
    def __repr__(self):
        return "Node(v=%r, nextV=%r)" % (self.v, self.next.v if self.next else None)

a = Node(1)
b = Node(2)
a.next = b

def flip(nodeA, nodeB):
    nodeB, nodeA.next, nodeA = nodeA, nodeB, nodeA.next
    return (nodeA, nodeB)

a, b = flip(a, b)
print "A=%r; B=%r" % (a, b)

它的正确/预期行为是交换链表中的两个节点,如下面的输出所示:

A=Node(v=2, nextV=None); B=Node(v=1, nextV=2)

但是,如果我像这样重新排序翻转函数:

def flip(nodeA, nodeB):
    nodeB, nodeA, nodeA.next = nodeA, nodeA.next, nodeB
    return (nodeA, nodeB)

...输出坏了:

A=Node(v=2, nextV=2); B=Node(v=1, nextV=2)

节点 A 最终得到了一个指向自身的指针(它的nextVv是相同的),因此尝试跟随这棵树将永远递归。

为什么这些结果不一样? 元组解包不应该像所有分配同时发生一样吗?

因为您正在修改的项目之一是另一个项目的属性,所以它们不是相互独立的——需要一个序列化顺序来确定操作将执行的操作,并且该操作是从左到右的。

让我们通过编写这段代码来看看它是如何发挥作用的,就像使用临时变量一样。


鉴于以下共享前奏:

old_nodeA      = nodeA
old_nodeB      = nodeB
old_nodeA_next = nodeA.next

工作代码类似于以下内容:

# nodeB, nodeA.next, nodeA = nodeA, nodeB, nodeA.next

nodeB      = old_nodeA
nodeA.next = old_nodeB       # nodeA is still the same as old_nodeA here
nodeA      = old_nodeA_next

这是破损的:

# nodeB, nodeA, nodeA.next = nodeA, nodeA.next, nodeB

nodeB      = old_nodeA
nodeA      = old_nodeA_next
nodeA.next = old_nodeB       # we're changing old_nodeA_next.next, not old_nodeA.next

不同之处在于nodeA.next指的是两种情况下不同nodeAnext属性。


让我们看看在运行时一切正常的情况下它是如何工作的,使用一些伪代码显示对象 ID,以便您可以区分对象是就地变异还是引用已更改:

# Working implementation
###############################################################
# id(nodeA) # id(nodeB) # AAA.v # AAA.next # BBB.v # BBB.next # 
###############################################################
# AAA       # BBB       # 1     # BBB      # 2     # None     # Starting condition
# AAA       # AAA       # 1     # BBB      # 2     # None     # nodeB = old_nodeA
# AAA       # AAA       # 1     # BBB      # 2     # None     # nodeA.next = old_nodeB
# BBB       # AAA       # 1     # BBB      # 2     # None     # nodeA = old_nodeA_next

在工作场景中,我们将AB的名称切换为各自引用相对节点; 没有其他改变。

相比之下:

# Broken implementation
###############################################################
# id(nodeA) # id(nodeB) # AAA.v # AAA.next # BBB.v # BBB.next # 
###############################################################
# AAA       # BBB       # 1     # BBB      # 2     # None     # Starting condition
# AAA       # AAA       # 1     # BBB      # 2     # None     # nodeB = old_nodeA
# BBB       # AAA       # 1     # BBB      # 2     # None     # nodeA = old_nodeA_next
# BBB       # AAA       # 1     # BBB      # 2     # BBB      # nodeA.next = old_nodeB

当我们到达nodeA.next = old_nodeB ,名称nodeA已经分配了最初与节点 B 关联的 id(在我们的示例中为BBB ),因此我们将原始节点 B 的next指针更改为指向自身,在问题的核心。

  相关解决方案