您现在的位置是:主页 > news > 北京网站托管公司/游戏推广员每天做什么
北京网站托管公司/游戏推广员每天做什么
admin2025/5/9 8:32:09【news】
简介北京网站托管公司,游戏推广员每天做什么,网站建设招聘,吉安做网站优化pPython是一门解释型语言,边解释边执行(效率比C低,暴力脚本经常炸),通常不会进行整体地编译和链接,使用专门的解释器逐行编译解释成特定字节码,其工作流程如下: 1. 将源代码编译转换为字节码2. 解释器执行字…
pPython是一门解释型语言,边解释边执行(效率比C低,暴力脚本经常炸),通常不会进行整体地编译和链接,使用专门的解释器逐行编译解释成特定字节码,其工作流程如下:
1. 将源代码编译转换为字节码
2. 解释器执行字节码(是不是像虚拟机?)
01pyc文件的生成pyc 文件是 py 文件编译后生成的字节码文件,在 __pycache__ 目录中。
其目的是提高加载速度:运行时会检查字节码文件修改时间是否与源代码一致,一致则编译步骤将会被跳过,解释器直接加载 pyc 文件;否则编译保存新生成的字节码。
一般以 module 形式加载时,会生成 pyc 文件。
pyc 文件生成方式有很多,如下列举了几种:
1命令python -m test
2代码1 生成单个 pyc 文件
py_compile 编译
import py_compile py_compile.compile(r'path/test.py')
load_module 间接加载
import impdef generate_pyc(name): fp, pathname, description = imp.find_module(name) try: imp.load_module(name, fp, pathname, description) finally: if fp: fp.close()if __name__ == '__main__': generate_pyc(input())
2 批量生成 pyc 文件
import compileall compileall.compile_dir(r'path')
02pyc文件格式解析pyc文件一般由三个部分组成:
最开始 4 个字节是一个 Maigc int,标识此 pyc 的版本信息,不同的版本的 Magic 都在 python/import.c 内定义
接下来 4 个字节还是个 int,是 pyc 产生的时间(TIMESTAMP, 1970.01.01 到产生 pyc 时候的秒数)
接下来是个序列化了的 PyCodeObject,此结构在 Include/code.h 内定义,序列化方法在 python/marshal.c 内定义
康一个实例(Python2.7,010editor template 用官方 PYC.bt)
print(2333)
struct Magic magic 0A0DF303h char mtime[4] 5E6E3AB6h TYPE_CODE (63h) int co_argcount 0h int co_nlocals 0h int co_stacksize 1h int co_flags 40h struct PyObject code enum ObjType type TYPE_STRING (73h) int n 9h 64 00 00 47 48 64 01 00 struct Instruction inst[0] LOAD_CONST 0 opcode 64h oparg 0h struct Instruction inst[1] PRINT_ITEM opcode 47h struct Instruction inst[2] PRINT_NEWLINE opcode 48h struct Instruction inst[3] LOAD_CONST 1 opcode 64h oparg 1h struct Instruction inst[4] RETURN_VALUE opcode 53h struct PyObject co_consts enum ObjType type TYPE_TUPLE (28h) int n 2h TYPE_INT (69h) 91Dh(2333) TYPE_NONE (4Eh) struct PyObject co_names struct PyObject co_varnames struct PyObject co_freevars struct PyObject co_cellvars struct PyObject co_filename enum ObjType type TYPE_STRING (73h) int n 9h ./test.py struct PyObject co_name enum ObjType type TYPE_INTERNED (74h) int n 8h int co_firstlineno 1h struct PyObject co_lnotab
每个作用域(block)对应一个 PyCodeObject 对象,在 Python 源码目录下 Include/code.h 文件 中,可以看到 PyCodeObject 的定义如下:
/* Bytecode object */ typedef struct { PyObject_HEAD int co_argcount; /* #arguments, except *args */ int co_nlocals; /* #local variables */ int co_stacksize; /* #entries needed for evaluation stack */ int co_flags; /* CO_..., see below */ PyObject *co_code; /* instruction opcodes | code对应的字节码 */ PyObject *co_consts; /* list (constants used) | 所有常量组成的tuple */ PyObject *co_names; /* list of strings (names used) | code所用的到符 号表,tuple类型,元素是字符串 */ PyObject *co_varnames; /* tuple of strings (local variable names) | code所用到的局部变量名,tuple类型,元素是PyStringObject('s/t/R') */ PyObject *co_freevars; /* tuple of strings (free variable names) */ PyObject *co_cellvars; /* tuple of strings (cell variable names) */ PyObject *co_filename; /* unicode (where it was loaded from) */ PyObject *co_name; /* unicode (name, for reference) */ PyObject *co_lnotab; /* string (encoding addrlineno mapping) See Objects/lnotab_notes.txt for details. */ } PyCodeObject;
03Python字节码“反汇编”分析Python 提供 dis 模块,为 Python 字节码提供 “反汇编”,通过 dis.dis() 或 dis.disassemble() 可获得字节码的可阅读理解版本,可以看作虚拟机 opcode。
通过使用 dis.dis() 可以对代码实现进行比较,像是“为什么 {} 比 dict() 更快”的问题,能够更加 直接得到解答。
来看一个简单的例子:(我后悔了,我为什么要写这么长的例子。
import dis def test(): a = 'hello' b = 2 c = 3782 d = a + str(b + c) print(d) if __name__ == '__main__': test() dis.dis(test)
通过 dis 得到能分析的 opcode,第一列是源代码行数,第二列是字节偏移,第三列是命令,第四列是命令参数。
>Python3 test.py hello3784 5 0 LOAD_CONST 1 ('hello') # 将co_consts[1] 即'hello'字符串压栈 2 STORE_FAST 0 (a) # 将栈顶TOS存放到 co_varnames[0]即变量a 6 4 LOAD_CONST 2 (2) # 将co_consts[2]即数 值2压栈 6 STORE_FAST 1 (b) # 将TOS存放到 co_varnames[1]即变量b 7 8 LOAD_CONST 3 (3782) # 将co_consts[3]即数 值3782压栈 10 STORE_FAST 2 (c) # 将TOS存放到 co_varnames[2]即变量c 8 12 LOAD_FAST 0 (a) # 将co_varnames[0]即 变量a压栈 14 LOAD_GLOBAL 0 (str) # 将co_names[0]即str 压栈 16 LOAD_FAST 1 (b) # 将co_varnames[1]即 变量b压栈 18 LOAD_FAST 2 (c) # 将co_varnames[2]即 变量c压栈 20 BINARY_ADD # 将栈顶的变量c和变量b 弹出,并做相加运算,将结果压栈 22 CALL_FUNCTION 1 # 调用参数数量为1个的函 数,即str(b+c) 24 BINARY_ADD # 弹出栈顶的字符串a和 temp=str(b+c),连接,将结果压栈 26 STORE_FAST 3 (d) # 将TOS存放到 co_varnames[3]即变量d 9 28 LOAD_GLOBAL 1 (print) # 将co_names[1]即 print压栈 30 LOAD_FAST 3 (d) # 将co_varnames[3]即 变量d压栈 32 CALL_FUNCTION 1 34 POP_TOP # 弹栈顶 36 LOAD_CONST 0 (None) # 将co_consts[0]即空 对象None压栈 38 RETURN_VALUE # 返回TOS到调用者,返回 空
Python 有着基于栈的运行机制。
CPython 使用三种类型的栈:
1. 调用栈(call stack),为每个当前活动的函数调用使用了帧(frame)。栈底是程序的入口点,每个函 数调用会推送一个新的帧到调用栈,函数调用返回后帧被销毁。
2. 在每个函数/帧中,对应有一个 计算栈(evaluation stack) (也称为 数据栈(data stack)),大多 数指令操作在计算栈中进行,如例子。
3. 在每个函数/帧中,对应有一个 块栈(block stack),被用于跟踪某些类型的控制结构:循环、 try / except 块、以及 with 块,进入这些控制结构时会被推入 Block 到块栈中。有助于了解 任意给定时刻的活动块,After all,一个 continue 或者 break 语句可能影响正确的块。
部分指令操作1 一般指令
POP_TOP :删除堆栈顶部(TOS)项。
ROT_TWO :交换两个最顶层的堆栈项。
DUP_TOP :复制堆栈顶部的引用。
2 Unary 一元操作
弹栈运算后压栈, UNARY_NEGATIVE 、 UNARY_NOT 、 UNARY_INVERT 、 GET_ITER(实现 TOS = iter(TOS) )
3 Binary 二元操作
弹出栈顶两个值运算后压栈, BINARY_POWER 、 BINARY_MULTIPLY 、 BINARY_MATRIX_MULTIPLY 、 BINARY_FLOOR_DIVIDE 、 BINARY_TRUE_DIVIDE 、 BINARY_MODULO 、 BINARY_ADD 、 BINARY_SUBTRACT 、 BINARY_SUBSCR (实现 TOS=TOS1[TOS] ,TOP1 为栈中第二 顶)、 BINARY_LSHIFT 、 BINARY_RSHIFT 、 BINARY_AND 、 BINARY_XOR 、 BINARY_OR
4 Inplace 就地二元操作
5 Load 压栈操作(相当于 push)
LOAD_CONST (consti):将 co_consts[consti] 的常量推入栈顶。
LOAD_GLOBAL (namei):加载名称为 co_names[namei] 的全局对象推入栈顶。
LOAD_FAST (var_num):将指向局部对象co_varnames[var_num] 的引用推入栈顶。
6 Store 弹栈赋值操作(相当于 top+pop)(对应 Load 种类)
STORE_FAST (var_num):将 TOS 存放到局部变量 co_varnames[var_num] 。
7 判断跳转循环
COMPARE_OP (opname):执行布尔运算操作。操作名称可在 cmp_op[opname] 中找到。相当于汇 编中的比较指令。
POP_JUMP_IF_TRUE(FALSE) (target):如果 TOS 为真/假值,则将字节码计数器的值设为 target。TOS 会被弹出。 JUMP_IF_TRUE(FALSE)_OR_POP (target):如果 TOS 为真/假值,则将字节码计数器的值设为 target 并将 TOS 留在栈顶。否则(如 TOS 为假/真值),TOS 会被弹出。
JUMP_ABSOLUTE (target):将字节码计数器的值设为 target。 JUMP_FORWARD (delta):将字节码计数器的值增加 delta。 FOR_ITER (delta):TOS 是一个 iterator。可调用它的 __next__() 方法。如果产生了一个新值, 则将其推入栈顶(将迭代器留在其下方)。如果迭代器提示已耗尽则 TOS 会被弹出,并将字节码 计数器的值增加 delta。
SETUP_LOOP (delta):将要循环的代码块压入堆栈。该代码块从当前指令开始扩展,大小为 delta 字节。
BREAK_LOOP
CONTINUE_LOOP (target)
8 函数调用
CALL_FUNCTION (argc):调用一个可调用对象并传入位置参数。argc 指明位置参数的数量。栈顶 包含位置参数,其中最右边的参数在最顶端。在参数之下是一个待调用的可调用对象。
CALL_FUNCTION 会从栈中弹出所有参数以及可调用对象,附带这些参数调用该可调用对象,并将 可调用对象所返回的返回值推入栈顶。在 3.6 版更改: 此操作码仅用于附带位置参数的调用。
RETURN_VALUE :返回 TOS 到函数的调用者。
列出来不够直观,再康几个例子。
1循环1.for 循环
def test(): for i in range(0x10): pass
5 0 SETUP_LOOP 16 (to 18) # 循环开始 2 LOAD_GLOBAL 0 (range) 4 LOAD_CONST 1 (16) 6 CALL_FUNCTION 1 # 函数调用,range(16) 8 GET_ITER # TOS=iter(TOS),即将 指向栈顶的指针压栈 >> 10 FOR_ITER 4 (to 16) # 不断调用即可遍历 iterator(range(16)),将每次产生的新值放于栈顶,迭代器上方,迭代器耗尽时弹出并修改字节码计 数器增加4,即跳到12(字节码计数器当前值)+4=16POP_BLOCK 12 STORE_FAST 0 (i) 6 14 JUMP_ABSOLUTE 10 # 简言之,蹦到10 >> 16 POP_BLOCK # 弹出循环Block >> 18 LOAD_CONST 0 (None) # 函数结束部分 20 RETURN_VALUE
2.while 循环
def test(): i = 0 while i < 0x16: i += 1
5 0 LOAD_CONST 1 (0) 2 STORE_FAST 0 (i) 6 4 SETUP_LOOP 20 (to 26) # 循环开始,循环Block压 栈,大小为20 >> 6 LOAD_FAST 0 (i) 8 LOAD_CONST 2 (22) 10 COMPARE_OP 0 ( 12 POP_JUMP_IF_FALSE 24 # 弹出TOS,如果为假值, 则将字节码计数器的值设为24,即跳到24POP_BLOCK 7 14 LOAD_FAST 0 (i) 16 LOAD_CONST 3 (1) 18 INPLACE_ADD 20 STORE_FAST 0 (i) 22 JUMP_ABSOLUTE 6 # 跳到6 >> 24 POP_BLOCK >> 26 LOAD_CONST 0 (None) 28 RETURN_VALUE
2函数调用def test(): return testt(2, 3, 5) def testt(a, b, c): return a + b - c
5 0 LOAD_GLOBAL 0 (testt) 2 LOAD_CONST 1 (2) 4 LOAD_CONST 2 (3) 6 LOAD_CONST 3 (5) 8 CALL_FUNCTION 3 # 函数调 用,testt(2,3,5),返回值压栈 10 RETURN_VALUE # 返回TOS到函数的调用者
04参考dis — Python 字节码反汇编器
https://docs.Python.org/zh-cn/3.6/library/dis.html
浮生半日:探究Python字节码
https://www.cnblogs.com/0xJDchen/p/6995204.html
PYC文件格式分析
https://kdr2.com/tech/Python/pyc-format.html