Python中 a, b = b, a 的具体字节码实现? 以及会出现的问题!

博客围绕力扣448题的Python解法展开,探讨交换数组元素时出现的问题。通过编译字节码,分析各指令含义,如LOAD_CONST、STORE_NAME等,并对代码生成的字节码进行推理,解释了元素交换的过程。同时提到对Python对象管理、字节码执行等知识的认识还较片面。

首先引入问题:

class Solution:
    def findDisappearedNumbers(self, nums):
        rst = list()

        # 循环遍历每一个数组元素
        for i in range(len(nums)):
            # 判断该元素位置上的值,是否需要交换, 注意 循环条件
            while nums[i] != i + 1 and nums[i] != nums[nums[i] - 1]:
                # 需要使用一个变量来代替,不能直接去交换
                a = nums[i] - 1 
                nums[i], nums[a] = nums[a], nums[i]
                
        # 再次遍历数组,找出缺失数字
        for i, num in enumerate(nums):
            if i + 1 != num:
                rst.append(i+1)
        return rst
  • 可以看到代码中交换两个数组中的两个位置的数, 我是用了一个变量来解决。 为什么不能直接写呢? 因为会出错! 具体看下面的引出问题:
  • 原意是想让 a[2] a[3] 元素交换位置,但是缺改变了三个元素,非常奇怪?
    在这里插入图片描述
  • 同样问题展示:并且有相应的解答:
    点击链接查看:问题描述
    在这里插入图片描述

我的想法和推敲(太深奥,可能不太准确)

(ps)字节码这里就不去细说了,非常多东西。推荐:
做出分析解释:
  • 下面正式对上述问题做出解释分析,直接去讲这个问题的推理, 不在记录知识点,因为前面推荐的文章和专栏已经非常精炼,全面了。

  • (1) 首先编译出字节码:

    • 每列代表的含义,第一列代表 源代码行号, 第二个代表偏移地址, 字节码形式,区分每条指令。第三个代表指令名字,第四个代表 操作数,指令所需的参数。 第五个括号则是 取操作数对应的具体内内容。
    >>> def a():
    ...     a = [1, 2, 3, 4, 5, 6, 7, 8]
    ...     a[2], a[a[2]] = a[a[2]], a[2]
    ... 
    >>> import dis
    >>> dis.dis(a)
      2           0 LOAD_CONST               1 (1)
                  2 LOAD_CONST               2 (2)
                  4 LOAD_CONST               3 (3)
                  6 LOAD_CONST               4 (4)
                  8 LOAD_CONST               5 (5)
                 10 LOAD_CONST               6 (6)
                 12 LOAD_CONST               7 (7)
                 14 LOAD_CONST               8 (8)
                 16 BUILD_LIST               8
                 18 STORE_FAST               0 (a)
    
      3          20 LOAD_FAST                0 (a)
                 22 LOAD_FAST                0 (a)
                 24 LOAD_CONST               2 (2)
                 26 BINARY_SUBSCR
                 28 BINARY_SUBSCR
                 30 LOAD_FAST                0 (a)
                 32 LOAD_CONST               2 (2)
                 34 BINARY_SUBSCR
                 36 ROT_TWO
                 38 LOAD_FAST                0 (a)
                 40 LOAD_CONST               2 (2)
                 42 STORE_SUBSCR
                 44 LOAD_FAST                0 (a)
                 46 LOAD_FAST                0 (a)
                 48 LOAD_CONST               2 (2)
                 50 BINARY_SUBSCR
                 52 STORE_SUBSCR
                 54 LOAD_CONST               0 (None)
                 56 RETURN_VALUE
    
    
  • (2) 分析字节码的含义:(第二个文章里有讲解,建议观看原文,我理解的可能不准确)

    • 官方文档的讲解,更是简短,不易理解,这里给出链接
    • LOAD_CONST: 主要要是将常量加载进栈(数据栈)。 根据操作数,在代码对象的co_consts 中根据下表索引进行取值,将常量对象引用压入栈中,实际对象在堆中存储。一条指令将一个对象或一个对象的值压入栈时,就意味着该对象的引用(或指针)入栈。 当一个对象或其值从栈弹出时,同理如此再次弹出引用。 解释器知道如何使用这些引用来检索或存储对象的数据!
    • STORE_NAME: 代码对象所保存的 名字列表co_names(资料中的描述), 根据操作数,对应索引找到 对应变量名字的引用(就是说变量的引用存储在 对应索引位置中)。
      • 将栈中的 常量引用出栈,并保存到对应的 变量所指向的 堆内存的某个位置。
    • LOAD_CONST: 访问 常量元组 co_consts对应索引位置的 常量对象的 引用。将引用压入栈中。
    • LOAD_NAME: 将变量的引用指向的变量值,压入栈中。(详细看文章讲解,对于变量,引用,指针的概念,比较绕)
    • BINARY_SUBTRACTSTORE_SUBSCR 可以查看对应的官方文档上面给出来了,并且下面还要去讲这两个,因为不太清楚具体用法概念,不敢分析~ 传播错误知识
  • (3) 进行我的推理(只供参考):

    • 通过上面的讲解,可以先试着分析简单的 a,b = b, a 理解下。
    >>> def func1():
    ...     a = 1
    ...     b = 2
    ...     a, b = b, a
    ... 
    >>> dis.dis(func1)
      2           0 LOAD_CONST               1 (1)    # 加载常量1对象的引用进栈
                  2 STORE_FAST               0 (a)	  # 出栈1的引用,并存储在堆中,由 a指向
    
      3           4 LOAD_CONST               2 (2)	  # 同理
                  6 STORE_FAST               1 (b)	  # 同理
    
      4           8 LOAD_FAST                1 (b)    # 加载 b指向对象的引用 进栈
                 10 LOAD_FAST                0 (a)	  # 同理
                 12 ROT_TWO							  # 将栈中的,两个元素反转。比如(打个比方)进栈是 a, b ,现在 变为 b, a 。 栈顶由b 变为 a
                 14 STORE_FAST               0 (a)    # 将栈顶元素弹出来,然后a指向栈顶弹出的引用
                 16 STORE_FAST               1 (b)	  # 同理, 这样就实现 a,b 指向的对象交换了
                 18 LOAD_CONST               0 (None)  # 专栏和文章里有解释
                 20 RETURN_VALUE
    
    • 下面分析,上面图片的问题

    • (1)分析第二行代码生成的字节码,主要就是利用 BUILD_LIST,将常量元素引用依次加入到栈中, 然后生成列表,并将 a变量只想到 对应的列表引用。
      在这里插入图片描述

    • (2)分析第三行代码

    • a[2], a[a[2]] = a[a[2]], a[2]

    • 20-28 :主要是实现 = 右边的 a[a[2]]

    			 20 LOAD_FAST                0 (a)    # a 指向的列表 引用进栈
                 22 LOAD_FAST                0 (a)	  # 同上
                 24 LOAD_CONST               2 (2)	  # 常量引用进栈,整体顺序是由外到内的
                 26 BINARY_SUBSCR			# 操作看下图  根据现在站内元素为(模拟) a, a, 2, 将a,2出栈,计算a[2] 引用压入栈中
                 28 BINARY_SUBSCR			# 同上,执行完毕后 得到a[a[2]]的引用,就是 a[3] 对应的引用
    
    • 官方文档中介绍 TOS 是栈顶元素, TOS1是栈顶元素下面一个元素,也就是第二个元素,TOS2依次类推。
      在这里插入图片描述
    • 执行完毕后,此时栈内的元素为 a[3] (此位置索引,对应的对象引用)只有这一个元素。
    • 30-34: 和上面一样的操作,计算 = 右面的 a[2]
     			 30 LOAD_FAST                0 (a)
                 32 LOAD_CONST               2 (2)
                 34 BINARY_SUBSCR
    
    • 此时栈内为 a[3], a[2] (笼统的表示,其实是他们指向对象的引用)
    • 36ROT_TWO,交换TOS,TOS1元素。 栈内为 a[2] , a[3]。 a[3] 为栈顶元素。
    • 下面重点来了,导致错误的原因,就是下面的字节码!!! 也是我个人的推敲不一定准确:
    • 38-42: 开始执行 = 左面的,处理a[2]
    			 38 LOAD_FAST                0 (a)   # 这两条加载 a, 2 进栈
                 40 LOAD_CONST               2 (2)
                 42 STORE_SUBSCR
                 # 此时的栈内是 a[2], a[3], a, 2
                 # 执行 42 :  a[2] = a[3], 这句话导致 a[2] 对应指向的对象变为了 a[3] 所指向的 也就是4.
    

    在这里插入图片描述

    • 我理解的是,STORE_SUBSCR 取出三个操作数之后,是不放回去的(理解不太对,光放文档也有提到二元操作,放回)此时栈 只剩下一个 a[2] 了,a[2] 是指向对象的引用,也就是 3的引用。
      44-52:
    			 44 LOAD_FAST                0 (a)
                 46 LOAD_FAST                0 (a)
                 48 LOAD_CONST               2 (2)
                 50 BINARY_SUBSCR
                 52 STORE_SUBSCR
    			# 执行起来就是  a[a[2]] = a[2]
    			# 迷不过来的地方,我理解的是,前面的字节码操作,已经使 a[2]指向的对象变成了 4.
    			# 这里就变成了 a[4] = a[2]  而 = 右面的 a[2] 是栈底的,代表原来 3对象的指向
    			# 这样就使得 a[4] 所对应的指向 变为了 3. 
    			# 这里的 STORE_SUBSCR 和 BINARY_SUBSCR 操作的区别感觉就是。STORE_SUBSCR,类似于STORE_NAME, a[4] 就相当于是一个变量名, 然后进行 STORE_NAME操作。 右面的 a[2] 就是一个具体的 3对象的指针。
    
    • 因为不会画图(不知道怎么表示列表),所以上述的表述 都用的 a[] 来表示,但是意义不同。比较乱,全是我的猜想,不一定很正确。
    • 通过上面的两步,就能解释了, 第一个 STORE_SUBSCR 使 a[2] 变成 4, 第二个 STORE_SUBSCR 使a[4] 变为 3.
因为这些知识点设及的东西很多,比如书python解释性慢在哪里? 什么时候给对象判断类型,分配内存空间,还有栈,堆空间的理解都是不够的,网上资料不多,并且内容错综复杂。经过很多查阅, 算是对 对象管理,字节码执行,字节码对象,栈帧对象有个更深的认识,但是还是很片面,以上所述,仅供参考~ 未来 有新的理解和认识,再来修改。
<think>好的,用户想理解Python中a, b = b, a的变量交换语法和工作原理。首先,我需要回忆一下Python的赋值机制。这个语法看起来像是元组解包,对吧?我记得Python在处理这种赋值时,实际上会先创建一个元组,然后解包赋值给左边的变量。所以右边b, a会被打包成一个元组,然后左边的a和b分别接收元组中的值。这一步可能涉及到栈的操作,或者说是临时对象的创建? 然后,用户可能想知道这个过程中的底层原理。比如,Python解释器在执行这行代码时具体做了什么操作。根据引用中的资料,比如引用2和3提到元组解包的特性,这可能涉及到先将右边的值计算并存储,然后按顺序赋值给左边。也就是说,右边的表达式会被完全求值后再进行赋值,这样就不会出现中间变量的问题。 接下来,需要考虑使用场景。这种语法通常用于交换变量,简洁高效。比如在排序算法中交换元素,或者在多返回值函数中解包。用户可能想知道为什么这种方法比其他语言中的交换更简洁,或者是否有其他应用场景,比如函数返回多个值时的解包操作。 另外,用户可能还会关心这种交换方式是否有性能上的优势。由于不需要中间变量,代码更简洁,执行效率可能更高,因为避免了额外的变量创建和内存操作。不过具体性能差异可能不大,但代码可读性更好。 还要注意可能的误区,比如在复杂表达式中的使用是否安全。比如,如果右侧的表达式有副作用,是否会影响结果。但根据Python的执行顺序,右侧会先被完全求值,所以应该不会有问题。例如,如果右侧是函数调用,函数执行后的结果才会被赋值给左边。 最后,总结这个机制的优势和使用场景,确保用户理解其背后的原理和适用情况。可能需要对比其他语言的方法,比如使用临时变量或者异或操作,来突出Python这种方法的简洁性。</think>### Python变量交换语法`a,b = b,a`的工作原理 #### 1. **语法特性与执行步骤** Python的`a,b = b,a`语句本质利用了**元组打包与解包**机制: - **步骤1**:右侧表达式`b,a`会先被计算,形成一个临时元组`(b, a)` - **步骤2**:左侧变量`a`和`b`按顺序从元组中解包赋值 - **执行过程**等价于: ```python temp_tuple = (b, a) # 打包操作 a = temp_tuple[0] # 解包赋值 b = temp_tuple[1] ``` #### 2. **底层实现原理** - **栈操作机制**:Python解释器通过操作数栈(operand stack)实现该语法。右侧表达式`b,a`会被压入栈顶,然后依次弹出并赋值给左侧变量[^3] - **原子性保证**:整个赋值过程是原子操作,避免多线程环境下出现中间状态 #### 3. **性能优势** - **无临时变量**:相比传统三变量交换法(需中间变量`temp`),内存占用更低 - **字节码优化**:Python编译器会生成`ROT_TWO`或`ROT_THREE`指令(适用于更复杂交换),直接交换栈顶元素,效率更高 #### 4. **典型使用场景** | 场景 | 示例 | 优势 | |------|------|------| | 数值交换 | 快速排序中的元素位置交换 | 代码简洁,执行高效 | | 函数多返回值 | `x, y = get_coordinates()` | 直观解包赋值 | | 循环迭代 | `for (key, val) in dict.items():` | 结构化数据提取 | | 状态切换 | `visible, hidden = hidden, visible` | 避免中间状态 | #### 5. **对比其他语言实现** ```python # Python a, b = b, a # C语言(需中间变量) int temp = a; a = b; b = temp; # Go语言(同样支持元组解包) a, b = b, a # JavaScript(解构赋值) [a, b] = [b, a] ``` #### 6. **注意事项** - **复杂表达式**:当右侧包含函数调用时,如`a,b = func1(), func2()`,会先执行所有函数调用再赋值 - **变量覆盖**:避免在链式赋值中出现变量覆盖,例如: ```python a = 1 b = 2 a, b = b, a + b # 正确结果:a=2, b=3 ``` ### 扩展应用 在**多维数据交换**中,该语法可扩展为: ```python # 三维坐标交换 x, y, z = z, x, y # 字典键值反转 d = {'a':1, 'b':2} d = {v:k for k,v in d.items()} # 输出{1:'a', 2:'b'} ```
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值