Python源码中的PyCodeObject

时间:2023-03-08 17:14:12

1.Python程序的执行过程

Python解释器(interpreter)在执行任何一个Python程序文件时,首先进行的动作都是先对文件中的Python源代码进行编译,编译的主要结果是产生的一组Python的字节码(byte code),然后将编译的结果交给Python虚拟机(Virtual Machine),由虚拟机按照顺序一条一条地执行字节码,从而完成对Python程序的执行动作。

对比java的执行:

java:     .java-->(javac)-->.class-->(java)-->结果

python: .py  -->(编译器)-->.pyc-->(虚拟机)-->结果

从上面可知:Python对源码的编译结果是产生的一个.pyc文件,这其实并不太准确。

如:

~/code/py/dis/demo.py

Python源码中的PyCodeObject
1 class A:
2 pass
3 def f():
4 pass
5 a = A()
6 f()
Python源码中的PyCodeObject

执行:python demo.py,并没有产生.pyc文件。

分析如下:

对于Python编译器来说,PyCodeObject对象是其真正的编译结果,而pyc文件只是这个对象在硬盘上的表现形式。

在程序的运行期间,编译结果存在于内存的PyCodeObject对象中;而运行结束后,这个编译结果又被保存到pyc文件中。当下次运行相同的程序时,Python会根据pyc文件中记录的编译结果直接建立内存中PyCodeObject对象,而不用再次对源码进行编译了。

把py文件的编译结果保存到pyc文件,最大的优点在于在运行程序时,不需要对该源码重新进行编译。所以,需要编译形成pyc文的应该是那些可以重用的代码。

对于仅仅运行一次的程序,保存其对于的pyc文件时没有必要的。

如果程序要执行import导入模块的操作,程序运行时会触发pyc文件的生成(如果该模块在PATH路径下与之已有匹配的pyc文件,不需生成直接使用即可)。

2.Python源码中的PyCodeObject

Include/code.h:

Python源码中的PyCodeObject
 1 /* Bytecode object */
2 typedef struct {
3 PyObject_HEAD
4 int co_argcount; /* #arguments, except *args */
5 int co_kwonlyargcount; /* #keyword only arguments */
6 int co_nlocals; /* #local variables */
7 int co_stacksize; /* #entries needed for evaluation stack */
8 int co_flags; /* CO_..., see below */
9 PyObject *co_code; /* instruction opcodes */
10 PyObject *co_consts; /* list (constants used) */
11 PyObject *co_names; /* list of strings (names used) */
12 PyObject *co_varnames; /* tuple of strings (local variable names) */
13 PyObject *co_freevars; /* tuple of strings (free variable names) */
14 PyObject *co_cellvars; /* tuple of strings (cell variable names) */
15 /* The rest doesn't count for hash or comparisons */
16 unsigned char *co_cell2arg; /* Maps cell vars which are arguments. */
17 PyObject *co_filename; /* unicode (where it was loaded from) */
18 PyObject *co_name; /* unicode (name, for reference) */
19 int co_firstlineno; /* first source line number */
20 PyObject *co_lnotab; /* string (encoding addr<->lineno mapping) See
21 Objects/lnotab_notes.txt for details. */
22 void *co_zombieframe; /* for optimization only (see frameobject.c) */
23 PyObject *co_weakreflist; /* to support weakrefs to code objects */
24 } PyCodeObject;
Python源码中的PyCodeObject

在解释PyCodeObject中各个域的含义之前,先说明一个概念--Code Block:

Python编译器在对Python源码进行编译的时候,对代码中的一个Code Block,会创建一个PyCodeObject对象与这段代码对应。

如何确定多少代码算一个Code Block?

Python中确定Code Block的规则:当进入一个新的名字空间或作用域时,就算进入了一个新的Code Block了。

即:一个名字空间对应一个Code Block,它会对应一个PyCodeObject。

现在暂且认为名字空间就是符号的上下文环境,是名字到对象的映射。名字空间以后会详述。

在Python中,类、函数和module都对应着一个独立的名字空间,因此都会对应一个PyCodeObject对象。

例如:

~/code/py/dis/demo.py

Python源码中的PyCodeObject
1 class A:
2 pass
3 def f():
4 pass
5 a = A()
6 f()
Python源码中的PyCodeObject

