问题描述
一直以来,我的印象是a, b, c = c, a, b
与a, 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 最终得到了一个指向自身的指针(它的nextV
和v
是相同的),因此尝试跟随这棵树将永远递归。
为什么这些结果不一样? 元组解包不应该像所有分配同时发生一样吗?
1楼
因为您正在修改的项目之一是另一个项目的属性,所以它们不是相互独立的——需要一个序列化顺序来确定操作将执行的操作,并且该操作是从左到右的。
让我们通过编写这段代码来看看它是如何发挥作用的,就像使用临时变量一样。
鉴于以下共享前奏:
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
指的是两种情况下不同nodeA
的next
属性。
让我们看看在运行时一切正常的情况下它是如何工作的,使用一些伪代码显示对象 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
在工作场景中,我们将A
和B
的名称切换为各自引用相对节点;
没有其他改变。
相比之下:
# 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
指针更改为指向自身,在问题的核心。