代码之家  ›  专栏  ›  技术社区  ›  Raksha

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

  •  -2
  • Raksha  · 技术社区  · 6 年前

    一直以来,我都以为 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 回复  |  直到 6 年前
        1
  •  2
  •   Charles Duffy    6 年前

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


    给出了以下共同的序曲:

    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 next 不同的属性 nodeA


    让我们看看在运行时,在一切正常的情况下,这是如何实现的,一些伪代码显示了对象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 ,名称 已经分配了最初与节点B关联的id( BBB 起初的 节点B 下一个 指向自身的指针,生成问题核心的循环。