python

python是一门解释性的语言

源代码—>中间代码—>机器语言

编译器先将源代码编译成虚拟机指令,再由解释器对中间代码进行解释。

python有两种常见的解释器:

CPython:C语言开发,使用最广,默认的解释器

PyPy:采用JIT技术,对python代码进行动态编译,追求执行速度

Python 字节码与字节码混淆 - 简书 (jianshu.com)关于python字节码的知识

深入理解python之Opcode备忘录 - 简书 (jianshu.com)这个博客里有关于字节码指令的详细解释

pyc文件

pyc是编译py后生成的字节码文件,可以用来隐藏源代码。pyc内容跟python

版本有关。

不同版本的python可能运行不了同一个pyc文件。

1
2
python -m py_compile test.py
#反编译pyc文件

学习python字节码学习搭配chatGPT,能提高学习效率

CPython使用基于堆栈的虚拟机

在执行python文件时候,第一步: python解释器会将你写的python代码先编译为字节码
第二步: 当你每一次调用函数,或者刚开始运行python的时候,cpython会建立一个新的Frame,然后在这个Frame框架下,cpython会一条一条的执行编译后的ByteCode, 每一条ByteCode在C语言中有相应的代码去执行它。
另外,在每一个Frame里, cpython都会维护一个stack,然后ByteCode会和这个Stack进行交互操作。

python字节码官方文档dis —- Python 字节码反汇编器 — Python 3.12.0 文档

变量指令解析

1
源码行号  |  指令偏移量  | 指令符号 | 指令参数  |  实际参数值

一下解析均来自chatGPT

FAST

LOAD_FAST

LOAD_FAST 是 Python 字节码的一种指令,用于加载局部变量的值。具体来说,它从当前函数的局部变量数组中加载指定名称的变量的值,并将其推送到堆栈上。

以下是关于 LOAD_FAST 指令的一些细节:

  • 指令名称: LOAD_FAST
  • Opcode: 0x7C
  • 参数: 一个整数索引,表示局部变量在局部变量数组中的位置

LOAD_FAST 的作用是加载函数的局部变量的值。局部变量是在函数中定义的变量,只在函数的范围内可见。

GLOBAL

LOAD_GLOBAL

LOAD_GLOBAL 是 Python 字节码的一种指令,用于加载全局变量的值。具体来说,它从当前模块的全局命名空间中加载指定名称的变量的值,并将其推送到堆栈上。

以下是关于 LOAD_GLOBAL 指令的一些细节:

  • 指令名称: LOAD_GLOBAL
  • Opcode: 0x74
  • 参数: 一个整数索引,表示在常量池中的全局变量名称的位置

LOAD_GLOBAL 的作用是加载全局变量的值。全局变量是在模块级别定义的变量,可以在整个模块内被访问。

STORE_NAME

STORE_NAME 是 Python 字节码的一种指令,用于将值存储到局部变量的名称中。具体来说,它从堆栈中弹出一个值,然后将该值存储到当前作用域中具有指定名称的变量中。

以下是关于 STORE_NAME 指令的一些细节:

  • 指令名称: STORE_NAME
  • Opcode: 0x5
  • 参数: 一个表示变量名称的字符串(变量名)

STORE_NAME 的作用是将堆栈顶部的值存储到局部变量中,变量的名称由参数指定。这通常是在赋值操作时使用的指令。

LIST

BUILD_LIST

BUILD_LIST 是 Python 字节码的一种指令,用于创建一个列表对象。具体来说,它从堆栈上弹出指定数量的元素,然后创建一个新的列表并将这些元素放入列表中,最后将列表对象推送回堆栈。

以下是关于 BUILD_LIST 指令的一些细节:

  • 指令名称: BUILD_LIST
  • Opcode: 0x14
  • 参数: 一个整数,表示要从堆栈中弹出的元素数量

BUILD_LIST 的作用是根据堆栈上的元素创建一个新的列表对象。

dict

BUILD_MAP

