Pythonic交换变量出错啦
在做Leetcode 143 Reorder List 这这题目时候, 发现了一个关于Python变量交换很神奇的问题.
这题目很简单, 先将链表分成前后两段, 然后再对后段进行反转, 最后重新合并两个列表. 这个奇怪的问题就出在对链表进行反转的过程中.
Pass的代码如下:
class Solution(object):
def reorderList(self, head):
"""
:type head: ListNode
:rtype: void Do not return anything, modify head in-place instead.
"""
# first divide the linked list into 2 parts
if head is None or head.next is None:
return
fast, slow = head, head
while fast and slow:
if fast.next is None or fast.next.next is None:
break
fast = fast.next.next
slow = slow.next
part2 = slow.next
slow.next = None
part1 = head
# Now, part1 is the first half and part2 is the last half
# Reverse the last half
this_node = part2
last_node = None
while this_node:
this_node.next, this_node, last_node = last_node, this_node.next, this_node
part2 = last_node
# combine this 2 parts together
p1, p2 = part1, part2
while p1 and p2:
p1.next, p2.next, p1, p2 = p2, p1.next, p1.next, p2.next
但是, 如果我将反转后半段链表中这句代码
this_node.next, this_node, last_node = last_node, this_node.next, this_node
将变量赋值的顺序换一下, 变成下面这样:
this_node, last_node, this_node.next = this_node.next, this_node, last_node
就会报错AttributeError: 'NoneType' object has no attribute 'next'
同样的, 我们将合并两个linkedin list的代码中的这句话
p1.next, p2.next, p1, p2 = p2, p1.next, p1.next, p2.next
交换下变量赋值的顺序, 变成下面这样:
p1, p2, p1.next, p2.next = p1.next, p2.next, p2, p1.next
程序就会进入死循环.
似乎对链表进行交换操作的时候, 必须把和next
指针相关的变量放在赋值语句的最前面才能成功赋值. 从逻辑上看, 这两个幅值语句交换位置之后, 并没有什么区别. 那么到底是什么原因导致程序执行的过程中产生差别的呢?
分析下具体报错的原因
假设我们需要反转的linked list为[5,6,7]
, 在执行完第一遍循环的时候, 我们期望得到的结果为:
this_node
表示链表[6,7]
;
last_node
表示链表[5]
;
但是在错误的代码运行完第一个循环后, 我们得到的结果如下:
this_node
表示链表[6]
last_node
表示链表[5,6]
也就是说, 我们将node[5]
指向None
的操作并没有执行, 执行的是将node[6]
指向了None
. That is, 赋值语句中的this_node
和next_node
两个变量交换在执行this_node.next = last_node
之前已经发生了!!!
Python到底做了什么
根据Stackoverflow上的这个回答. 我们可以做一件和答案相似的事情!
import dis
src = '''
class ListNode(object):
def __init__(self, x):
self.val = x
self.next = None
this_node = ListNode('5')
this_node.next = ListNode('6')
this_node.next.next = ListNode('7')
last_node = None
this_node, last_node, this_node.next = this_node.next, this_node, last_node
'''
code = compile(src, '<string>', 'exec')
dis.dis(code)
得到关键行的输出如下:
11 73 LOAD_NAME 2 (this_node)
76 LOAD_ATTR 3 (next)
79 LOAD_NAME 2 (this_node)
82 LOAD_NAME 5 (last_node)
85 ROT_THREE
86 ROT_TWO
87 STORE_NAME 2 (this_node)
90 STORE_NAME 5 (last_node)
93 LOAD_NAME 2 (this_node)
96 STORE_ATTR 3 (next)
99 LOAD_CONST 5 (None)
102 RETURN_VALUE
可以发现, 当我们在等号右边写下this_node.next
时候, Python解释器并没有去计算this_node.next
的值, 而是记下了this_node
和next
这个属性. 当PC指向87时, 变量this_node
的值已经被修改了; 当PC指向96时, 我们修改了新的this_node
的next
属性! 这就导致了之前问题的发生!
类似问题的解决方法
我们可以直接同传统的幅值方式, 即采用一个tmp
临时变量来存储中间值, 但是这么写并不Pythonic, 不能体现Life is short, I use Python的精髓.
另外一个解决方案就是对类似变量进行交换时, 将有attr的变量放在最前面而没有attr的变量放在最后面. 因为Python执行幅值的顺序是从左往右的, 先修改变量的attr, 再修改相应的变量就不会出现这个奇怪的错误了!!