python笔记 装饰器强化中

76 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第17天,点击查看活动详情

0 环境

  • 编辑器:pycharm或者vscode
  • 系统版本:windows10
  • python版本:3.9.6

1 怎么利用装饰器实现对函数的类型检查

现在类型注解是给我们人看的,方便传入和返回正常类型值,但是运行时,对其不做检查,我们可以自己手写个检查呢,可以的,一找github上,还真有,分享给大家。写一个装饰器,然后实现对函数的类型检查。这里要用到反射库,叫做inspect

1 认识该反射库

如下代码:先获取count函数的签名,获取parameters,得到是一个OrderedDict类型,它的存在让字典有序,取到具体一个key,比如x,得到默认值和它的注解

import inspect

def count(x: int, y: int) -> int:
    return x + y

def base():
    sign = inspect.signature(count)
    print(sign.parameters)
    print(sign.parameters["x"])
    print(sign.parameters["x"].default)
    print(sign.parameters["x"].annotation)

if __name__ == '__main__':
    base()

image.png

2 做一个类型检查的装饰

如下代码:这里使用了装饰器了,定义了一个typed函数,传入fn(这里是count函数),在wrap的函数中,先利用反射得到count函数里面所有的参数(有序字典),当遇到是位置参数的时候,通过enumerate(args)的方式得到里面的索引和值(count(1,2)里的值,也就是:0 1, 1 2),继续往下执行,这里需要先拿到字典里的每个values,因为它的类型是odict_values,还需要list转化一下,这是整个list的值所以需要通过index,也就说这里说的索引值,得到对应注解类型,然后该值和注解类型比较,是一伙的pass,不是抛出异常。提示你这个类型不对呀,需要改成符合我们要求的类型才可。其实看到去是不是挺简单的。最后返回fn(*args, **kwargs)这里*args和**kwargs一定加上,不然我们count类型检查是完成了,虽然通过了检查,但是最终函数相加没入参,要这类型检查何用。

import functools
import inspect

def typed(fn):
    @functools.wraps(fn)
    def wrap(*args, **kwargs):
        # 检查
        params = inspect.signature(fn).parameters
        # 位置参数检查
        for index, arg in enumerate(args):
            param = list(params.values())[index]
            if not isinstance(arg, param.annotation):
                raise TypeError(f"parameter {param.name} required {param.annotation}, but {type(arg)}")

        return fn(*args, **kwargs)
    return wrap

@typed
def count(x: int, y: int) -> int:
    return x + y

if __name__ == '__main__':
    count(1,2)
    count("1",2)

image.png

2 总结

这里的可变位置的类型检查,其实就是我们定义函数内的一个个类型注解,与我们调用该函数里的值,进行一一对比,若是一致的话,放行,若是不一致,抛出异常,并且告诉它抛出异常的具体错误是什么,这里的list(params.values())[index]是为了拿到该值对应的类型注解,好做下一步的比对,这时候你会发现怪不得.parameters的值是一个有序字典,不然还真不可以这么玩呢。所以通过反射来获取注解还是很方便的。假如还不明白的话,可以略过,毕竟它不会影响到你以后的学习,只是扩展或者说是一种延伸,满足一些好奇罢了。