BUILD_MAP 是 Python 字节码的一种指令,用于创建一个字典对象。具体来说,它从堆栈上弹出两倍于指定元素数量的元素,然后创建一个新的字典并将这些元素添加到字典中,最后将字典对象推送回堆栈。

以下是关于 BUILD_MAP 指令的一些细节:

  • 指令名称: BUILD_MAP
  • Opcode: 0x15
  • 参数: 一个整数,表示要从堆栈中弹出的键值对数量(通常是键值对的数量的两倍)

BUILD_MAP 的作用是根据堆栈上的键值对创建一个新的字典对象。

LOOP

SETUP_LOOP

SETUP_LOOP 是 Python 字节码的一种指令,用于在循环开始的位置设置循环的头部。它指定了循环体的开始位置.

以下是关于 SETUP_LOOP 指令的一些细节:

  • 指令名称: SETUP_LOOP
  • Opcode: 0x13
  • 参数: 两个字节的有符号偏移量(表示循环体的结束位置)

SETUP_LOOP 的作用是标记循环的开始位置,并在堆栈上推送一个块堆栈(block stack)记录循环的开始位置和结束位置。当循环体结束时,POP_BLOCK 指令将从块堆栈中弹出该记录。

示例:

1
2
for i in range(5):
print(i)

对应的字节码可能包含:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0 SETUP_LOOP              28  # 设置循环的头部,指定循环体的结束位置
2 LOAD_NAME 0 (range)
4 LOAD_CONST 0 (5)
6 CALL_FUNCTION 1
8 GET_ITER
>> 10 FOR_ITER 16 # 循环体的开始
12 STORE_NAME 1 (i)
14 LOAD_NAME 2 (print)
16 LOAD_NAME 1 (i)
18 CALL_FUNCTION 1
20 POP_TOP
22 JUMP_ABSOLUTE 10 # 跳转到循环体的开始位置
>> 24 POP_BLOCK # 循环体的结束
26 LOAD_CONST 1 (None)
28 RETURN_VALUE

在这个例子中,SETUP_LOOP 用于设置循环的头部,POP_BLOCK 将在循环体结束时弹出块堆栈。循环体的开始和结束位置由 FOR_ITERPOP_BLOCK 来标记。

POP_BLOCK

POP_BLOCK 是 Python 字节码指令之一,用于在异常处理中管理块的结束。具体来说,它用于弹出当前块的信息,以便程序继续正常执行而不是执行异常处理。在 CPython 的实现中,这通常与异常处理语句(try, except, finally)一起使用。

在 Python 字节码中,POP_BLOCK 通常出现在异常处理块的末尾,它会将与该块相关的信息从块堆栈中弹出。

例如,下面是一个简单的 Python 代码片段和对应的字节码:

Python 代码片段:

1
2
3
4
5
6
7
8
9
10
11
12
pythonCopy codetry:
# 一些可能引发异常的代码
x = 1 / 0
except ZeroDivisionError:
# 处理 ZeroDivisionError 异常
print("Division by zero!")
finally:
# 无论是否发生异常,都会执行的代码块
print("Finally block!")

# 此处的代码将继续执行
print("After the try-except-finally block.")

对应的字节码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
scssCopy code  1           0 SETUP_EXCEPT            16 (to 19)

2 3 LOAD_CONST 0 (1)
6 LOAD_CONST 1 (0)
9 BINARY_TRUE_DIVIDE
10 STORE_NAME 0 (x)
13 POP_BLOCK
14 POP_EXCEPT
15 JUMP_FORWARD 7 (to 25)

3 >> 18 POP_BLOCK

4 >> 19 SETUP_FINALLY 14 (to 36)

5 22 LOAD_NAME 1 (print)
25 LOAD_CONST 2 ('Division by zero!')
28 CALL_FUNCTION 1
31 POP_BLOCK
32 LOAD_CONST 3 (None)
35 POP_EXCEPT
36 LOAD_NAME 1 (print)
39 LOAD_CONST 4 ('Finally block!')
42 CALL_FUNCTION 1
45 END_FINALLY