Python编译器对demo.py源码编译之后,会创建3个PyCodeObject对象:第一个是对应整个demo.py文件代表的Code Block,第二个是对应Class A代表的Code Block,第三个是对应f代表的Code Block。

PyCodeObject中各个域的含义:

(1)co_argcount、co_kwonlyargcount

PEP 3102:http://www.python.org/dev/peps/pep-3102/

Keyword-only argument:在函数参数列表中,出现在*varargs之后的命名参数只能使用关键参数的形式调用。

函数调用是参数的赋值顺序:位置参数-->关键字参数-->可变参数(*varargs)

co_argcount:CodeBlock中位置参数的个数,即:在调用时出现的位置参数的个数(不包含可变参数*varargs)。

co_kwonlyargcount:CodeBlock中的关键参数的个数,即在调用时是出现在可变参数(*varargs)之后的参数个数,可变参数之后的参数均是形式为“keyvalue”的关键参数。

Python源码中的PyCodeObject
>>> def f1(a, b, c, *d, e, f):
... m = 1
... pass
...
>>> f1.__code__.co_argcount
3
>>> f1.__code__.co_kwonlyargcount
2
Python源码中的PyCodeObject

(2)co_nlocals:Code Block中的所有局部变量的个数,包括code block的参数(co_argcount+co_kwonlyargcount+可变参数个数)+code block内的局部变量

>>> f1.__code__.co_nlocals
7

7 = (3+2+1)+1

(3)co_stacksize:执行该段Code Block需要的栈空间数

>>> f1.__code__.co_stacksize
1

(4)co_code:Code Block编译所得的字节码指令序列

>>> f1.__code__.co_code
b'd\x01\x00}\x06\x00d\x00\x00S'

(5)co_consts、co_names

co_consts:Code Block中的所有常量的元组

co_names:Code Block中的所有符号(名字)的元组

>>> f1.__code__.co_consts
(None, 1)
>>> f1.__code__.co_names
()

(6)co_filename、co_name

co_filename:Code Block所对应的的.py文件的完整路径

co_name:Code Block的名字,,通常是函数名或类名

>>> f1.__code__.co_filename
'<stdin>'
>>> f1.__code__.co_name
'f1'

非交互式运行结果:

vim demo1.py

def f1(a, b, c, *d, e, f):
m = 1
pass
print(f1.__code__.co_filename)
print(f1.__code__.co_name)

运行:

[root@lq dis]# python demo1.py
demo1.py
f1

(7)co_firstlineno:Code Block在对应的.py文件中的起始行

demo1.py

def f1(a, b, c, *d, e, f):
m = 1
pass
print(f1.__code__.co_firstlineno)

运行:

[root@lq dis]# python demo1.py
1

(8)co_varnames、co_freevars、co_cellvars

co_varnames:在本代码段中被赋值,但没有被内层代码段引用的变量

co_freevars(freevars:*变量):在本代码段中被引用,在外层代码段中被赋值的变量

co_cellvars(cellvars:被内层代码所约束的变量):在本代码段中被赋值,且被内层代码段引用的变量

例1:简单函数

vim demo1.py:

Python源码中的PyCodeObject
 1 def f1(a, b, c, *d, e, f):
2 m = 1
3 pass
4
5 print('co_argcount :', f1.__code__.co_argcount)
6 print('co_kwonlyargcount :', f1.__code__.co_kwonlyargcount)
7 print('co_nlocals :', f1.__code__.co_nlocals)
8 print('co_stacksize :', f1.__code__.co_stacksize)
9 print('co_flags :', f1.__code__.co_flags)
10 print('co_code :', f1.__code__.co_code)
11 print('co_consts :', f1.__code__.co_consts)
12 print('co_names :', f1.__code__.co_names)
13 print('co_varnames :', f1.__code__.co_varnames)
14 print('co_freevars :', f1.__code__.co_freevars)
15 print('co_cellvars :', f1.__code__.co_cellvars)
16 print('co_filename :', f1.__code__.co_filename)
17 print('co_name :', f1.__code__.co_name)
18 print('co_firstlineno :', f1.__code__.co_firstlineno)
19 print('co_lnotab :', f1.__code__.co_lnotab)
Python源码中的PyCodeObject

运行:

