我在这里只写关于 CPython 2.7 的文章。CPython 3 大体相似,但做了一些增量修改。除其他外,函数的代码对象现在位于f.__code__而不是,f.func_code并且添加了一个新属性co_kwonlyargcount以支持仅关键字参数。Python 3 的下一个版本可能会使用一个显着改变的字节码实现,称为 wordcode。其他 Python 实现,例如 PyPy 和 Jython,可能使用完全不同的方式来存储代码。
我还将主要提到功能。模块和类定义也是使用代码对象来实现的(确实,.pyc文件基本上包含序列化的模块代码对象),但是代码对象的很多特性只与函数相关。
co_argcount。这是函数采用的参数数量,不包括任何*args和**kwargs。字节码中的函数调用通过将所有参数压入堆栈然后调用CALL_FUNCTION; 然后co_argcount可用于确定函数是否传递了正确数量的变量。
co_cellvars 和 co_freevars。这两个用于实现嵌套函数范围。co_cellvars是一个元组,包含函数中所有变量的名称,这些变量也用于嵌套函数,并且co_freevars具有函数中使用的所有变量的名称,这些变量在封闭函数范围中定义。例如,这里y是 的 cellvarsf和 freevars 之间的g:
In [21]: def f(x):
....: y = 3
....: def g():
....: return y + 1
....: return g()
....:
In [22]: f.func_code.co_cellvars
Out[22]: ('y',)
In [23]: f.func_code.co_consts[2].co_freevars
Out[23]: ('y',)
与存储元组的其他几个代码对象属性一样,处理这些变量的字节码使用元组的索引。例如,y上面第 2 行的赋值被编译成一个STORE_DEREF带有参数 0 的操作码,表示它位于单元变量y中的位置 0,第y4 行的读取变成LOAD_DEREF带有参数 0 的操作码。DEREF操作码用于单元变量和freevars 和索引实际上是两个 ( co_cellvars + co_freevars) 的串联,所以如果一个函数有 cellvars(a, b)和 freevars (c, d),LOAD_DEREF2 将加载的值c和LOAD_DEREF1 将加载b。在 cellvar 和 freevar 中,名称按字母顺序列出。
我不熟悉这两个字段在运行时如何用于将信息从一个功能范围传递到另一个功能范围。
co_code,这是二进制格式的实际字节码,存储为普通的 Python 字符串。如上所示,它是VM的指令列表。函数从第一条指令开始执行,在遇到RETURN_VALUE指令时停止。在此答案的其他地方讨论了一些字节码,所有字节码都记录在dis模块文档,但我不会在这里讨论所有的说明。
字符串中的编码在co_code每条指令中使用可变数量的字节。每条指令都包含一个opcode ,它指示 VM 要执行的操作,加上一个可选参数,它始终是一个整数。操作码是一个单字节整数,因此可能有 256 个不同的操作码,尽管其中许多当前未使用。每个操作码都有一个名称,该名称显示在dis输出中(参见上面的几个示例)并在opcode标准库模块中定义。
所有整数值低于称为HAVE_ARGUMENT(Python 2.7 上为 90)的截止值的操作码都没有参数,因此它们的指令只占用一个字节。接受参数的操作码占用三个字节,其中第二个和第三个字节以小端顺序存储参数。如果参数太大而无法容纳这两个字节(即,它大于216= 65536),使用了一个特殊的操作码EXTENDED_ARG。例如,如果您想在函数中加载第 65537 个单元格变量(为什么要这样做呢?),您首先有一个EXTENDED_ARG带有参数 1 的指令,然后是LOAD_DEREF带有参数 1 的指令,表明您需要加载元素1 * 65536 + 1 = 65537.
co_consts。这是函数中使用的所有常量的元组,如整数、字符串和布尔值。它由LOAD_CONST操作码使用,它接受一个参数,该参数指示co_consts要从中加载的元组中的索引。例如,f下面是上面定义的函数的常量co_cellvars:
In [34]: print f.func_code.co_consts
(None, 3, <code object g at 0xf6796800, file "<ipython-input-1-a7b65140e37b>", line 3>)
元组中的第二个元素是3,因此赋值代码y = 3包含指令LOAD_CONST1,指示索引 1 处的常量应放入堆栈。同样,LOAD_CONST2 在创建嵌套函数时加载代码g。
函数代码对象中的第一个co_consts元素始终是函数的文档字符串,可能是None(就像这里一样)。否则,常量大多按照它们在字节码中首次使用的顺序排列,但 VM 不需要这样做,而且 CPython 的窥孔优化器在生成字节码后运行,有时会做出不遵守此顺序的更改。
co_filename。这是在其中创建代码的文件的名称。
co_firstlineno。生成代码对象的 Python 代码开头的 1 索引行号。与 结合使用co_lnotab,用于计算异常回溯等位置的行信息。
co_flags。这是一个整数,它结合了许多关于函数的布尔标志。它没有完全记录,但标志包括(使用inspect模块中定义的名称):
-
CO_OPTIMIZED
: 表示该函数是在启用 Python 优化的情况下编译的;我相信这只是意味着删除文档字符串和断言。 -
CO_NEWLOCALS
:为除模块之外的所有代码对象设置;我猜这是对 CPython 的早期更改的残余。 -
CO_VARARGS
: 该函数采用 *args。 -
CO_VARKEYWORDS
: 该函数需要 **kwargs。 -
CO_NESTED
: 该函数嵌套在另一个函数中。 -
CO_GENERATOR
: 该函数是一个生成器函数。 -
CO_NOFREE
: 如果函数没有单元格或自由变量,则设置。
co_lnotab。这意味着行号表,并存储字节码指令到行号的压缩映射。它是一串二进制数据,其中每两个字节是一对(增加co_code字符串中的偏移量,增加 Python 行号)。第一个从 0 开始,第二个从 的值开始co_firstlineno。这是一个例子:
In [39]: def f(x):
....: x = 3
....: y = 4
....:
In [40]: f.func_code.co_lnotab
Out[40]: '\x00\x01\x06\x01'
In [41]: dis.dis(f)
2 0 LOAD_CONST 1 (3)
3 STORE_FAST 0 (x)
3 6 LOAD_CONST 2 (4)
9 STORE_FAST 1 (y)
12 LOAD_CONST 0 (None)
15 RETURN_VALUE
在这个函数中,代表六个字节的前两条指令来自函数代码的第一行,其余的来自第二行。co_lnotab可以更易读地呈现为 (0, 1), (6, 1) 。第一行表示我们应该将字节码偏移量加0,行号偏移量加1(第一行号是def行,但没有字节码直接对应)。然后第二行告诉我们将字节码偏移量增加 6,行号偏移量增加 1,这对应于接下来的 6 个字节在我们刚刚通过的行的事实。然后其余的代码隐含在我们现在到达的那一行。这在lnotab_notes.txt中有更详细的解释在 Python 源代码中。
在实践中,Python 有时会生成条目略多于所需条目的 lnotab。例如,由生成器表达式(例如(f(x) for x in lst))创建的代码对象总是有一个非空的 lnotab,即使所有代码都在第一行。
co_name。这是与代码对象相关联的对象(例如函数)的名称。
同名。在代码对象中用作属性、全局变量名称和导入名称的字符串元组。使用这些名称之一的操作码(例如,LOAD_ATTR)将这个元组的整数索引作为参数。这些是按第一次使用的顺序。
co_names。函数中局部变量的数量。据我所知,这只是co_varnames. 这可能是为了决定在调用函数时为局部变量分配多少空间。
co_stacksize。一个整数,表示函数将使用的最大堆栈空间量。这是必要的,因为与代码对象关联的 VM 堆栈是在调用代码时预先分配的。因此,如果co_stacksize太低,该函数可能会溢出其分配的堆栈并发生可怕的事情。
无法计算任意一段 Python 字节码将使用的堆栈空间量(这个问题看起来与停止问题很相似,但我不确定是否已正式证明是这种情况)。但是,由 CPython 生成的字节码表现良好,可以编写一个计算co_stacksize. 关键的简化假设是控制流图中的任何循环(例如,由循环生成的循环)对使用的堆栈空间没有净影响。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Python工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Python开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以扫码获取!!!(备注:Python)
08edf79.png)
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以扫码获取!!!(备注:Python)