Skip to content

Commit e0fe7cb

Browse files
committed
update
1 parent 9bd884e commit e0fe7cb

File tree

9 files changed

+367
-5
lines changed

9 files changed

+367
-5
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
.DS_Store
2+
*.py[co]
3+
.ipynb_checkpoints/

ch11.md

Lines changed: 302 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,302 @@
1-
# Python虚拟机中的函数机制
1+
# Python 虚拟机中的函数机制
2+
3+
一个函数意味着一个新的作用域(命名空间),当 Python 虚拟机执行一个函数时,首先创建一个 PyFrameObject,然后执行函数的字节码(PyCodeObject)。
4+
5+
6+
PyFunctionObject 的定义:
7+
8+
```C
9+
typedef struct {
10+
PyObject_HEAD
11+
PyObject *func_code; /* A code object */
12+
PyObject *func_globals; /* A dictionary (other mappings won't do) */
13+
PyObject *func_defaults; /* NULL or a tuple */
14+
PyObject *func_closure; /* NULL or a tuple of cell objects */
15+
PyObject *func_doc; /* The __doc__ attribute, can be anything */
16+
PyObject *func_name; /* The __name__ attribute, a string object */
17+
PyObject *func_dict; /* The __dict__ attribute, a dict or NULL */
18+
PyObject *func_weakreflist; /* List of weak references */
19+
PyObject *func_module; /* The __module__ attribute, can be anything */
20+
21+
/* Invariant:
22+
* func_closure contains the bindings for func_code->co_freevars, so
23+
* PyTuple_Size(func_closure) == PyCode_GetNumFree(func_code)
24+
* (func_closure may be NULL if PyCode_GetNumFree(func_code) == 0).
25+
*/
26+
} PyFunctionObject;
27+
28+
```
29+
30+
PyCodeObject 是在编译时产生的,包含函数的一些静态信息,如 co_consts, co_names, co_code 等,而 PyFunctionObject 则是执行 def 语句时动态产生的,包含 PyCodeObject 以及其它动态的信息,如 func_globals。
31+
32+
一个函数只有一个 PyCodeObject,但是却会产生多个 PyFunctionObject,每次调用函数时都会创建一个新的 PyFunctionObject,该 PyFunctionObject 关联至唯一的 PyCodeObject。
33+
34+
## 函数调用流程
35+
36+
```python
37+
[func_0.py]
38+
def f():
39+
0 LOAD_CONST 0 (code object f)
40+
3 MAKE_FUNCTION 0
41+
6 STORE_NAME 0 (f)
42+
print "Function"
43+
0 LOAD_CONST 1 (“Function”)
44+
3 PRINT_ITEM
45+
4 PRINT_NEWLINE
46+
5 LOAD_CONST 0 (None)
47+
8 RETURN_VALUE
48+
49+
f()
50+
9 LOAD_NAME 0 (f)
51+
12 CALL_FUNCTION 0
52+
15 POP_TOP
53+
16 LOAD_CONST 1 (None)
54+
19 RETURN_VALUE
55+
56+
```
57+
58+
重点指令:MAKE_FUNCTION,CALL_FUNCTION。
59+
60+
```C
61+
case MAKE_FUNCTION:
62+
v = POP(); /* code object */
63+
x = PyFunction_New(v, f->f_globals);
64+
Py_DECREF(v);
65+
/* XXX Maybe this should be a separate opcode? */
66+
if (x != NULL && oparg> 0) {
67+
// oparg 表示 “具有默认值的参数” 的个数
68+
v = PyTuple_New(oparg);
69+
if (v == NULL) {
70+
Py_DECREF(x);
71+
x = NULL;
72+
break;
73+
}
74+
while (--oparg>= 0) {
75+
w = POP();
76+
PyTuple_SET_ITEM(v, oparg, w);
77+
}
78+
err = PyFunction_SetDefaults(x, v);
79+
Py_DECREF(v);
80+
}
81+
PUSH(x);
82+
break;
83+
```
84+
85+
```C
86+
case CALL_FUNCTION:
87+
{
88+
PyObject **sp;
89+
PCALL(PCALL_ALL);
90+
sp = stack_pointer;
91+
#ifdef WITH_TSC
92+
x = call_function(&sp, oparg, &intr0, &intr1);
93+
#else
94+
x = call_function(&sp, oparg);
95+
#endif
96+
stack_pointer = sp;
97+
PUSH(x); // 函数返回值
98+
if (x != NULL)
99+
continue;
100+
break;
101+
}
102+
103+
static PyObject *
104+
call_function(PyObject ***pp_stack, int oparg
105+
#ifdef WITH_TSC
106+
, uint64* pintr0, uint64* pintr1
107+
#endif
108+
)
109+
{
110+
// oparg 是 short 类型,高字节表示 nk(实参中关键字参数个数),低字节表示 na(实参中位置参数个数)
111+
int na = oparg & 0xff;
112+
int nk = (oparg>>8) & 0xff;
113+
int n = na + 2 * nk;
114+
PyObject **pfunc = (*pp_stack) - n - 1;
115+
PyObject *func = *pfunc;
116+
PyObject *x, *w;
117+
118+
/* Always dispatch PyCFunction first, because these are
119+
presumed to be the most frequent callable object.
120+
*/
121+
if (PyCFunction_Check(func) && nk == 0) {
122+
int flags = PyCFunction_GET_FLAGS(func);
123+
PyThreadState *tstate = PyThreadState_GET();
124+
125+
PCALL(PCALL_CFUNCTION);
126+
if (flags & (METH_NOARGS | METH_O)) {
127+
PyCFunction meth = PyCFunction_GET_FUNCTION(func);
128+
PyObject *self = PyCFunction_GET_SELF(func);
129+
if (flags & METH_NOARGS && na == 0) {
130+
C_TRACE(x, (*meth)(self,NULL));
131+
}
132+
else if (flags & METH_O && na == 1) {
133+
PyObject *arg = EXT_POP(*pp_stack);
134+
C_TRACE(x, (*meth)(self,arg));
135+
Py_DECREF(arg);
136+
}
137+
else {
138+
err_args(func, flags, na);
139+
x = NULL;
140+
}
141+
}
142+
else {
143+
PyObject *callargs;
144+
callargs = load_args(pp_stack, na);
145+
READ_TIMESTAMP(*pintr0);
146+
C_TRACE(x, PyCFunction_Call(func,callargs,NULL));
147+
READ_TIMESTAMP(*pintr1);
148+
Py_XDECREF(callargs);
149+
}
150+
} else {
151+
if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL) {
152+
/* optimize access to bound methods */
153+
PyObject *self = PyMethod_GET_SELF(func);
154+
PCALL(PCALL_METHOD);
155+
PCALL(PCALL_BOUND_METHOD);
156+
Py_INCREF(self);
157+
func = PyMethod_GET_FUNCTION(func);
158+
Py_INCREF(func);
159+
Py_DECREF(*pfunc);
160+
*pfunc = self;
161+
na++;
162+
n++;
163+
} else
164+
Py_INCREF(func);
165+
READ_TIMESTAMP(*pintr0);
166+
if (PyFunction_Check(func))
167+
x = fast_function(func, pp_stack, n, na, nk);
168+
else
169+
x = do_call(func, pp_stack, na, nk);
170+
READ_TIMESTAMP(*pintr1);
171+
Py_DECREF(func);
172+
}
173+
174+
/* Clear the stack of the function object. Also removes
175+
the arguments in case they weren't consumed already
176+
(fast_function() and err_args() leave them on the stack).
177+
*/
178+
while ((*pp_stack) > pfunc) {
179+
w = EXT_POP(*pp_stack);
180+
Py_DECREF(w);
181+
PCALL(PCALL_POP);
182+
}
183+
return x;
184+
}
185+
```
186+
187+
`fast_function``PyEval_EvalCodeEx` 的代码太长,就不复制粘贴了。源代码(branch: ch11)有一些注释。
188+
189+
190+
几个和函数参数有关的变量:
191+
192+
- PyCodeObject->co_argcount
193+
194+
形参中参数的个数,不包括 *args 和 **kwargs
195+
196+
- PyCodeObject->co_nlocals
197+
198+
函数局部变量的个数,包括参数个数(co_argcount + *args + **kwargs)
199+
200+
- na
201+
202+
实参中位置参数的个数
203+
204+
- nw
205+
206+
实参中关键字参数的个数
207+
208+
209+
f_localsplus 布局
210+
211+
```
212+
PyFrameObject:
213+
214+
+------------+-------------------+---------------------------+
215+
| | | |
216+
| frame_info | extras | valuestack |
217+
| | | |
218+
+--------------------------------+---------------------------+
219+
| |
220+
<-------------+ f_localsplus +----------------->
221+
| |
222+
223+
extras:
224+
225+
+----------+---------+-------+----------+----------+----------+
226+
| | | | | | |
227+
| pos args | kw args | *args | **kwargs | cellvars | freevars |
228+
| | | | | | |
229+
+----------+---------+-------+----------+----------+----------+
230+
231+
232+
```
233+
234+
cellvars 一般是外层函数使用,存放着被内层嵌套函数引用的变量
235+
236+
freevars 一般是内层嵌套函数使用,存放着对外层函数中变量的引用
237+
238+
## 常见的函数定义和调用
239+
240+
可以使用下面的例子去思考源码中 fast_function和 PyEval_EvalCodeEx的执行流程。
241+
242+
- 没有参数
243+
244+
```py
245+
def f():
246+
print 'hello world'
247+
248+
f()
249+
250+
# co_argcount = 0
251+
# na = 0, nw = 0
252+
```
253+
254+
**符合 fast_function 条件**
255+
256+
- 只有位置参数
257+
258+
```py
259+
def f(a, b):
260+
print 'hello world'
261+
262+
f(1, 2)
263+
# co_argcount = 2
264+
# na = 2, nw = 0
265+
266+
f(a=1, b=2)
267+
# co_argcount = 2
268+
# na = 0, nw = 2
269+
```
270+
271+
**f(1, 2) 符合 fast_function 条件**
272+
273+
**f(a=1, b=2) 不符合 fast_function 条件**
274+
275+
- 位置参数和关键字参数
276+
277+
```py
278+
def f(a, b, c=1, d=2):
279+
print 'hello world'
280+
281+
f(1, 2)
282+
283+
f(1, 2, 3)
284+
285+
f(1, 2, 3, 4)
286+
287+
f(1, 2, c=3)
288+
289+
f(1, 2, d=3)
290+
```
291+
292+
293+
- 可变参数
294+
295+
```py
296+
def f(a, b, *args, **kwargs):
297+
print 'hello world'
298+
299+
f(1, 2)
300+
301+
f(1, 2, 3, 4, c=1, d=2)
302+
```

