Python函数背后的秘密(1)
只有函数的代码对象不能运行函数
函数在编译后会产生代码对象,它包含了函数的一些静态信息,即那些可以在源代码中看到的信息。比如,若函数中有a = 1表达式,那么符号a和值1就是一种静态信息。
但是函数运行常常需要外部环境提供的变量,比如函数参数的值,必须在调用时动态确定,无法存储在代码对象中,因而解释器就把代码对象与外部环境变量打包在一起,创建一个函数对象。
函数对象(PyFunctionObject)
在程序运行时,当解释器遇到def语句,就会把环境提供的动态信息与代码对象打包成一个函数对象,供之后的调用。函数对象是一个C语言的结构体,位于 Include/funcobject.h 中:
typedef struct {
PyObject_HEAD
PyObject *func_code; // 函数的代码对象
PyObject *func_globals; // 函数的全局名字空间
PyObject *func_defaults; // 参数默认值元组
PyObject *func_kwdefaults; // 只能通过关键字传递的默认值参数
PyObject *func_closure; // 函数的闭包
PyObject *func_doc; // 函数的文档
PyObject *func_name; // 函数名字
PyObject *func_dict; // 函数的__dict__属性
PyObject *func_module;
我们这里只列出了关键部分。结构体中的变量在Python里称做函数的特殊属性,形式上其前后都有双下划线,我们还是举个例子来说明:
g = 0
def foo(x:int,y:str ='默认值',*,z:str ='仅限关键字'):
'''用于说明函数对象的例子'''
a = 1
def bar():
b = 2
print(bar.__qualname__)
return x + a + b
return bar
>>> foo.__code__ # 也就是*func_code,函数foo的代码对象
<code object foo at 0x0000028220CB8EA0, file "E:\code\虚拟机\4.py", line 2>
>>> foo.__globals__ # 全局名字空间,函数名字当然也包含在内
{…, 'g': 0, 'foo': <function foo at 0x0000028220CA1C10>}
>>> foo.__defaults__ # 默认参数值组成的元组
('默认值',)
>>> foo.__kwdefaults__ # 仅限关键字参数组成的字典
{'z': '仅限关键字'}
>>> print(foo.__closure__) # foo的闭包属性,由其自由变量组成的元组
None # 我们会在下面专门介绍闭包
>>> foo.__doc__ # 函数的文档
'用于说明函数对象的例子'
>>> foo.__dict__
{}
>>> foo.x = 1 # 添加普通属性
>>> foo.__dict__
{'x': 1}
>>> foo.__module__
'__main__'
>> foo.__name__ # 函数的简单名字
'foo'
>>> foo.__qualname__ # 全限定名,带了祖先的名字
'foo' # 在顶层,普通名与全限定名并无区别
可以与bar的全限定名比较一下:
>>> bar = foo(8)
>>> bar()
foo.<locals>.bar
11
未完待续
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: