首先引入问题:
- 力扣448题: 这是我的一种解法:
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)字节码这里就不去细说了,非常多东西。推荐:
-
慕课专栏:《Python 源码深度剖析》
- 具体查看第四章 虚拟机讲解。 内容精炼,易理解,记得评论也要看。
-
简书上找到的文章:CPython实现原理:字节码的执行
- 建议连续的几章都看看,内容很深。
做出分析解释:
-
下面正式对上述问题做出解释分析,直接去讲这个问题的推理, 不在记录知识点,因为前面推荐的文章和专栏已经非常精炼,全面了。
-
(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_SUBTRACT 和 STORE_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] (笼统的表示,其实是他们指向对象的引用)
- 36 : ROT_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.