打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
《源码探秘 CPython》56. 函数的底层结构

楔子


函数是任何一门编程语言都具备的基本元素,它可以将多个动作组合起来,一个函数代表了一系列的动作。而且在调用函数时会干什么来着,没错,要创建栈帧,用于函数的执行。

那么下面就来看看函数在 C 中是如何实现的,生得一副什么模样。


PyFunctionObject


Python中一切皆对象,函数也不例外。函数这种抽象机制在底层是通过PyFunctionObject对象实现的,位于funcobject.h 中。

我们来实际获取一下这些成员,看看它们在 Python 中是如何表现的。

1)func_code:函数的字节码

def foo(a, b, c):    pass
code = foo.__code__print(code) # <code object foo at ......>print(code.co_varnames) # ('a', 'b', 'c')

2)func_globals:global名字空间

def foo(a, b, c):    pass
name = "古明地觉"print(foo.__globals__) # {......, 'name': '古明地觉'}# 拿到的其实就是外部的 global名字空间print(foo.__globals__ == globals()) # True

3)func_defaults:函数参数的默认值

def foo(name="古明地觉", age=16):    pass
# 打印的是默认值print(foo.__defaults__) # ('古明地觉', 16)

def bar(): pass
# 没有默认值的话,__defaults__ 为 Noneprint(bar.__defaults__) # None

4)func_kwdefaults:只能通过关键字参数传递的 "参数" 和 "该参数的默认值" 组成的字典

def foo(name="古明地觉", age=16):    pass
# 打印为 None,这是因为虽然有默认值# 但并不要求必须通过关键字参数的方式传递print(foo.__kwdefaults__) # None

def bar(*, name="古明地觉", age=16):    pass
print(bar.__kwdefaults__) # {'name': '古明地觉', 'age': 16}

如果在前面加上一个 *,就表示后面的参数必须通过关键字的方式传递。因为如果不通过关键字参数传递的话,那么无论多少个位置参数都会被 * 接收,无论如何也不可能传递给name、age。

我们经常会看到 *args,这是因为我们需要函数调用时传递过来的值,而通过 args 能以元组的形式来拿到这些值。但是这里我们不需要,我们只是希望后面的参数必须通过关键字参数传递,因此前面写一个 * 即可,当然写 *args 也是可以的。

5)func_closure:闭包对象

def foo():    name = "古明地觉"    age = 16
def bar(): nonlocal name nonlocal age
return bar
# 查看的是闭包里面 nonlocal 的值,由于有两个 nonlocal# 所以foo().__closure__ 是一个包含两个元素的元组print(foo().__closure__) """(<cell at 0x000001FD1D3B02B0: int object at 0x00007FFDE559D660>, <cell at 0x000001FD1D42E310: str object at 0x000001FD1D3DA090>)"""
print(foo().__closure__[0].cell_contents) # 16print(foo().__closure__[1].cell_contents) # 古明地觉

注意:查看闭包属性我们使用的是内层函数,不是外层的 foo。

6)func_doc:函数的 doc

def foo():    """    hi,欢迎来到我的小屋    遇见你真好    """    pass 
print(foo.__doc__)"""
hi,欢迎来到我的小屋 遇见你真好 """

6)func_name:函数的名字

def foo(name, age):    pass
print(foo.__name__) # foo

当然不光是函数,方法、类、模块都有自己的名字,

import numpy as np
print(np.__name__) # numpyprint(np.ndarray.__name__) # ndarrayprint(np.array([1, 2, 3]).transpose.__name__) # transpose

7)func_dict:函数的属性字典

因为函数在底层也是由一个类实例化得到的,所以它可以有自己的属性字典,只不过这个字典一般为空。

def foo(name, age):    pass
print(foo.__dict__)  # {}

当然啦,我们也可以整点骚操作:

所以虽然叫函数,但它也是由某个类型对象实现的。

8)func_weakreflist:弱引用列表

Python无法获取这个属性,底层没有提供相应的接口,关于弱引用此处就不深入讨论了。

9)func_module:函数所在的模块

def foo(name, age):    pass
print(foo.__module__) # __main__

类、方法、协程也有 __module__ 属性。

10)func_annotations:类型注解

def foo(name: str, age: int):    pass
# Python3.5 新增的语法,但只能用于函数参数# 而在 3.6 的时候,声明变量也可以使用这种方式# 特别是当 IDE无法得知返回值类型时,便可通过类型注解的方式告知 IDE# 这样就又能使用 IDE 的智能提示了print(foo.__annotations__) # {'name': <class 'str'>, 'age': <class 'int'>}

11)func_qualname:全限定名

def foo():    passprint(foo.__name__, foo.__qualname__)  # foo foo

class A:
def foo(self): passprint(A.foo.__name__, A.foo.__qualname__) # foo A.foo

小结


以上就是函数的底层结构,在Python里面是由 function 实例化得到的。

def foo(name, age):    pass
# <class 'function'> 就是 C 里面的 PyFunction_Typeprint(foo.__class__) # <class 'function'>

但是这个类底层没有暴露给我们,我们不能直接用,因为函数通过 def 创建即可,不需要通过类型对象来创建。

后续会介绍更多关于相关的知识。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
14. Python函数对象的深度解析(第一部分): 函数在底层的数据结构、以及它的创建方式
Python学习:类和实例
温故而知新--day2
理解 Python 装饰器看这一篇就够了 – 码农网
Python面向对象编程:类和实例
四、Python类对象的创建和使用
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服