Python中变量交换的陷阱

在解决LeetCode 143题时,发现Python中链表反转过程中变量交换的异常现象。错误地交换赋值顺序会导致程序报错或进入死循环。问题在于Python解释器的执行顺序,它先记录了变量引用而非值。解决方法包括使用临时变量或确保指针变量在赋值语句中优先更新。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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_nodenext_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_nodenext这个属性. 当PC指向87时, 变量this_node的值已经被修改了; 当PC指向96时, 我们修改了新的this_nodenext属性! 这就导致了之前问题的发生!

类似问题的解决方法

我们可以直接同传统的幅值方式, 即采用一个tmp临时变量来存储中间值, 但是这么写并不Pythonic, 不能体现Life is short, I use Python的精髓.

另外一个解决方案就是对类似变量进行交换时, 将有attr的变量放在最前面而没有attr的变量放在最后面. 因为Python执行幅值的顺序是从左往右的, 先修改变量的attr, 再修改相应的变量就不会出现这个奇怪的错误了!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值