楔子
函数是任何一门编程语言都具备的基本元素,它可以将多个动作组合起来,一个函数代表了一系列的动作。而且在调用函数时会干什么来着,没错,要创建栈帧,用于函数的执行。
那么下面就来看看函数在 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__ 为 None
print(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) # 16
print(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__) # numpy
print(np.ndarray.__name__) # ndarray
print(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():
pass
print(foo.__name__, foo.__qualname__) # foo foo
class A:
def foo(self):
pass
print(A.foo.__name__, A.foo.__qualname__) # foo A.foo
小结
以上就是函数的底层结构,在Python里面是由 function 实例化得到的。
def foo(name, age):
pass
# <class 'function'> 就是 C 里面的 PyFunction_Type
print(foo.__class__) # <class 'function'>
但是这个类底层没有暴露给我们,我们不能直接用,因为函数通过 def 创建即可,不需要通过类型对象来创建。
后续会介绍更多关于相关的知识。
联系客服