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

首先引入问题:

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解释性慢在哪里? 什么时候给对象判断类型,分配内存空间,还有栈,堆空间的理解都是不够的,网上资料不多,并且内容错综复杂。经过很多查阅, 算是对 对象管理,字节码执行,字节码对象,栈帧对象有个更深的认识,但是还是很片面,以上所述,仅供参考~ 未来 有新的理解和认识,再来修改。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值