10.python编程规范之annotation、函数注解、类型注解、类型检查、inspect、参数类型检查
annotation
Python是动态语言,变量可以随时被赋值并改变类型,也就是说Python的变量是运行时决定的。
def add(x, y):
return x + y
print(add(4, 5))
print(add('mag', 'edu'))
print(add([10], [11]))
print(add(4, 'abc')) # 不到运行时,无法判断类型是否正确
动态语言缺点:
- 难发现:由于不能做任何类型检查,往往到了运行时问题才显现出来,或到了线上运行时才暴露出来
- 难使用:函数使用者看到函数时,并不知道函数设计者的意图,如果没有详尽的文档,使用者只能猜测数据的类型。对于一个新的API,使用者往往不知道该怎么使用,对于返回的类型更是不道该怎么使用
动态类型对类型的约束不强,在小规模开发的危害不大,但是随着Python的广泛使用,这种缺点确实对
大项目的开发危害非常大。
如何解决这种问题呢?
- 文档字符串。对函数、类、模块使用详尽的使用描述、举例,让使用者使用帮助就能知道使用方式。但是,大多数项目管理不严格,可能文档不全,或者项目发生变动后,文档没有更新等等。
- 类型注解:函数注解、变量注解
函数注解
def add(x:int, y:int) -> int:
"""
:param x: int
:param y: int
:return: int
"""
return x + y
help(add)
print(add(4, 5))
print(add('mag', 'edu'))
函数注解
- 3.5版本引入
- 对函数的形参和返回值的类型说明
- 只是对函数形参类型、返回值类型做的辅助的说明,非强制类型约束
- 第三方工具,例如Pycharm就可以根据类型注解进行代码分析,发现隐藏的Bug
- 函数注解存放在函数的属性 annotations 中,字典类型
{'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}
类型注解
i:int = 3 j:str = 'abc'
k:str = 300
print(i, j, k)
类型注解
- 3.6版本引入
- 对变量类型的说明,非强制约束
- 第三方工具可以进行类型分析和推断
类型检查
函数传参经常传错,如何检查?
可以在函数内部写isinstance来判断参数类型是否正确,但是检查可以看做不是业务代码,写在里面就是侵入式代码。那如何更加灵活的检查呢?
- 非侵入式代码
- 动态获取待检查的函数的参数类型注解
- 当函数调用传入实参时,和类型注解比对
能否使用函数的 annotations 属性吗?虽然Python 3.6之后,字典记录了录入序,但是我们还是要认为字段是无顺序的。那如何和按位置传实参对应呢?
使用inspect模块
inspect模块
inspect模块可以获取Python中各种对象信息。
- inspect.isfunction(add),是否是函数
- inspect.ismethod(pathlib.Path().absolute),是否是类的方法,要绑定
- inspect.isgenerator(add)),是否是生成器对象
- inspect.isgeneratorfunction(add)),是否是生成器函数
- inspect.isclass(add)),是否是类
- inspect.ismodule(inspect)),是否是模块
- inspect.isbuiltin(print)),是否是内建对象
还有很多is函数,需要的时候查阅inspect模块帮助
inspect.signature(callable, *, follow_wrapped=True)
- 获取可调用对象的签名
- 3.5增加follow_wrapped,如果使用functools的wraps或update_wrapper,follow_wrapped为True跟进被包装函数的 wrapped ,也就是去获取真正的被包装函数的签名
import inspect
def add(x:int, /, y:int=5, *args, m=6, n, **kwargs) -> int:
return x + y + m + n
sig = inspect.signature(add) # 获取签名
print(sig)
print(sig.return_annotation) # 返回值注解
params = sig.parameters # 所有参数
print(type(params))
print(params) # 有序字典OrderedDict
for k,v in params.items():
print(type(k), k, type(v), v, sep='\t')
inspect.Parameter
- 4个属性
- name,参数名,字符串
- default,缺省值
- annotation,类型注解
- kind,类型
- POSITIONAL_ONLY,只接受仅位置传参
- POSITIONAL_OR_KEYWORD,可以接受关键字或者位置传参
- VAR_POSITIONAL,可变位置参数,对应*args
- KEYWORD_ONLY,对应或者args之后的出现的非可变关键字形参,只接受关键字传参
- VAR_KEYWORD,可变关键字参数,对应**kwargsempty,特殊类,用来标记default属性或者annotation属性为空
- empty,特殊类,用来标记default属性或者annotation属性为空
import inspect
def add(x:int, /, y:int=5, *args, m=6, n, **kwargs) -> int:
return x + y + m + n
sig = inspect.signature(add) # 获取签名
print(sig)
print(sig.return_annotation) # 返回值注解
params = sig.parameters # 所有参数
print(type(params))
print(params) # 有序字典OrderedDict
for k,v in params.items():
print(type(k), k)
t:inspect.Parameter = v # 这一步是多余的,但是t使用了变量注解
print(t.name, t.default, t.kind, t.annotation, sep='\t')
print('-' * 30)
参数类型检查
有如下函数
def add(x, y:int=7) -> int:
return x + y
add(4, 5)
add('mag', 'edu')
请检查用户的输入是否符合参数类型注解的要求
分析:
- 调用时,用户才会传入实参,才能判断实参是否符合类型要求
- 调用时,让用户感觉上还是调用原函数
- 如果类型不符,提示用户
from functools import wraps
import inspect
def prints(wrappend):
@wraps(wrappend)
def wrapper(*args, **kwargs):
params = inspect.signature(wrappend).parameters
# OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y">)])
# key:odict_keys(['x', 'y'])
# values:odict_values([<Parameter "x: int">, <Parameter "y">])
# print(params.values())
for k, v in zip(args, params.values()):
if v.annotation != inspect._empty and not isinstance(k, v.annotation):
print("{} annotation is error".format(k))
for k, v in kwargs.items():
if params[k].annotation != inspect._empty and not isinstance(v, params[k].annotation):
print("{} annotation is error".format(v))
return wrappend
return wrapper
@prints # add=prints(add)
def add(x, y: int = 7) -> int:
return x + y
if __name__ == '__main__':
add(4, 5)
add('songcheng', 'xishan')
作者:宋城西栅
-------------------------------------------
个性签名:独学而无友,则孤陋而寡闻。做一个灵魂有趣的人!
如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!
万水千山总是情,打赏一分行不行,所以如果你心情还比较高兴,也是可以扫码打赏博主,哈哈哈(っ•̀ω•́)っ✎⁾⁾!