Python学习之 ---python参数类型注解

python参数类型注解

  • Python是动态语言,变量随时可以被赋值,且能赋值为不同的类型

  • Python不是静态编译型语言,变量类型是在运行期决定的

  • 动态语言很灵活,但是这种特性也是弊端

def add(x, y): 
    return x + y
print(add(4, 5))
print(add('hello', 'world'))
add(4, 'hello') #int与数字类型不能相加,报错
  • 难发现:由于不做任何类型检查,直到运行期问题才显现出来,或者线上运行时才能暴露出问 题

  • 难使用:函数的使用者看到函数的时候,并不知道你的函数的设计,并不知道应该传入什么类 型的数据

  • 如何解决这种动态语言定义的弊端呢?

    • 增加文档Documentation String
      • 这只是一个惯例,不是强制标准,不能要求程序员一定为函数提供说明文档
      • 函数定义更新了,文档未必同步更新
def add(x:int,y:int):#注解只是注释性作用,不具有执行功能
    """ 
    x:int
    y:int
    return:int
    """  # 也可以使用三个引号进行文档字符串形式标注,但是文档更新不及时
    return x+y
  • 函数注解

    • Python 3.5引入
  • 对函数的参数进行类型注解

    • 对函数的返回值进行类型注解
    • 只对函数参数做一个辅助的说明,并不对函数参数进行类型检查
    • 提供给第三方工具,做代码分析,发现隐藏的bug
    • 函数注解的信息,保存在 annotations 属性中

变量注解

  • Python 3.6引入。注意它也只是一种对变量的说明,非强制 i : int = 3

业务应用

  • 函数参数类型检查

  • 思路

    • 函数参数的检查,一定是在函数外,如要把检查代码侵入到函数中
    • 函数应该作为参数,传入到检查函数中
    • 检查函数拿到函数传入的实际参数,与形参声明对比
    • **__annotations__**属性是一个字典,其中包括返回值类型的声明。假设要做位置参数的判断,无 法和字典中的声明对应。使用inspect模块
  • inspet模块

    • 提供获取对象信息的函数,可以检查函数和类、类型检查

inspect 模块

  • signature(callable),获取签名(函数签名包含了一个函数的信息,包括函数名、它的参数类型、它所在的类和名称空间及其他信息)
import inspect
inspect.isfunction(int)
inspect.signature(add)# 得到函数的签名与函数的注解,包括return值
def add(x:int,y:int,*args,**kwargs)-->int:
    return x+y
sig = inspect.signature(add)# 得到函数的签名与函数的注解,包括return值
print(sig,type(sig))
>>>(x:int, y:int, *args, **kwargs) -> int    <class 'inspect.Signature'> 
#  sig 的类型为 
sig.parameters
>>>mappingproxy({'x': <Parameter "x:int">,
              'y': <Parameter "y:int">,
              'args': <Parameter "*args">,
              'kwargs': <Parameter "**kwargs">})  #  返回一个字典类型,包含所有参数类型的注解
sig.return_annotation # 返回值的注解,存在则输入注解,未给出为inspect_empty
>>>int                #    inspect._empty  
print(sig.parameters['y'], type(sig.parameters['y']))
>>>  y:int      <class 'inspect.Parameter'>  # y的注解类型为 int  
print(sig.parameters['kwargs'],sig.parameters['kwargs'].annotation)
>>>  **kwargs  <class 'inspect._empty'>  # 与上面的y 一样,如果存在则返回注解,不存在则原值返回,和 
操作 描述
inspect.ismethod(add)) 是否是类的方法
inspect.isgenerator(add)) 是否是生成器对象
inspect.isgeneratorfunction(add)) 是否是生成器函数
inspect.isclass(add)) 是否是类
inspect.ismodule(inspect)) 是否是模块
inspect.isbuiltin(print)) 是否是内建对象
inspect.isfunction(add) 是否是函数

还有很多inspect函数,需要的时候查阅inspect模块帮助

  • Parameter对象
    • 保存在元组中,是只读的

    • name,参数的名字

    • annotation,参数的注解,可能没有定义

    • default,参数的缺省值,可能没有定义

    • empty,特殊的类,用来标记default属性或者注释annotation属性的空值

    • kind,实参如何绑定到形参,就是形参的类型

      • POSITIONAL_ONLY,值必须是位置参数提供 # python里不存在position_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) # 有序字典  返回一个包含函数签名的有序字典 ('x', )
print('return : ', sig.return_annotation)  # 返回return 的注释值,不存在这返回empty 
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')  # 判断缺省值是不是 param.empty
>>> 
(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):  #d定义函数 
    def wrapper(*args, **kwargs): 
        sig = inspect.signature(fn)
        params = sig.parameters
        values = list(params.values()) # 将params 的value值送进字典,变为有序的可用索引遍历
        for x,(k,v) in zip(args,params.items()): # 用zip  进行一个个元素遍历 ,并且用到了参数解构
            if  not isinstance(x,v.annotation): # 参数不是声明的报错
                raise Exception('wrong param= {}{}'.format(k, x))
#      for i,p in enumerate(args):   
#         if isinstance(p, values[i].annotation): # 实参和形参声明一致
#             print('==')
#   上下两种方法不同,但结果是一致的,都是遍历字典内的key 
        for k,v in kwargs.items(): # 验证关键字传参的注释值
            if isinstance(v, params[k].annotation): # 实参和形参声明一致
                print('===')
        return fn(*args, **kwargs)
    return wrapper
check(add)(20,10)
check(add)(20,y=10)
check(add)(x=20,y=10)

#思考? 如果当业务需求是参数有注解就要求实参类型与声明的一致,如果没有声明的就比较,如何实现?

import inspect
from inspect import Parameter
def check (fn):
	def wrapper (*args,**kwargs):
		sig = inspect.signature(add)
		params = sig.parameters
		values = list(params.values())
		keys = list(params.keys())
		# for i ,x in enumerate(args):  #print(check(add)(4,5))
		# 	if  not isinstance(x,values[i].annotation):
		# 		raise Exception ('wrong param= {}{}'.format(keys[i],x))
		flag =True
		for x,(k,v) in zip(args,params.items()): # 用zip  进行一个个元素遍历
			if v.annotation != inspect._empty and  not isinstance(x,v.annotation): 
                # 加一个判断即可
				raise Exception('wrong param= {}{}'.format(k, x))
				flag =False
		for k,v in kwargs.items():  #  print(check(add)(4,y=5))
			if params[k].annotation != inspect._empty and not isinstance(v,params[k].annotation):
				raise Exception('wrong param= {}{}'.format(k, v))
				flag =False
		if not  flag:  #  这一段代码则是使参数检查更加完善,
			pass
			return
		else:
			ret =fn(*args,**kwargs)# 参数解构
			return ret
	return wrapper
@check   # add = check(add)=> wrapper(4,5)  #  装饰器 
def add(x :int ,y :int=7 )->int:
	return x+ y
# print(check(add)(4,5))
# print(check(add)(4,y=5))
print(check(add)(x=4,y=5))

参数注解的使用可以为我们在保证函数执行过程中 的准确性,不至于发生错误导致系统崩溃,若存在不符合类型的参数,可以跳转到日志,数据库等中,进行相应的修改便可.很好的解决了参数类型不一致造成的损失.

你可能感兴趣的:(Python学习历程)