Python源码中的PyCodeObject
[root@lq dis]# python demo1.py
co_argcount : 3
co_kwonlyargcount : 2
co_nlocals : 7
co_stacksize : 1
co_flags : 71
co_code : b'd\x01\x00}\x06\x00d\x00\x00S'
co_consts : (None, 1)
co_names : ()
co_varnames : ('a', 'b', 'c', 'e', 'f', 'd', 'm')
co_freevars : ()
co_cellvars : ()

co_filename : demo1.py
co_name : f1
co_firstlineno : 1
co_lnotab : b'\x00\x01\x06\x01'
Python源码中的PyCodeObject

例2:嵌套函数

vim demo2.py

Python源码中的PyCodeObject
 1 def f1(a, b, c, *d, e, f):
2 m = 1
3 def f2():
4 n = m
5 print('f2-->co_argcount :', f2.__code__.co_argcount)
6 print('f2-->co_kwonlyargcount :', f2.__code__.co_kwonlyargcount)
7 print('f2-->co_nlocals :', f2.__code__.co_nlocals)
8 print('f2-->co_stacksize :', f2.__code__.co_stacksize)
9 print('f2-->co_flags :', f2.__code__.co_flags)
10 print('f2-->co_code :', f2.__code__.co_code)
11 print('f2-->co_consts :', f2.__code__.co_consts)
12 print('f2-->co_names :', f2.__code__.co_names)
.co_varnames)
16 print('f2-->co_filename :', f2.__code__.co_filename)
17 print('f2-->co_name :', f2.__code__.co_name)
18 print('f2-->co_firstlineno :', f2.__code__.co_firstlineno)
19 print('f2-->co_lnotab :', f2.__code__.co_lnotab)
20
21 print('f1-->co_argcount :', f1.__code__.co_argcount)
22 print('f1-->co_kwonlyargcount :', f1.__code__.co_kwonlyargcount)
23 print('f1-->co_nlocals :', f1.__code__.co_nlocals)
24 print('f1-->co_stacksize :', f1.__code__.co_stacksize)
25 print('f1-->co_flags :', f1.__code__.co_flags)
26 print('f1-->co_code :', f1.__code__.co_code)
27 print('f1-->co_consts :', f1.__code__.co_consts)
28 print('f1-->co_names :', f1.__code__.co_names)
.co_varnames)
32 print('f1-->co_filename :', f1.__code__.co_filename)
33 print('f1-->co_name :', f1.__code__.co_name)
34 print('f1-->co_firstlineno :', f1.__code__.co_firstlineno)
35 print('f1-->co_lnotab :', f1.__code__.co_lnotab)
36 print('=========================================================')
37 f1(1, 2, 3, 4, 5, 6, 7, e = 8, f = 9)
Python源码中的PyCodeObject

运行:

Python源码中的PyCodeObject
 1 [root@lq dis]# python demo2.py
