python中的函数是一种对象,它有属于对象的属性。除此以外,函数还能够自定义本身的属性。注意,属性是和对象相关的,和做用域无关。python
自定义函数本身的属性方式很简单。假设函数名称为myfunc,那么为这个函数添加一个属性var1:闭包
myfunc.var1="abc"
那么这个属性var1就像是全局变量同样被访问、修改。但它并非全局变量。函数
能够跨模块自定义函数的属性。例如,在b.py中有一个函数b_func()
,而后在a.py中导入这个b.py模块,能够直接在a.py中设置并访问来自b.py中的b_func()
的属性。spa
import b b.b_func.var1="hello" print(b.b_func.var1) # 输出hello
python函数是一种对象,是对象就会有对象的属性。能够经过以下方式查看函数对象的属性:code
dir(func_name)
例如,有一个属性__name__
,它表示函数的名称:对象
def f(x): y=10 def g(z): return x+y+z return g print(f.__name__) # 输出f
还有一个属性__code__
,这个属性是本文的重点,它表示函数代码对象:内存
print(f.__code__)
输出:作用域
<code object f at 0x0335B180, file "a.py", line 2>
上面的输出结果已经指明了__code__
也是对象,既然是对象,它就有本身的属性:编译器
print( dir(f.__code__) )
如今,就能够看到函数代码对象相关的属性,其中有一类属性都以co_
开头,表示字节码的意思,后文会详细解释这些属性的意义。实际上,并不是只有函数具备这些属性,全部的代码块(code block)都有这些属性。源码
[...省略其它非co_属性... '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']
如何查看这些__code__
的属性?使用f.__code__.co_XXX
便可。因为dir()
返回的是属性列表,因此下面使用循环将co_
开头的属性都输出出来:
for i in dir(f.__code__): if i.startswith("co"): print(i+":",eval("f.__code__."+i))
输出结果:
co_argcount: 1 co_cellvars: ('x', 'y') co_code: b'd\x01\x89\x01\x87\x00\x87\x01f\x02d\x02d\x03\x84\x08}\x01|\x01S\x00' co_consts: (None, 10, <code object g at 0x02FB7338, file "g:/pycode/b.py", line 3>, 'f.<locals>.g') co_filename: g:/pycode/b.py co_firstlineno: 1 co_flags: 3 co_freevars: () co_kwonlyargcount: 0 co_lnotab: b'\x00\x01\x04\x01\x0e\x02' co_name: f co_names: () co_nlocals: 2 co_stacksize: 3 co_varnames: ('x', 'g')
此外,还可使用dis
模块的show_code()
函数来输出这些信息的整理:
import dis def f(x): y=10 def g(z): return x+y+z return g print(dis.show_code(f))
输出结果:
Name: f Filename: g:/pycode/b.py Argument count: 1 Kw-only arguments: 0 Number of locals: 2 Stack size: 3 Flags: OPTIMIZED, NEWLOCALS Constants: 0: None 1: 10 2: <code object g at 0x00A89338, file "g:/pycode/b.py", line 4> 3: 'f.<locals>.g' Variable names: 0: x 1: g Cell variables: 0: x 1: y None
这些属性定义在python源码包的Include/code.h
文件中,若有须要,可自行去查看。
另外,这些属性是代码块(code block)的,不限于函数。但此处以函数为例进行说明。
因为这些属性中涉及到了闭包属性(或者嵌套函数的属性),因此如下面这个a.py文件中的嵌套函数为例:
import dis x=3 def f(a,b,*args,c): a=3 y=10 print(a,b,c,x,y) def g(z): return a+b+c+x+z return g
如下是查看函数f()和闭包函数g()的方式:
# f()的show_code结果 dis.show_code(f) # f()的co_XXX属性 for i in dir(f.__code__): if i.startswith("co"): print(i+":",eval("f.__code__."+i)) # 闭包函数,注意,传递了*args参数 f1=f(3,4,"arg1","arg2",c=5) # f1()的show_code结果 dis.show_code(f1) # f1()的co_XXX属性 for i in dir(f1.__code__): if i.startswith("co"): print(i+":",eval("f1.__code__."+i))
下面将根据上面查看的结果解释各属性:
co_name
函数的名称。
上例中该属性的值为外层函数f和闭包函数g,注意不是f1。
co_filename
函数定义在哪一个文件名中。
上例中为a.py
。
co_firstlineno
函数声明语句在文件中的第几行。即def关键字所在的行号。
上例中f()的行号为3,g()的行号为7。
co_consts
该函数中使用的常量有哪些。python中并无专门的常量概念,全部字面意义的数据都是常量。
如下是show_code()获得的f()中的常量:
Constants: 0: None 1: 3 2: 10 3: <code object g at 0x0326B7B0, file "a.py", line 7> 4: 'f.<locals>.g'
而内层函数g()中没有常量。
co_kwonlyargcount
keyword-only的参数个数。
f()的keyword-only的参数只有c,因此个数为1
g()中没有keyword-only类的参数,因此为0
co_argcount
除去*args
以外的变量总数。其实是除去*
和**
所收集的参数以及keyword-only类的参数以后剩余的参数个数。换句话说,是*
或**
前面的位置参数个数。
f()中属于此类参数的有a和b,因此co_argcount数值为2
g()中只有一个位置参数,因此co_argcount数值为1
co_nlocals
co_varnames
本地变量个数和它们的名称,变量名称收集在元组中。
f()的本地变量个数为6,元组的内容为:('a', 'b', 'c', 'args', 'y', 'g')
g()的本地变量个数为1,元组的内容为:('z',)
co_stacksize
本段函数须要在栈空间评估的记录个数。换句话说,就是栈空间个数。
这个怎么计算的,我也不知道。如下是本示例的结果:
f()的栈空间个数为6
g()的栈空间个数为2
co_names
函数中保存的名称符号,通常除了本地变量外,其它须要查找的变量(如其它文件中的函数名,全局变量等)都须要保存起来。
f()的co_names:
Names: 0: print 1: x
g()的co_names:
Names: 0: x
co_cellvars
co_freevars
这两个属性和嵌套函数(或者闭包有关),它们是互相对应的,因此内容彻底相同,它们以元组形式存在。
co_cellvars
是外层函数的哪些本地变量被内层函数所引用
co_freevars
是内层函数引用了哪些外层函数的本地变量
对外层函数来讲,co_freevars
必定是空元组,对内层函数来讲,co_cellvars
则必定是空元组。
若是知道自由变量的概念,这个很容易理解。
f()的co_cellvars
内容: ('a', 'b', 'c', 'y')
f()的co_freevars
内容: ('a', 'b', 'c', 'y')
co_code
co_flags
co_lnotab
这3个属性和python函数的源代码编译成字节码有关,本文不解释它们。
对于python,一般都认为它是一种解释型语言。但实际上它在进行解释以前,会先进行编译,会将python源代码编译成python的字节码(bytecode),而后在python virtual machine(PVM)中运行这段字节码,就像Java同样。可是PVM相比JVM而言,要更"高级"一些,这个高级的意思和高级语言的意思同样:离物理机(处理机器码)的距离更远,或者说要更加抽象。
源代码被python编译器编译的结果会保存在内存中一个名为PyCodeObject的对象中,当须要运行时,python解释器开始将其放进PVM中解释执行,执行完毕后解释器会"根据须要"将这个编译的结果对象持久化到二进制文件*.pyc
中。下次若是再执行,将首先从文件中加载(若是存在的话)。
所谓"根据须要"是指该py文件是否只运行一次,若是不是,则写入pyc文件。至少,对于那些模块文件,都会生成pyc二进制文件。另外,使用compileall模块,能够强制让py文件编译后生成pyc文件。
但须要注意,pyc虽然是字节码文件,但并不意味着比py文件执行效率更高,它们是同样的,都是一行行地读取、解释、执行。pyc惟一比py快的地方在导入,由于它无需编译的过程,而是直接从文件中加载对象。
py文件中的每个代码块(code block)都有一个属于本身的PyCodeObject对象。每一个代码块除了被编译获得的字节码数据,还包含这个代码块中的常量、变量、栈空间等内容,也就是前面解释的各类co_XXX
属性信息。
pyc文件包含3部分: