Python 为什么翻转元组赋值可以改变其行为?

Python 为什么翻转元组赋值可以改变其行为?,python,variable-assignment,Python,Variable Assignment,一直以来,我都有这样的印象,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):

一直以来,我都有这样的印象,
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=节点(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=节点(v=1,nextV=2)
节点A以一个指向自身的指针结束(其
nextV
v
是相同的),因此跟随此树的尝试将永远重复


为什么这些结果不相同?元组解包的行为不应该像所有赋值同时发生一样吗?

因为您要修改的一个项是另一个项的属性,这些项不是相互独立的——需要序列化顺序来确定操作将执行的操作,并且该操作是从左到右的

让我们通过编写与临时变量相同的代码来了解这一点


鉴于以下共同的前奏:

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,这样您就可以区分原地变异的对象和更改引用的对象:

#工作实现
###############################################################
#id(nodeA)#id(nodeB)#AAA.v#AAA.next#BBB.v#BBB.next#
###############################################################
#AAA#BBB#1#BBB#2#无#启动条件
#AAA#AAA#1#BBB#2#无#节点B=旧节点A
#AAA#AAA#1#BBB#2#无#nodeA.next=旧的#nodeB
#BBB#AAA#1#BBB#2#无#nodeA=旧#nodeA#下一个
在工作场景中,我们将
A
B
的名称切换到每个指向相反节点的名称;其他一切都没有改变

相比之下:

#执行失败
###############################################################
#id(nodeA)#id(nodeB)#AAA.v#AAA.next#BBB.v#BBB.next#
###############################################################
#AAA#BBB#1#BBB#2#无#启动条件
#AAA#AAA#1#BBB#2#无#节点B=旧节点A
#BBB#AAA#1#BBB#2#无#nodeA=旧#nodeA#下一个
#BBB#AAA#1#BBB#2#BBB#nodeA.next=旧nodeB

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

@CharlesDuffy,在那里,高兴吗?我回答了python是如何从左到右(不是同时)赋值的,但是看到代码我不确定问题在于赋值,而不是赋值的含义,它是一个无限递归的外壳loop@Raksha“请尊重我。”如我所说,拉克沙,请尊重它。给人打电话是不会帮你得到答案的。这是必要的。在你提供答案后,我将尝试回答你的问题。我也将撤回我的否决票和结束票。顺便说一句,如果我试图编辑问题以获得适当的MCVE,你会生气吗?我不明白这意味着什么。。。。什么“不同”c?这个“不同”的c没有下一个吗?它到底在哪一点上被打破了?我添加的评论有帮助吗?不是真的。。。这是
=
创建指针而不是像列表那样创建副本的情况之一吗?因此,
old\u c\u next
不仅仅是一个节点本身,即使它是一个新变量。。。我只是想把整个过程形象化,记住
nodeA.next
是一个不同的变量,取决于
nodeA
是什么。因此,如果您在更改
nodeA.next
之前更改
nodeA
,您正在修改哪个对象具有作为分配目标的
next
属性。@Raksha,顺便说一句,我的表中有一个错误,来自最初组装的损坏版本;见更正。