发布于2019-08-22 16:57 阅读(435) 评论(0) 点赞(14) 收藏(5)
类型提示的装饰器。
python是动态类型语言,也就是说一个变量的类型是在运行时决定的,一个变量的类型在应用的生命周期中是可变的。
但是当我们写一个函数时:
def add(x, y):
return x + y
这个时候,就只能用int值作相加,但有时也会出现str + int的这种情况。于是就需要明确函数的类型。
一般在定义的函数中会加上注释:
def add(x, y):
'''
x + y
:param x: int
:param y: int
:return: int
'''
return x + y
这样就可以调用help(add)
来查看这个函数的文档,以确定函数的类型。但是这种方式并不会保证在更改函数后还能实时的更新文档。为了解决这个问题:
def add(x: int, y: int) -> int:
return x + y
此时用help查看如下:
>>> help(add)
Help on function add in module __main__:
add(x: int, y: int) -> int
当然,这只是一个注解而已,并不会强制限定你必须使用int类型,但是在写入参数时会出现警告提示。
如下可查看add的返回参数类型等:
>>> add.__annotations__
{'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}
此外我们也可以使用typing来做类型注解:
import typing
def sum(lst: typing.List[int]) -> int:
ret = 0
for x in lst:
ret += x
return ret
>>> sum.__annotations__
{'lst': typing.List[int], 'return': <class 'int'>}
写一个装饰器,实现函数的类型检查,导入一个库inspect(是一个反射库)
>>> import inspect
>>> sig = inspect.signature(add)
>>> sig.parameters
mappingproxy(OrderedDict([('x', <Parameter "x: int">), ('y', <Parameter "y: int">)]))
# 上面这个是参数字典
>>> sig.parameters['x']
<Parameter "x: int">
# 获取到整个x参数的详细信息,包括类型
>>> param = sig.parameters['x']
>>> param.default
<class 'inspect._empty'>
# 查看x参数的默认参数,如果有就可以获取到
>>> param.annotation
<class 'int'>
# 查看x参数的类型
利用这个库,就可以做一个类型检查,让我们的类型提示,变得真正的类型检查。
(位置参数都在args里面,关键字参数都在kwargs里面)
如下,写出大致模样:
def typed(fn):
@functools.wraps(fn)
def wrap(*args, **kwargs):
# TODO,如何来检查
params = inspect.signature(fn).parameters
# 检查关键字参数
for k, v in kwargs.items():
if not isinstance(v, params[k].annotation):
raise TypeError('parameter {} require {}, but {}'
.format(k, params[k].annotation, type(k)))
# 检查位置参数
for i, arg in enumerate(args):
param = list(params.values())[i]
if not isinstance(arg, param.annotation):
raise TypeError('parameter {} required {}, but {}'
.format(param.name, param.annotation, type(arg)))
return fn(*args, **kwargs)
return wrap
测试一下:
@typed
def add(x: int, y: int) -> int:
return x + y
print(add(1, 2))
# 结果
3
print(add('a', 2))
# 结果
TypeError: parameter x required <class 'int'>, but <class 'str'>
这里再传入字符后发生了类型报错,是符合参数校验逻辑的。
再对关键字参数进行测试:
print(add(y=3, a='a'))
#结果
if not isinstance(v, params[k].annotation): KeyError: 'a'
这里也发生了报错。目前为止,已经实现了这么一个装饰器的最基本逻辑。
下面对函数进行分析:
@functools.wraps没有问题,就是装饰器都需要加上这个,避免函数名和文档出现异常。
然后就是对关键字和位置参数的检查,注释写得很清楚,不同的是方法。
关键字参数比较简单,直接用k, v就可以获取到。
而位置参数就要用到enumerate来帮助我们获取到他的位置,然后通过param.values的位置匹配来确定。
raise就是一个抛出异常的关键字。
再来查看一个例外情况:
@typed
def add(x, y: int) -> int:
return x + y
print(add(1, 1))
# 结果
TypeError: parameter x required <class 'inspect._empty'>, but <class 'int'>
这里并没有给x类型注解,就导致了结果的报错,但是类型注解加不加应该都不能影响代码的正常运行,所有需要对装饰器进行纠正:
def typed(fn):
@functools.wraps(fn)
def wrap(*args, **kwargs):
# TODO,如何来检查
params = inspect.signature(fn).parameters
# 检查关键字参数
for k, v in kwargs.items():
# 对类型注解进行判断:
param = param[k]
if param.annotation != inspect._empty and not isinstance(v, params[k].annotation):
#if not isinstance(v, params[k].annotation):
raise TypeError('parameter {} require {}, but {}'
.format(k, params[k].annotation, type(k)))
# 检查位置参数
for i, arg in enumerate(args):
param = list(params.values())[i]
if param.annotation != inspect._empty and not isinstance(arg, param.annotation):
raise TypeError('parameter {} required {}, but {}'
.format(param.name, param.annotation, type(arg)))
return fn(*args, **kwargs)
return wrap
加上一个对类型注解的判断之后便能解决该问题了param.annotation != inspect._empty
其中inspect._empty
就是判断该参数有没有类型注解。
用装饰器做类型检查,做权限校验,或者注册函数,都可以。
作者:343ueru
链接:https://www.pythonheidong.com/blog/article/53104/6d6f6c9e4578e2ab9fcf/
来源:python黑洞网
任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任
昵称:
评论内容:(最多支持255个字符)
---无人问津也好,技不如人也罢,你都要试着安静下来,去做自己该做的事,而不是让内心的烦躁、焦虑,坏掉你本来就不多的热情和定力
Copyright © 2018-2021 python黑洞网 All Rights Reserved 版权所有,并保留所有权利。 京ICP备18063182号-1
投诉与举报,广告合作请联系vgs_info@163.com或QQ3083709327
免责声明:网站文章均由用户上传,仅供读者学习交流使用,禁止用做商业用途。若文章涉及色情,反动,侵权等违法信息,请向我们举报,一经核实我们会立即删除!