6 46 LOAD_NAME 1 (print)
49 LOAD_CONST 5 ('After the try-except-finally block.')
52 CALL_FUNCTION 1
55 POP_TOP
56 LOAD_CONST 3 (None)
59 RETURN_VALUE

在上述字节码中,POP_BLOCK 出现在 POP_EXCEPTEND_FINALLY 之后,用于清理块堆栈。这样,程序可以继续执行正常的代码,而不是进入异常处理块。

BINARY_SUBSCR

BINARY_SUBSCR 是 Python 字节码的一种指令,它用于在可迭代对象(如列表、元组、字典等)上执行下标(索引)操作。具体来说,它从堆栈中弹出两个值,一个是容器对象,另一个是索引值,然后将容器对象中指定索引的元素推送回堆栈。

下面是关于 BINARY_SUBSCR 指令的一些细节:

  • 指令名称: BINARY_SUBSCR
  • Opcode: 0x25
  • 参数:
  • 作用: 从容器对象中获取指定索引的元素,并将其推送到堆栈上。
STORE_SUBSCR

STORE_SUBSCR 是 Python 字节码的一种指令,用于在容器对象(如列表、字典等)中的指定索引位置存储一个值。具体来说,它从堆栈中弹出三个值:容器对象、索引值和要存储的值,然后将值存储到容器对象的指定索引位置。

以下是关于 STORE_SUBSCR 指令的一些细节:

  • 指令名称: STORE_SUBSCR
  • Opcode: 0x3A
  • 参数:
  • 作用: 从堆栈中弹出容器对象、索引值和值,将值存储到容器对象的指定索引位置。

这个指令通常在对列表、字典等进行索引赋值操作时使用。例如,如果有一个列表 my_list,栈顶有一个整数表示索引,而下面的两个值分别是容器对象和要存储的值,STORE_SUBSCR 将在列表的指定索引位置存储新的值。

跳转指令

POP_JUMP_IF_FALSE

POP_JUMP_IF_FALSE 是 Python 字节码的一种条件跳转指令。它用于在堆栈上弹出一个值,并在该值为假(False)时执行跳转。如果弹出的值为假,指令将跳转到指定的目标地址,否则继续执行下一条指令。

以下是关于 POP_JUMP_IF_FALSE 指令的一些细节:

  • 指令名称: POP_JUMP_IF_FALSE
  • Opcode: 0x72
  • 参数: 两个字节的有符号偏移量(表示跳转目标的相对位置)

POP_JUMP_IF_FALSE 的作用是从堆栈中弹出一个值,如果该值为假(False),则执行跳转到指定的目标地址。否则,继续执行下一条指令。

示例:

1
2
3
4
5
6
x = False

if not x:
print("x is False")
else:
print("x is True")

上述代码的字节码可能包含类似下面的部分:

1
2
3
4
5
6
7
8
9
10
11
2           0 LOAD_GLOBAL              0 (x)
2 POP_JUMP_IF_FALSE 12 # 如果 x 为 False,则跳转到指令地址 12
4 LOAD_CONST 1 ('x is True')
6 PRINT_ITEM
8 JUMP_FORWARD 8 # 跳转到指令地址 18,绕过 else 语句块
>> 10 POP_BLOCK
12 LOAD_CONST 2 ('x is False')
14 PRINT_ITEM
16 JUMP_FORWARD 2 # 跳转到指令地址 18,绕过 else 语句块
>> 18 LOAD_CONST 0 (None)
20 RETURN_VALUE

在这个例子中,POP_JUMP_IF_FALSE 指令用于检查变量 x 的真假,并根据结果选择性地执行不同的分支。

JUMP_FORWARD

JUMP_FORWARD 是 Python 字节码的一种无条件跳转指令。它用于在执行过程中无条件地跳转到指定的相对位置

以下是关于 JUMP_FORWARD 指令的一些细节:

  • 指令名称: JUMP_FORWARD
  • Opcode: 0x28
  • 参数: 两个字节的有符号偏移量(表示跳转的相对位置)

