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 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!