2 f1-->co_argcount : 3
3 f1-->co_kwonlyargcount : 2
4 f1-->co_nlocals : 7
5 f1-->co_stacksize : 3
6 f1-->co_flags : 7
7 f1-->co_code : b'd\x01\x00\x89\x00\x00\x87\x00\x00f\x01\x00d\x02\x00d\x03\x00\x86\x00\x00}\x06\x00t\x00\x00d\x04\x00|\x06\x00j\x01\x00j\x02\x00\x83\x02\x00\x01t\x00\x00d\x05\x00|\x06\x00j\x01\x00j\x03\x00\x83\x02\x00\x01t\x00\x00d\x06\x00|\x06\x00j\x01\x00j\x04\x00\x83\x02\x00\x01t\x00\x00d\x07\x00|\x06\x00j\x01\x00j\x05\x00\x83\x02\x00\x01t\x00\x00d\x08\x00|\x06\x00j\x01\x00j\x06\x00\x83\x02\x00\x01t\x00\x00d\t\x00|\x06\x00j\x01\x00j\x07\x00\x83\x02\x00\x01t\x00\x00d\n\x00|\x06\x00j\x01\x00j\x08\x00\x83\x02\x00\x01t\x00\x00d\x0b\x00|\x06\x00j\x01\x00j\t\x00\x83\x02\x00\x01t\x00\x00d\x0c\x00|\x06\x00j\x01\x00j\n\x00\x83\x02\x00\x01t\x00\x00d\r\x00|\x06\x00j\x01\x00j\x0b\x00\x83\x02\x00\x01t\x00\x00d\x0e\x00|\x06\x00j\x01\x00j\x0c\x00\x83\x02\x00\x01t\x00\x00d\x0f\x00|\x06\x00j\x01\x00j\r\x00\x83\x02\x00\x01t\x00\x00d\x10\x00|\x06\x00j\x01\x00j\x0e\x00\x83\x02\x00\x01t\x00\x00d\x11\x00|\x06\x00j\x01\x00j\x0f\x00\x83\x02\x00\x01t\x00\x00d\x12\x00|\x06\x00j\x01\x00j\x10\x00\x83\x02\x00\x01d\x00\x00S'
8 f1-->co_consts : (None, 1, <code object f2 at 0x7f5c2f036930, file "demo2.py", line 3>, 'f1.<locals>.f2', 'f2-->co_argcount :', 'f2-->co_kwonlyargcount :', 'f2-->co_nlocals :', 'f2-->co_stacksize :', 'f2-->co_flags :', 'f2-->co_code :', 'f2-->co_consts :', 'f2-->co_names :', 'f2-->co_varnames :', 'f2-->co_freevars :', 'f2-->co_cellvars :', 'f2-->co_filename :', 'f2-->co_name :', 'f2-->co_firstlineno :', 'f2-->co_lnotab :')
9 f1-->co_names : ('print', '__code__', 'co_argcount', 'co_kwonlyargcount', 'co_nlocals', 'co_stacksize', 'co_flags', 'co_code', 'co_consts', 'co_names', 'co_varnames', 'co_freevars', 'co_cellvars', 'co_filename', 'co_name', 'co_firstlineno', 'co_lnotab')
)
13 f1-->co_filename : demo2.py
14 f1-->co_name : f1
15 f1-->co_firstlineno : 1
16 f1-->co_lnotab : b'\x00\x01\x06\x01\x12\x02\x13\x01\x13\x01\x13\x01\x13\x01\x13\x01\x13\x01\x13\x01\x13\x01\x13\x01\x13\x01\x13\x01\x13\x01\x13\x01\x13\x01'
17 =========================================================
18 f2-->co_argcount : 0
19 f2-->co_kwonlyargcount : 0
20 f2-->co_nlocals : 1
21 f2-->co_stacksize : 1
22 f2-->co_flags : 19
23 f2-->co_code : b'\x88\x00\x00}\x00\x00d\x00\x00S'
24 f2-->co_consts : (None,)
25 f2-->co_names : ()
,)
29 f2-->co_filename : demo2.py
30 f2-->co_name : f2
31 f2-->co_firstlineno : 3
32 f2-->co_lnotab : b'\x00\x01'
Python源码中的PyCodeObject

例3:闭包

vim demo3.py

Python源码中的PyCodeObject
 1 def f1(a, b, c, *d, e, f):
2 m = 1
3 def f2():
4 n = m
5 return f2
6
7 print('f1-->co_argcount :', f1.__code__.co_argcount)
8 print('f1-->co_kwonlyargcount :', f1.__code__.co_kwonlyargcount)
9 print('f1-->co_nlocals :', f1.__code__.co_nlocals)
10 print('f1-->co_stacksize :', f1.__code__.co_stacksize)
11 print('f1-->co_flags :', f1.__code__.co_flags)
12 print('f1-->co_code :', f1.__code__.co_code)
13 print('f1-->co_consts :', f1.__code__.co_consts)
14 print('f1-->co_names :', f1.__code__.co_names)
.co_varnames)
18 print('f1-->co_filename :', f1.__code__.co_filename)
19 print('f1-->co_name :', f1.__code__.co_name)
20 print('f1-->co_firstlineno :', f1.__code__.co_firstlineno)
21 print('f1-->co_lnotab :', f1.__code__.co_lnotab)
22 print('=========================================================')
23 f3 = f1(1, 2, 3, 4, 5, 6, 7, e = 8, f = 9)
24 print('f3-->co_argcount :', f3.__code__.co_argcount)
25 print('f3-->co_kwonlyargcount :', f3.__code__.co_kwonlyargcount)
26 print('f3-->co_nlocals :', f3.__code__.co_nlocals)
27 print('f3-->co_stacksize :', f3.__code__.co_stacksize)
28 print('f3-->co_flags :', f3.__code__.co_flags)
29 print('f3-->co_code :', f3.__code__.co_code)
30 print('f3-->co_consts :', f3.__code__.co_consts)
31 print('f3-->co_names :', f3.__code__.co_names)
32 print('f3-->co_varnames :', f3.__code__.co_varnames)
33 print('f3-->co_freevars :', f3.__code__.co_freevars)
34 print('f3-->co_cellvars :', f3.__code__.co_cellvars)
35 print('f3-->co_filename :', f3.__code__.co_filename)
36 print('f3-->co_name :', f3.__code__.co_name)
37 print('f3-->co_firstlineno :', f3.__code__.co_firstlineno)
38 print('f3-->co_lnotab :', f3.__code__.co_lnotab)
Python源码中的PyCodeObject

运行:

Python源码中的PyCodeObject
 1 [root@lq dis]# python demo3.py
2 f1-->co_argcount : 3
3 f1-->co_kwonlyargcount : 2
4 f1-->co_nlocals : 7
5 f1-->co_stacksize : 3
6 f1-->co_flags : 7
7 f1-->co_code : b'd\x01\x00\x89\x00\x00\x87\x00\x00f\x01\x00d\x02\x00d\x03\x00\x86\x00\x00}\x06\x00|\x06\x00S'
8 f1-->co_consts : (None, 1, <code object f2 at 0x7f010517e930, file "demo3.py", line 3>, 'f1.<locals>.f2')
9 f1-->co_names : ()
)
13 f1-->co_filename : demo3.py
14 f1-->co_name : f1
15 f1-->co_firstlineno : 1
16 f1-->co_lnotab : b'\x00\x01\x06\x01\x12\x02'
17 =========================================================
18 f3-->co_argcount : 0
19 f3-->co_kwonlyargcount : 0
20 f3-->co_nlocals : 1
21 f3-->co_stacksize : 1
22 f3-->co_flags : 19
23 f3-->co_code : b'\x88\x00\x00}\x00\x00d\x00\x00S'
24 f3-->co_consts : (None,)
25 f3-->co_names : ()
26 f3-->co_varnames : ('n',)
27 f3-->co_freevars : ('m',)
28 f3-->co_cellvars : ()
29 f3-->co_filename : demo3.py
30 f3-->co_name : f2
31 f3-->co_firstlineno : 3
32 f3-->co_lnotab : b'\x00\x01'
Python源码中的PyCodeObject

(9)co_lnotab:字节码指令与.pyc文件中的source code行号的对于关系

Object/lnotab_notes.txt:

All about co_lnotab, the line number table.

Code objects store a field named co_lnotab. This is an array of unsigned bytes disguised as a Python string. It is used to map bytecode offsets to source code line #s for tracebacks and to identify line number boundaries for line tracing.

The array is conceptually a compressed list of (bytecode offset increment, line number increment) pairs. The details are important and delicate, best illustrated by example:

byte code offset               source code line number
     0                                              1
     6                                              2
     50                                            7
     350                                          307
     361                                          308

Instead of storing these numbers literally, we compress the list by storing only the increments from one row to the next.Conceptually, the stored list might look like:

0, 1, 6, 1, 44, 5, 300, 300, 11, 1

形成的数组:0, 1, (0+6), (1+1), (6+44), (2+5), (50+300), (7+300), (350+11), (307+1)

3.在Python中访问PyCodeObject对象

以上的例子中已经有一个函数的属性可以访问函数对应的PyCodeObject对象:__code__,函数的该属性表示已经编译函数体的Code Object。

在Python中,可以通过code对象访问PyCodeObject对象中的各个域。code对象是对C一级的PyCodeObject对象的一个简单包装。

通过内建函数compile可以获得一个code对象。

例如:

demo.py:

Python源码中的PyCodeObject
class A:
pass
def f():
pass
a = A()
f()
Python源码中的PyCodeObject

交互式:

Python源码中的PyCodeObject
>>> source = open('./demo.py').read()
>>> source
'class A:\n pass\ndef f():\n pass\na = A()\nf()\n'
>>> co = compile(source, './demo.py', 'exec')
>>> type(co)
<class 'code'>
>>> dir(co)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_kwonlyargcount', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_stacksize', 'co_varnames']
>>> co.co_names
('A', 'f', 'a')
>>>
Python源码中的PyCodeObject

访问PyCodeObject域的py文件:access_demo.py

Python源码中的PyCodeObject
 1 source = open('./demo.py').read()
2 co = compile(source, './demo.py', 'exec')
3
4 #co = co.co_consts[0] #code object A
5 #co = co.co_consts[2] #code object f
6
7 print('type(co) :', type(co))
8 print('co.co_argcount :', co.co_argcount)
9 print('co.co_kwonlyargcount :', co.co_kwonlyargcount)
10 print('co.co_nlocals :', co.co_nlocals)
11 print('co.co_stacksize :', co.co_stacksize)
12 print('co.co_flags :', co.co_flags)
13 print('co.co_code :', co.co_code)
14 print('co.co_consts :', co.co_consts)
15 print('co.co_names :', co.co_names)
16 print('co.co_varnames :', co.co_varnames)
17 print('co.co_freevars :', co.co_freevars)
18 print('co.co_cellvars :', co.co_cellvars)
19 print('co.co_filename :', co.co_filename)
20 print('co.co_name :', co.co_name)
21 print('co.co_firstlineno :', co.co_firstlineno)
22 print('co.co_lnotab :', co.co_lnotab)
Python源码中的PyCodeObject

运行结果为demo.py文件全局的PyCodeObject的信息:

Python源码中的PyCodeObject
 1 [root@lq dis]# python access_demo.py
2 type(co) : <class 'code'>
3 co.co_argcount : 0
4 co.co_kwonlyargcount : 0
5 co.co_nlocals : 0
6 co.co_stacksize : 3
7 co.co_flags : 64
8 co.co_code : b'Gd\x00\x00d\x01\x00\x84\x00\x00d\x01\x00\x83\x02\x00Z\x00\x00d\x02\x00d\x03\x00\x84\x00\x00Z\x01\x00e\x00\x00\x83\x00\x00Z\x02\x00e\x01\x00\x83\x00\x00\x01d\x04\x00S'
9 co.co_consts : (<code object A at 0x7f471304cb70, file "./demo.py", line 1>, 'A', <code object f at 0x7f4712b66db0, file "./demo.py", line 3>, 'f', None)
10 co.co_names : ('A', 'f', 'a')
11 co.co_varnames : ()
12 co.co_freevars : ()
13 co.co_cellvars : ()
14 co.co_filename : ./demo.py
15 co.co_name : <module>
16 co.co_firstlineno : 1
17 co.co_lnotab : b'\x13\x02\x0c\x02\t\x01'
Python源码中的PyCodeObject

去掉access_demo.py第4行的注释:得到类A对应的PyCodeObject

Python源码中的PyCodeObject
 1 [root@lq dis]# python access_demo.py
2 type(co) : <class 'code'>
3 co.co_argcount : 1
4 co.co_kwonlyargcount : 0
5 co.co_nlocals : 1
6 co.co_stacksize : 1
7 co.co_flags : 66
8 co.co_code : b'|\x00\x00Ee\x00\x00Z\x01\x00d\x00\x00Z\x02\x00d\x01\x00S'
9 co.co_consts : ('A', None)
10 co.co_names : ('__name__', '__module__', '__qualname__')
11 co.co_varnames : ('__locals__',)
12 co.co_freevars : ()
13 co.co_cellvars : ()
14 co.co_filename : ./demo.py
15 co.co_name : A
16 co.co_firstlineno : 1
17 co.co_lnotab : b'\x10\x01'
Python源码中的PyCodeObject

去掉access_demo.py第5行的注释:得到函数f对应的PyCodeObject

Python源码中的PyCodeObject
 1 [root@lq dis]# python access_demo.py
2 type(co) : <class 'code'>
3 co.co_argcount : 0
4 co.co_kwonlyargcount : 0
5 co.co_nlocals : 0
6 co.co_stacksize : 1
7 co.co_flags : 67
8 co.co_code : b'd\x00\x00S'
9 co.co_consts : (None,)
10 co.co_names : ()
11 co.co_varnames : ()
12 co.co_freevars : ()
13 co.co_cellvars : ()
14 co.co_filename : ./demo.py
15 co.co_name : f
16 co.co_firstlineno : 3
17 co.co_lnotab : b'\x00\x01'
Python源码中的PyCodeObject

4.pyc文件的生成

根据前面所述,执行普通的.py程序如python demo.py是不会产生pyc的文件,可能因为编译器认为demo.py可能仅运行一次,不会被重用的,所以没有将编译生成PyCodeObject对象存储为pyc文件。

