了解函数的专属属性
除了 __doc__,函数对象还有很多属性,使用 dir 内置函数可以查看其他函数具有的属性:
def func():
pass
# 使用 dir 内置函数查看其他函数具有的属性
print(dir(func))
# ['__annotations__', '__call__', '__class__', '__closure__', '__code__',
# '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__',
# '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__',
# '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__',
# '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__',
# '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
其中大多数属性是 Python 对象共有的,本节讨论与把函数视作对象相关的几个属性,先从 __dict__ 开始。
与自定义类一样,函数使用 __dict__ 属性存储赋予它的用户属性,这相当于一种基本注解:
def func():
pass
# func 函数使用 __dict__ 存储赋予它的用户属性
func.short_desc = 'Customer Function'
print(func.__dict__) # {'short_desc': 'Customer Function'}
下面重点说明函数专有而用户定义的一般对象没有的属性,利用两个属性集合的差集便能得到函数专有属性列表:
def func():
pass
class C:
pass
# 计算出专属与函数的属性
li = sorted(set(dir(func)) - set(dir(C)))
print(li)
# ['__annotations__', '__call__', '__closure__', '__code__', '__defaults__',
# '__get__', '__globals__', '__kwdefaults__', '__name__', '__qualname__']
"""
# 用户定义的函数的属性:
# 名称 类型 说明
# __annotations__ dict 参数和返回值的注解
# __call__ method-wrapper 实现 () 运算符,即可调用对象协议
# __closure__ tuple 函数闭包,即自由变量的绑定,通常是 None
# __code__ code 编译成字节码的函数元数据和函数定义体
# __defaults__ tuple 形式参数的默认值
# __get__ method-wrapper 实现只读描述符协议
# __globals__ dict 函数所在模块中的全局变量
# __kwdefaults__ dict 仅限关键字形式参数的默认值
# __name__ str 函数名称
# __qualname__ srt 函数的限定名称,如 Random.choice
"""
内省函数,获取函数参数的信息
HTTP 微框架 Bobo 中有个使用函数内省的例子:
import bobo
@bobo.query('/')
def hello(person):
return f'Hello {person}'
上面这段代码中,Bobo 会内省 hello 函数,发现它需要一个名为 person 的参数,然后从请求中获取这个名字对应的参数,将其传递给 hello 函数,在这个过程中,程序员根本不用触碰请求对象。
假如我们在本地启动这个服务,然后访问服务地址:http://127.0.0.1:8080/,然后我们看到的是一条报错信息: “Missing form variable person”,这是因为 Bobo 通过内省 hello 函数知道它需要一个 person 参数,但是请求中并没有发现这个参数;然后我们再访问 http://127.0.0.1:8080/?person=Tom,就会收到正确的响应了: “Hello Tom” 。
那么 Bobo 具体是怎么发现 hello 是需要 person 参数的呢?
这是因为函数对象有个 __defaults__ 属性,它的值是一个元组,里面保存着这个函数的位置参数和关键字参数的默认值,仅限关键字参数则保存在 __kwdefaults__ 属性中。还有,参数的名称保存在 __code__ 属性中,它的值是 code 对象的引用,本身也有很多属性;
下面我们来看下如何利用这几个属性来内省函数参数:
def demo(test, max_len=80, *, value=100):
end = 0
print(test, end, max_len, value)
# 提取函数参数信息
# 获取位置参数和关键字参数的默认值,如没有默认值则不显示
print(demo.__defaults__) # (80,)
# 获取仅限关键字参数的默认值,如没有默认值则为 None
print(demo.__kwdefaults__) # {'value': 100}
print(demo.__code__) # # <code object demo at 0x10445d6f0, ...>
# 获取函数的参数名,但是函数内部定义的变量也显示出来了
print(demo.__code__.co_varnames) # ('test', 'max_len', 'value', 'end')
# 获取函数的位置参数和关键字参数的个数
print(demo.__code__.co_argcount) # 2
# 获取函数仅限关键字参数的个数
print(demo.__code__.co_kwonlyargcount) # 1
# 1. 根据上面的属性,先获取函数的参数个数,
# 2. 然后根据参数个数从参数元组中取出真正的参数,
# 3. 再然后将获取到的真正的参数元组倒序排列(有默认值的必定是在无默认值的后面),
# 4. 再和默认值一一匹配,匹配不到值就便是没有默认值,最终得到了这个函数的所有参数和参数默认值;
用 inspect 模块提取函数签名,提取参数信息(参数名、默认值、参数注解):
from inspect import signature
# 返回 Signature 对象,它有一个 parameters 属性,这是一个有序字典,把参数名和 Signature 对象对应起来;
sig = signature(demo)
# 直接显示函数的参数和默认值
print(sig) # (test: int, max_len: int = 80, *, value: str = '100')
# 获取函数的参数
print(sig.parameters)
# OrderedDict([('test', <Parameter "test: int">),
# ('max_len', <Parameter "max_len: int = 80">),
# ('value', <Parameter "value: str = '100'">)])
for name, param in sig.parameters.items():
# 打印参数名、参数类型、默认值、参数注解
print(name, param.kind, param.default, param.annotation)
# test POSITIONAL_OR_KEYWORD <class 'inspect._empty'> <class 'int'>
# max_len POSITIONAL_OR_KEYWORD 80 <class 'int'>
# value KEYWORD_ONLY 100 <class 'str'>
# inspect._empty: 代表没有默认值,没有和 None 不一样。
# POSITIONAL_OR_KEYWORD:代表可以用位置参数和关键字参数进行传参
# POSITIONAL_ONLY:仅支持位置参数传参
# KEYWORD_ONLY:仅支持关键字参数传参
# VAR_POSITIONAL:位置参数元组
# VAR_KEYWORD:关键字参数字典
函数注解
Python3 提供了一种句法,用于为函数声明中的参数和返回值附加元数据,但是 Python 并不会强制检查验证类型,仅仅会把注解存在函数的 __annotations__ 属性中,注解只是元数据,可以供 IDE、框架、装饰器使用。
使用注解和提取注解:
"""
Python3 提供了一种句法,用于为函数声明中的参数和返回值附加元数据;
Python 不做强制,不做检查,不做验证,注解只是元数据,可以提供给 IDE 、框架、装饰器等工具用。
"""
# 1. 函数声明中的各个参数可以在 : 之后增加注解,注解支持表达式。
# 2. 如果有默认值,注解放在参数名和 = 号之间。
# 3. 如果有返回值,在 () 和 末尾的 : 之间添加 -> 和一个注解表达式。
# 4. 注解中最常用的类型是 类(str、int、自定义类) 和 字符串('int > 0')。
def demo(text: str, max_len: 'int > 0' = 10) -> str:
return text
# Python 不会对注解做任何处理,只是存储在函数的 __annotations__(是个字典) 属性中,
print(demo.__annotations__) # {'text': <class 'str'>, 'max_len': 'int > 0', 'return': <class 'str'>}
# 从函数中提取注解
from inspect import signature
sig = signature(demo)
# sig 是返回的 Signature 对象,它有一个 return_annotation 属性,里面包含了参数名、返回值和注解的对应关系
print(sig.return_annotation) # {'text': <class 'str'>, 'max_len': 'int > 0', 'return': <class 'str'>}
# parameters 属性的元素也有一个 annotation 属性,是这个参数的注解
for param in sig.parameters.values():
print(param.annotation)
# <class 'str'>
# <class 'str'>
# int > 0