ch12.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Python虚拟机中的类机制
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
def f():
2+
print 'hello world'
3+
4+
f()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
def f(a, b):
2+
print a, b
3+
4+
f(1, 2)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
def f(a, b):
2+
print a, b
3+
4+
f(1, b=2)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
def f(a, b, *args, **kwargs):
2+
print a, b, args, kwargs
3+
4+
f(1, 2, 3, 4, c=1, d=2)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
def f(a):
2+
v = 'value'
3+
def g():
4+
print v
5+
return g
6+
7+
g = f(1)
8+
g()

codes/python_scripts/get_codeobj_info.py

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,42 @@
1212

1313
codeobj = compile(src, f_name, 'exec')
1414

15-
print 'f_consts:', codeobj.co_consts
16-
print 'code:', codeobj.co_code.encode('hex')
17-
print 'lnotab:', codeobj.co_lnotab
15+
def display(codeobj):
16+
info_keys = [
17+
'co_filename',
18+
'co_name',
19+
'co_firstlineno',
20+
'co_flags',
21+
'co_lnotab',
22+
'co_names',
23+
'co_argcount',
24+
'co_nlocals',
25+
'co_varnames',
26+
'co_consts',
27+
'co_cellvars',
28+
'co_freevars',
29+
'co_code',
30+
'co_stacksize',
31+
]
32+
print 'code obj info:'
33+
for k in info_keys:
34+
v = getattr(codeobj, k)
35+
print k, ':',
36+
if k == 'co_code':
37+
print
38+
print dis.dis(codeobj)
39+
elif k == 'co_consts':
40+
print
41+
for const in v:
42+
if hasattr(const, 'co_code'):
43+
print '----'
44+
display(const)
45+
print '----'
46+
else:
47+
print const
48+
else:
49+
print v
50+
51+
52+
display(codeobj)
1853

19-
print dis.dis(codeobj)

0 commit comments

Comments
 (0)