Почему переключение назначения кортежа может изменить его поведение? - PullRequest
0 голосов
/ 23 сентября 2018

Все это время я находился под впечатлением, что 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 Ответ

0 голосов
/ 23 сентября 2018

Поскольку один из элементов, которые вы изменяете, является атрибутом другого, они не зависят друг от друга - необходим порядок сериализации, чтобы определить, что будет делать операция, и эта операция слева направо.

Давайте посмотрим, как это работает, написав этот код, как это было бы с временными переменными.


Учитывая следующую общую прелюдию:

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 между двумя случаями.


Давайте посмотрим, как это работает во время выполнения в случае, когда все работает правильно, с некоторым псевдокодом, показывающим идентификаторы объектов, чтобы вы могли различатьмежду объектами, которые были видоизменены на месте по сравнению с измененными ссылками:

# 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 (BBBв нашем примере), поэтому мы изменили оригинальный узел B указатель next, чтобы он указывал на себя, создавая цикл в основе проблемы.

...