第5章:函数-函数内省、提取参数信息、函数注解

了解函数的专属属性

除了 __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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值