函数定义的弊端
Python 是动态语言,变量随时可以被赋值,且能赋值为不同的类型 Python 不是静态编译型语言,变量类型是在运行期决定的 动态语言很灵活,但是这种特性也是弊端
def add( x,y) :
return x + y
print( add( 4,5))
print( add( 'hello' ,'world' ))
print( add( 4,'hello' ) ) 运行错误
难发现:由于不做任何类型检查,直到运行期问题才显现出来,或者线上运行时才能暴露出问题 难使用:函数的使用者看到函数的时候,并不知道你的函数的设计,并不知道应该传入什么类型的参数
如何解决这种动态语言定义的弊端呢?
增加文档 Documentation String
这只是一个惯例,不是强制标准,不能要求程序员一定为函数提供说明文档 函数定义更新了,文档未必同步更新
def add( x,y) :
'' '
:param x: int
:param y: int
:return: int
' ''
return x + y
print( hlep( add))
函数注解 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' ))
函数注解
Pyhton 3.5 引入 对函数的参数进行类型注解 对函数的返回值进行类型注解 只对函数参数做一个辅助的说明,并不对函数参数进行类型检查 提供给第三方工具,做代码分析,发现隐藏的BUG 函数注解的信息,保存在__annotations__属性中 变量注解
Python 3.6 引入。注意它也只是一种对变量的说明,非强制 i : int = 3
业务应用
函数参数类型检查 思路
函数参数的检查,一定是在函数外,如要把检查代码侵入到函数中, 函数应该作为参数,传入到检查函数中 检查函数拿到函数传入的实际参数,与形参声明对比 __annotations__属性是一个字典,其中包括返回值类型的声明。假设要做位置参数的判断,无法和字典中的声明对应。使用 inspect模块 inspect模块
提供获取对象信息的函数,可以检查函数和类、类型检查
def add( x:int,y:int) -> int:
reutrn x + y
请检查用户输入是否符合参数注解的要求,符合的话就执行
import inspect
def check( fn) :
sig = inspect.signature( fn)
param = sig.parameters
def wrapper( *args,**kwargs) :
for x,( k,v) in zip( args,param.items( )) :
if not isinstance( x,v.annotation) and v.annotation != inspect._empty:
print( 'Wrong' )
break
for k,v in kwargs.items( ) :
if not isinstance( v,param[ k] .annotation) and param[ k] .annotation != inspect._empty:
print( 'Wrong' )
break
ret = fn( *args,**kwargs)
return ret
return wrapper
@check
def add( x:int,y:int) :
return x + y
print( add( 4,5))
inspect 模块
signature(callable),获取签名(函数签名包含了一个函数的信息,包括函数名、它的函数类型、它所在的类和名称空间及其他信息)
import inspect
def add( x:int,y:int,*args,**kwargs) -> int:
return x + y
sig = inspect.signature( add)
print( sig,type( sig))
print( 'params:' ,sig.parameters)
print( 'return:' ,sig.return_annotation)
print( sig.paramrters[ 'y' ] ,type( sig.parameters[ 'y' ] ))
print( sig.paramrters[ 'x' ] .annotation)
print( sig.parameters[ 'args' ] )
print( sig.parameters[ 'args' ] .annotation)
print( sig.parameters[ 'kwargs' ] )
print( sig.parameters[ 'kwargs' ] .annotation)
inspect.isfunction(add),是否是函数 inspect.ismethod(add),是否是类的方法 inspect.isgeneratorfunction(add),是否是生成器函数 inspect.isclass(add),是否是类 inspect.ismodule(inspect),是否是模块 inspect.isbuiltin(print),是否是内建对象 还有很多is函数,需要的时候查阅 inspect模块帮助
Parameter对象
保存在元组中,是只读的 name,参数的名字 annotation,参数的注解,可能没有定义 default,参数的缺省值,可能没有定义 empty,特殊的类,用来标记default属性或者注释annotation属性的空值 kind,实参如何绑定到形参,就是形参的类型
POSITIONAL_ONLY,值必须是位置参数提供 POSITIONAL_OR_KEYWORD,值可以作为关键字或者位置参数提供 VAR_POSITIONAL,可变位置参数,对应*args KEYWORD_ONLY,keyword-only参数,对应*或者 *args之后出现的非可变关键字参数 VAR_KEYWORD,可变关键字参数,对应**kwargs
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' )