JUMP_FORWARD 的作用是使程序跳转到当前位置之后的指定位置。它通常用于实现循环、条件语句等结构,其中需要在不同条件下跳转到不同的代码块。

示例:

1
2
3
4
5
6
7
8
x = True

if x:
print("x is True")
else:
print("x is False")

print("End of program")

上述代码的字节码可能包含类似下面的部分:

1
2
3
4
5
6
7
8
9
10
11
2           0 LOAD_GLOBAL              0 (x)
2 POP_JUMP_IF_FALSE 12 # 如果 x 为 False,则跳转到指令地址 12
4 LOAD_CONST 1 ('x is True')
6 PRINT_ITEM
8 JUMP_FORWARD 12 # 无条件跳转到指令地址 20,绕过 else 语句块
>> 10 POP_BLOCK
12 LOAD_CONST 2 ('x is False')
14 PRINT_ITEM
16 JUMP_FORWARD 2 # 无条件跳转到指令地址 20,绕过 else 语句块
>> 18 LOAD_CONST 0 (None)
20 RETURN_VALUE

在这个例子中,JUMP_FORWARD 用于在条件判断为真时跳过 else 语句块。在字节码中,JUMP_FORWARD 的目标是跳过指定数量的字节码指令,从而实现程序的流程控制。

JUMP_ABSOLUTE

JUMP_ABSOLUTE 是 Python 字节码的一种无条件跳转指令。它用于无条件地跳转到指定的绝对位置

以下是关于 JUMP_ABSOLUTE 指令的一些细节:

  • 指令名称: JUMP_ABSOLUTE
  • Opcode: 0x71
  • 参数: 两个字节的有符号偏移量(表示跳转的绝对位置)

JUMP_ABSOLUTE 的作用是使程序跳转到指定的绝对位置。这个位置通常是由 bytecode 的相对位置计算得到的,而不是 Python 源代码中的行号。

示例:

1
2
3
4
5
6
x = 5

if x > 0:
print("Positive")
else:
print("Non-positive")

对应的字节码可能包含类似下面的部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2           0 LOAD_NAME                0 (x)
2 LOAD_CONST 1 (0)
4 COMPARE_OP 4 (>)
6 JUMP_ABSOLUTE 12 # 如果 x > 0,则跳转到指令地址 12
8 POP_TOP
10 JUMP_FORWARD 8 # 跳转到指令地址 18,绕过 else 语句块
>> 12 POP_TOP
14 LOAD_CONST 2 ('Positive')
16 PRINT_ITEM
18 JUMP_FORWARD 8 # 跳转到指令地址 26,绕过 else 语句块
>> 20 POP_TOP
22 LOAD_CONST 3 ('Non-positive')
24 PRINT_ITEM
>> 26 LOAD_CONST 0 (None)
28 RETURN_VALUE

在这个例子中,JUMP_ABSOLUTE 指令用于在条件判断为真时跳转到指定的位置。在字节码中,JUMP_ABSOLUTE 的目标是跳转到指定的绝对位置,绕过相应的代码块。

运算指令

  1. BINARY_ADD: 从堆栈中弹出两个值,相加,并将结果推回堆栈。
  2. BINARY_SUBTRACT: 从堆栈中弹出两个值,相减,并将结果推回堆栈。
  3. BINARY_MULTIPLY: 从堆栈中弹出两个值,相乘,并将结果推回堆栈。
  4. BINARY_DIVIDE: 从堆栈中弹出两个值,相除,并将结果推回堆栈。
  5. BINARY_MODULO: 从堆栈中弹出两个值,取模,并将结果推回堆栈。
  6. BINARY_POWER: 从堆栈中弹出两个值,计算幂,并将结果推回堆栈。
  7. BINARY_LSHIFT: 将堆栈顶部的值左移指定位数,并将结果推回堆栈。
  8. BINARY_RSHIFT: 将堆栈顶部的值右移指定位数,并将结果推回堆栈。
  9. BINARY_AND: 从堆栈中弹出两个值,进行按位与操作,并将结果推回堆栈。
  10. BINARY_OR: 从堆栈中弹出两个值,进行按位或操作,并将结果推回堆栈。
  11. BINARY_XOR: 从堆栈中弹出两个值,进行按位异或操作,并将结果推回堆栈。
  12. INPLACE_ADD: 从堆栈中弹出两个值,相加,并将结果就地存储回栈顶的位置。
  13. INPLACE_SUBTRACT: 从堆栈中弹出两个值,相减,并将结果就地存储回栈顶的位置。
  14. INPLACE_MULTIPLY: 从堆栈中弹出两个值,相乘,并将结果就地存储回栈顶的位置。
  15. INPLACE_DIVIDE: 从堆栈中弹出两个值,相除,并将结果就地存储回栈顶的位置。

