函数定义的弊端
Python是动态语言,变量随时可以被赋值,且能赋值为不同的类型 Python不是静态编译型语言,变量类型是在运行器决定的 动态语言很灵活,但是这种特性也是弊端
def add (x, y) :
return x + y
print(add(4 , 5 ))
print(add('hello' , 'world' ))
print(add(4 , 'hello' ))
-----------------------------
9
helloworld
TypeError: unsupported operand type(s) for +: 'int' and 'str'
弊端
难发现:由于不做任何类型检查,直到运行期问题才显现出来,或者线上运行时才能暴露出问题 难使用:函数的使用者看到函数的时候,并不知道你的函数的设计,并不知道应该传入什么类型的数据
如何解决这种动态语言定义的弊端呢?
增加文档Documentation String
这只是一个惯例,不是强制标准,不能要求程序员一定为函数提供说明文档 函数定义更新了,文档未必同步更新
def add (x, y) :
'''
:param x: int
:param y: int
:return: int
'''
return x + y
print(help(add))
-------------------
add(x, y)
:param x: int
:param y: int
:return : int
None
函数注解Function Annotations
如果解决这种动态语言定义的弊端呢
def add (x:int , y:int) -> int :
'''
:param x: int
:param y: int
:return: int
'''
return x + y
print(help(add))
print(add(4 , 5 ))
print(add('mag' , 'edu' ))
print(add.__annotations__)
print(add.__annotations__['x' ], type(add.__annotations__['x' ]))
------------------------------------
Help on function add in module __main__:
add(x:int, y:int) -> int
:param x: int
:param y: int
:return : int
None
9
magedu
{'y' : <class 'int '>, 'x ': <class 'int '>, 'return ': <class 'int '>}
<class 'int '> <class 'type '>
函数注解说明
Python 3.5引入 对函数的参数进行类型注解 对函数的返回值进行类型注解 只对函数参数做一个辅助的说明,并不对函数参数进行类型检查 提供给第三方工具,做代码分析,发现隐藏的bug 函数注解的信息,保存在annotations 属性中
print(add.__annotations__)
{'y' : <class 'int '>, 'x ': <class 'int '>, 'return ': <class 'int '>}
业务应用
函数参数类型检查 思路
函数参数的检查,一定是在函数外 函数应该作为参数,传入到检查函数中 检查函数拿到函数传入的实际参数,与形参声明对比 annotations 属性是一个字典,其中包括返回值类型的声明。假设要做位置参数的判断,无法和字典中的声明对应。使用inspect模块 inspet模块
提供获取对象信息的函数,可以检查函数和类、类型检查
inspect模块
signature
signature(callable),获取签名(函数签名包含了一个函数的信息,包括函数名、它的参数类型、它所在的类和名称空间及其他信息)
import inspect
def add (x:int, y:int, *args,**kwargs) -> int :
return x + y
sig = inspect.signature(add)
print(1 , sig, type(sig))
print(2 , 'params : ' , sig.parameters)
print(3 , 'return : ' , sig.return_annotation)
print(4 , sig.parameters['y' ], type(sig.parameters['y' ]))
print(5 , sig.parameters['x' ].annotation)
print(6 , sig.parameters['args' ])
print(7 , sig.parameters['args' ].annotation)
print(8 , sig.parameters['kwargs' ])
print(9 , sig.parameters['kwargs' ].annotation)
-----------------------------------------
1 (x:int, y:int, *args, **kwargs) -> int <class 'inspect .Signature '>
2 params : OrderedDict([('x' , <Parameter "x:int" >), ('y' , <Parameter "y:int" >), ('args' , <Parameter "*args" >), ('kwargs' , <Parameter "**kwargs" >)])
3 return : <class 'int '>
4 y : int <class 'inspect .Parameter '>
5 <class 'int '>
6 *args
7 <class 'inspect ._empty '>
8 **kwargs
9 <class 'inspect ._empty '>
inspect.is函数
inspect.isfunction(add),是否是函数 inspect.ismethod(add)),是否是类的方法 inspect.isgenerator(add)),是否是生成器对象 inspect.isgeneratorfunction(add)),是否是生成器函数 inspect.isclass(add)),是否是类 inspect.ismodule(inspect)),是否是模块 inspect.isbuiltin(print)),是否是内建对象 还有很多is函数,需要的时候查阅inspect模块帮助
print(1 , inspect.isfunction(add))
print(2 , inspect.ismethod(add))
print(3 , inspect.isgenerator(add))
print(4 , inspect.isgeneratorfunction(add))
print(5 , inspect.isclass(add))
print(5 , inspect.ismodule(inspect))
print(6 , inspect.isbuiltin(print ))
----------------------------------
1 True
2 False
3 False
4 False
5 False
5 True
6 True
Parameter对象
保存在元组中,是只读的 name,参数的名字 annotation,参数的注解,可能没有定义 default,参数的缺省值,可能没有定义 empty,特殊的类,用来标记default属性或者注释annotation属性的空值 kind,实参如何绑定到形参,就是形参的类型
POSITIONAL_ONLY,值必须是位置参数提供 POSITIONAL_OR_KEYWORD
,值可以作为关键字或者位置参数提供VAR_POSITIONAL
,可变位置参数,对应*argsKEYWORD_ONLY
,keyword-only参数,对应*或者*args之后的出现的非可变关键字参数VAR_KEYWORD
,可变关键字参数,对应**kwargs
inspect举例
import inspect
def add (x, y:int=7 , *args, z, t=10 , **kwargs) -> int :
return x + y
sig = inspect.signature(add)
print(sig)
print('params' , sig.parameters)
print('return' , sig.return_annotation)
print('~~~~~~~~~~~~~~~~~~~~~~~~' )
for i, item in enumerate(sig.parameters.items()):
name, param = item
print(i+1 , name, param.annotation, param.kind, param.default)
print(param.default is param.empty, end='\n\n' )
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(x, y:int=7 , *args, z, t=10 , **kwargs) -> int
params OrderedDict([('x' , <Parameter "x" >), ('y' , <Parameter "y:int=7" >), ('args' , <Parameter "*args" >), ('z' , <Parameter "z" >), ('t' , <Parameter "t=10" >), ('kwargs' , <Parameter "**kwargs" >)])
return <class 'int '>
~~~~~~~~~~~~~~~~~~~~~~~~
1 x <class 'inspect ._empty '> POSITIONAL_OR_KEYWORD <class 'inspect ._empty '>
True
2 y <class 'int '> POSITIONAL_OR_KEYWORD 7
False
3 args <class 'inspect ._empty '> VAR_POSITIONAL <class 'inspect ._empty '>
True
4 z <class 'inspect ._empty '> KEYWORD_ONLY <class 'inspect ._empty '>
True
5 t <class 'inspect ._empty '> KEYWORD_ONLY 10
False
6 kwargs <class 'inspect ._empty '> VAR_KEYWORD <class 'inspect ._empty '>
True
业务应用
def add (x, y:int=7 ) -> int :
return x + y
请检查用户输入是否符合参数注解的要求? 思路
调用时,判断用户输入的实参是否符合要求 调用时,用户感觉上还是在调用add函数 对用户输入的数据和声明的类型进行对比,如果不符合,提示用户
import inspect
def add (x, y: int=7 ) -> int :
return x + y
def check (fn) :
def wrapper (*args, **kwargs) :
sig = inspect.signature(fn)
params = sig.parameters
values = list(params.values())
for i, p in enumerate(args):
if isinstance(p, values[i].annotation):
print('==' )
for k, v in kwargs.items():
if isinstance(v, params[k].annotation):
print('===' )
return fn(*args, **kwargs)
return wrapper
print(check(add)(20 , 10 ))
print(check(add)(20 , y=10 ))
print(check(add)(y=10 , x=20 ))
---------------------------------------------
==
30
===
30
===
30
业务应用思考
业务需求是参数有注解就要求实参类型和声明应该一致,没有注解的参数不比较,如何修改代码?
业务应用代码改进
import inspect
import functools
def check (fn) :
@functools.wraps(fn)
def wrapper (*args, **kwargs) :
sig = inspect.signature(fn)
params = sig.parameters
keys = list(params.keys())
values = list(params.values())
for i, val in enumerate(args):
if values[i].annotation \
is not \
inspect._empty \
and \
isinstance(val, values[i].annotation):
"""
def add(x, y: int = 7) -> int:
print(values[i].annotation, inspect._empty)
x没有注解
<class 'inspect._empty'> <class 'inspect._empty'> = False
y有注释
<class 'int'> <class 'inspect._empty'> = Ture
values[i].empty 同等于 inspect._empty
"""
print(keys[i], '==' , val, values[i].annotation)
for k, v in kwargs.items():
if params[k].annotation \
is not \
params[k].empty \
and \
isinstance(v, params[k].annotation):
"""
def add(x: int, y) -> int:
print(params[k].annotation, params[k].empty)
x有注释
<class 'int'> <class 'inspect._empty'> = Ture
y没有注解
<class 'inspect._empty'> <class 'inspect._empty'> = False
params[k].empty 同等于 inspect._empty
"""
print(k, '===' , v, params[k].annotation)
ret = fn(*args, **kwargs)
return ret
return wrapper
@check # add = check(add)
def add (x, y: int=7 ) -> int :
return x + y
print(add(20 , 10 ))
print(add(20 , y=10 ))
print(add(y=10 , x=20 ))
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
y == 10 <class 'int '>
30
y === 10 <class 'int '>
30
y === 10 <class 'int '>
30