那么如何生成pyc文件呢?

(1)使用import demo

在Python运行的过程中,如果碰到import demo这样的语句,那么Python将在设定好的path中寻找demo.pyc或者demo.dll文件,如果没有找到这些文件,而只是发现了demo.py,然后Python会首先将demo.py编译成相应的PyCodeObject的中间结果,接着创建demo.pyc文件,并将中间结果写入该文件。接下来,Python才会对demo.pyc文件进行import的动作,实际上就是根据demo.pyc文件中记录的编译结果直接建立内存中的PyCodeObject。

Python源码中的PyCodeObject
 1 [root@lq dis]# ls
2 access_demo.py demo1.py demo2.py demo3.py demo.py test.py
3 [root@lq dis]# python
4 Python 3.3.0 (default, Nov 21 2012, 11:37:07)
5 [GCC 4.4.6 20110731 (Red Hat 4.4.6-3)] on linux
6 Type "help", "copyright", "credits" or "license" for more information.
7 >>> import demo
8 >>> exit()
9 [root@lq dis]# ls
10 access_demo.py demo1.py demo2.py demo3.py demo.py __pycache__ test.py
11 [root@lq dis]# cd __pycache__/
12 [root@lq __pycache__]# ls
13 demo.cpython-33.pyc
Python源码中的PyCodeObject

从上面的例子,可以看出import demo之后,在demo.py所在目录生成了一个__pycache__文件夹,该文件夹下有一个demo.cpython-33.pyc的文件,该pyc文件就是我们想要的。

pyc文件是一个二进制文件。在Windows下可以通过UE查看:

Python源码中的PyCodeObject

Linux可以通过Vim查看:vim -b demo.cpython-33.pyc,然后输入命令::%!xxd查看,使用:%!xxd -r返回。

Python源码中的PyCodeObject

Python源码中的PyCodeObject

Python源码中的PyCodeObject

Python源码中的PyCodeObject

pyc文件的文件格式(清楚PyCodeObject中域的含义是了解pyc文件格式的基础)及其具体创建过程,在后续部分会详细介绍。

(2)使用py_compile.compile()

py_compile模块详见:http://docs.python.org/3/library/py_compile.html

The py_compile module provides a function to generate a byte-code file from a source file, and another function used when the module source file is invoked as a script.

py_compile.compile(file, cfile=None, dfile=None, doraise=False, optimize=-1)

Compile a source file to byte-code and write out the byte-code cache file.

The source code is loaded from the file name file.
The byte-code is written to cfile, which defaults to the PEP 3147 path, ending in .pyc (.pyo if optimization is enabled in the current interpreter). For example, if file is/foo/bar/baz.py cfile will default to /foo/bar/__pycache__/baz.cpython-32.pyc for Python 3.2.
 If dfile is specified, it is used as the name of the source file in error messages when instead of file.
If doraise is true, a PyCompileError is raised when an error is encountered while compiling file. Ifdoraise is false (the default), an error string is written to sys.stderr, but no exception is raised.

This function returns the path to byte-compiled file, i.e. whatever cfile value was used.

optimize controls the optimization level and is passed to the built-in compile() function. The default of -1 selects the optimization level of the current interpreter.

Python源码中的PyCodeObject
 1 [root@lq dis]# ls
2 access_demo.py demo1.py demo2.py demo3.py demo.py __pycache__ test.py
3 [root@lq dis]# rm -rf __pycache__/
4 [root@lq dis]# ls
5 access_demo.py demo1.py demo2.py demo3.py demo.py test.py
6 [root@lq dis]# python
7 Python 3.3.0 (default, Nov 21 2012, 11:37:07)
8 [GCC 4.4.6 20110731 (Red Hat 4.4.6-3)] on linux
9 Type "help", "copyright", "credits" or "license" for more information.
10 >>> import py_compile
11 >>> py_compile.compile('./demo.py')
12 './__pycache__/demo.cpython-33.pyc'
13 >>> exit()
14 [root@lq dis]# ls
15 access_demo.py demo1.py demo2.py demo3.py demo.py __pycache__ test.py
16 [root@lq dis]# cd __pycache__/
17 [root@lq __pycache__]# ls
18 demo.cpython-33.pyc
Python源码中的PyCodeObject