比较指令COMPARE_OP

  • COMPARE_OP 是 Python 字节码的一种指令,用于执行比较操作。它弹出两个值,执行指定的比较操作,然后将比较结果的布尔值推送回堆栈。

    以下是关于 COMPARE_OP 指令的一些细节:

    • 指令名称: COMPARE_OP
    • Opcode: 0x6E
    • 参数: 一个表示比较操作类型的整数码(具体的操作由该参数确定)

    COMPARE_OP 的作用是执行两个值之间的比较操作,比较操作的类型由参数确定。这可以是诸如等于、不等于、小于、大于等操作。

    比较操作的参数码和对应的比较操作如下:

    • 0: < (小于)
    • 1: <= (小于等于)
    • 2: == (等于)
    • 3: != (不等于)
    • 4: > (大于)
    • 5: >= (大于等于)
    • 6: in (成员测试)
    • 7: not in (不是成员测试)
    • 8: is (同一对象测试)
    • 9: is not (不是同一对象测试)
    • 10: exception match (异常匹配)
    • 11: BAD (无效的比较)

    示例:

    1
    2
    3
    4
    # 比较操作: a < b
    a = 5
    b = 10
    result = a < b

    对应的字节码可能包含:

    1
    2
    3
    0 LOAD_CONST               1 (5)
    2 LOAD_CONST 2 (10)
    4 COMPARE_OP 0 (<) # 执行 a < b 的比较操作

    在这个例子中,COMPARE_OP 指令执行了小于(<)的比较操作,将结果推送回堆栈。

用户输入

在 Python 2 中,使用 raw_input 函数来接收用户的输入。raw_input 会将用户输入的内容作为字符串返回,不进行任何解析或转换。

从 Python 3 开始,raw_input 被移除,而 input 函数则获得了与 Python 2 中的 raw_input 类似的行为,即将用户输入的内容作为字符串返回。

image-20231115174715910

image-20231115174729372

yield关键字

首先了解一下python迭代器

python迭代器

迭代器有两个重要函数
iter() 用于创建一个迭代器(所有的序列都可以转化为迭代器)。
next() 可以返回迭代器的下一元素。若迭代器数据耗尽时仍然调用next()函数,则会引发StopIteration异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import sys

list = [1,1,4,5,1,4]

it = iter(list)

while True:

try:

print (next(it),end='')

except StopIteration:

​ sys.exit()


image-20231119151901373

Python3 迭代器与生成器 | 菜鸟教程 (runoob.com)

yield

在 Python 中,使用了 yield 的函数被称为生成器(generator)。

生成器是一个返回迭代器的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

import sys

def fibonacci(n): # 生成器函数 - 斐波那契
a, b, counter = 0, 1, 0
while True:
if (counter > n):
return
yield a
a, b = b, a + b #b赋值给a,a+b赋值给b,可以不使用中间变量,简化了编程。
counter += 1
f = fibonacci(10) # f 是一个迭代器,由生成器返回生成

while True:
try:
print (next(f), end=" ")
except StopIteration:
sys.exit()

生成器函数的优势是它们可以按需生成值,避免一次性生成大量数据并占用大量内存。此外,生成器还可以与其他迭代工具(如for循环)无缝配合使用,提供简洁和高效的迭代方式。