Python类型注解

注解-annotation

 Python是动态强类型语言,变量可以随时被赋值并改变类型,也就是说Python的变量是运行时决定的

def add(x,y):
    '''
    This is a add function
    x is int 
    y is int 
    '''
    return x + y 
add(4,5)
add(4.0,5)
add([1,2],[3,4])
add('abc',4)#不到运行时是无法判断类型是否正确

动态语言的弊端:

  难发现,由于不能做任何类型的检查,往往到了运行时出问题才显现出来,或到了运行时才暴露出来

  难使用,函数使用者看到函数时,并不知道设计者的意图,如果没有详细的文档,使用者只能是猜测数据的类型,对于一个新的API,使用者往往不知道该怎么使用,对于返回的类型更是不知道该怎么使用

动态类型对类型的约束不强,在小规模的开发危害不大,但是随着Python的广泛使用,这种缺点确实对大项目的开发危害非常大

 

如何解决这种问题?

  文档字符串:对函数,类、模块使用尽可能详细的使用描述、举例,让使用者使用帮助文档就能知道使用方式,但是大多数项目管理不严格,可能导致文档不全,或项目发生变更后文档未更新等

  类型注解:函数注解、变量注解

 

函数注解

字符串文档在编码的过程冲未必同步更新,只是对函数的形参类型、返回值类型的辅助说明,非强制类型约束

def add(x:int,y:int) -> int: #x:int形参注解,-> int 返回值注解
    '''
    This is a add function
    :param x:
    :param y:
    :return:
    '''
    return x + y

print(help(add))#获取帮助文档
print(add(4,y=5))

函数注解:

  Python3.5引入

  对函数的形参和返回值的类型进行说明

  只是对函数的形参类型、返回值类型的辅助说明,非强制类型约束

  第三方工具例如pycharm就可以根据类型注解进行代码分析,发现隐藏bug

  函数注解放在了函数的属性__annotations__中,在有序字典中

print(add.__annotations__)

{'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}  #在Python3.6以后是记录了录入序,但字典实际的无序的

 

变量注解:

i:int = 3
j:str = 'abc'
k:str = 100
print(i,j,k)

#输出
3 abc 100

3.6版本引入,对变量类型的说明,非强制约束

第三方工具可以进行类型分析和推断

 

业务应用,类型检查

函数传参经常传错,如何检查?

  可以在函数内部写isinstance来判断参数类型是否正确,但是检查可以看做不是业务代码,写在里面就是侵入式代码;

  使用装饰器:

    非侵入式代码

    动态获取待检查的函数和参数类型注解

    当函数调用传入实参时,和类型注解对比

inspect模块

inspect模块可以获取Python中各种对象信息

import inspect #导入模块

inspect.isfunction(add) #判断是否是函数
inspect.ismethod(add) #是否是方法
inspect.isgenerator(add) #是否是生成器
inspect.isgeneratorfunction(add) #是否是生成器函数
inspect.isclass(add) #是否是类
inspect.isbuiltin(add) #是否是内嵌函数
......

函数签名:inspect.signature()    signature(callable) 获取签名(函数签名包含了一个函数的信息,包括函数名,参数类型,它所在的类和命名空间及其他信息)

parameter对象:

  name,参数名

  default,缺省值

  annotation,类型注解

  kind,类型:

    POSITION_ONLY 值必须是位置参数,在Python3.8后支持

    POSITION_OR_KEYWORD 值可以作为关键字或者位置参数提供

    VAR_POSITIONAL 可变位置参数,对应*args

    KEYWORD_ONLY 对应* 或者*args之后出现的非可变关键字参数

    VAR_KEYWORD 可变关键字参数,对应**kwargs

import inspect


def add(x:int,y:int) -> int: #分别注明了参数注解和返回值注解都为int类型
    return x + y
print(add.__name__,add.__annotations__,add.__defaults__) #返回函数名,参数注解,参数默认值
#add {'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>} None

print(inspect.isfunction(add)) #判断是否为函数,True

sig = inspect.signature(add)
print(sig) # (x:int, y:int) -> int 函数签名
print(sig.return_annotation) #<class 'int'> 获取返回值注解

parameters = sig.parameters #获取参数,以及参数类型注解
print(parameters) #OrderedDict([('x', <Parameter "x:int">), ('y', <Parameter "y:int">)]) #有序字典

for k,v in parameters.items():
    print(type(k),type(v),k,v,sep='\t')
#输出
# <class 'str'>    <class 'inspect.Parameter'>    x    x:int
# <class 'str'>    <class 'inspect.Parameter'>    y    y:int

for _,v in parameters.items():
    print(v.name,v.annotation,v.default,v.empty,v.kind,sep='+++')
#输出
x+++<class 'int'>+++<class 'inspect._empty'>+++<class 'inspect._empty'>+++POSITIONAL_OR_KEYWORD
y+++<class 'int'>+++<class 'inspect._empty'>+++<class 'inspect._empty'>+++POSITIONAL_OR_KEYWORD

#inspect.signature(add).parameters.items()

    

 有如下函数:

请检查用户的输入是否符合参数类型注解的要求

分析:

  调用时,用户输入才会传入实参,才能判断实参是否符合类型要求

  调用时,让用户感觉上还是调用原来的函数,使用装饰器实现

  如果类型不对,则提醒用户

def add(x:int,y:int=7) -> int:
    '''
    This is a add function
    x is int 
    y is int 
    '''
    return x + y 
add(4,5)
add('abc','de')

 

 NO1:先实现对add函数的参数类型的提取

#先实现对add函数的参数类型的提取
import inspect

def check(fn):
    sig = inspect.signature(fn)
    parameters = sig.parameters
    
    for k,v in parameters.items():
        print(k,v.annotation,v.default,v.name,v.kind)


def add(x:int,y:int=7) -> int:
    '''
    This is a add function
    x is int 
    y is int 
    '''
    return x + y
print(check(add))
add(4,5)

改造成使用装饰器

import inspect

def check(fn):
    def wrapper(*args,**kwargs):
        sig = inspect.signature(fn)
        parameters = sig.parameters
        print(sig,parameters)
        
        for k,v in parameters.items():
            print(k,v.annotation,v.default,v.name,v.kind)
            
        ret = fn(*args,**kwargs)
        return ret 
    return wrapper 

    
@check
def add(x:int,y:int=7) -> int:
    '''
    This is a add function
    x is int 
    y is int 
    '''
    return x + y 

add(4,5)

 

解决按位置传参

import inspect


def check(fn):
    def wrapper(*args,**kwargs):
        sig = inspect.signature(fn)
        parameters = sig.parameters
        print(sig,parameters.values())
        values = list(parameters.values())
        print(values[0])
        print(values[0].annotation)
        
        for i,x in enumerate(args):
            if values[i].annotation is not values[i].empty and isinstance(x,values[i].annotation):
                print("{}={}".format(values[i].name,x),'is ok')
            else:
                print("{}={}".format(values[i].name,x),'is error')
                           
        ret = fn(*args,**kwargs)
        return ret 
    return wrapper 

    
@check
def add(x:int,y:int=7) -> int:
    '''
    This is a add function
    x is int 
    y is int 
    '''
    return x + y 

add(4)

解决关键字传参

import inspect


def check(fn):
    def wrapper(*args,**kwargs):
        sig = inspect.signature(fn)
        parameters = sig.parameters
        values = list(parameters.values())

        
        for i,x in enumerate(args):
            if values[i].annotation is not values[i].empty and isinstance(x,values[i].annotation):
                print("{}={}".format(values[i].name,x),'is ok')
            else:
                print("{}={}".format(values[i].name,x),'is error')
        for k,v in kwargs.items():
            if isinstance(v,parameters[k].annotation)and parameters[k].annotation is not inspect._empty:
                print("{}={}".format(parameters[k].name,v),'is ok')
            else:
                print("{}={}".format(parameters[k].name,v),'is error')
                           
        ret = fn(*args,**kwargs)
        return ret 
    return wrapper 

    
@check
def add(x:int,y:int=7) -> int:
    '''
    This is a add function
    x is int 
    y is int 
    '''
    return x + y 

add(4,y=5)

 

#解决按关键字传参
import inspect
import functools

def check(fn):
    @functools.wraps(fn)
    def wrapper(*args,**kwargs):
        sig = inspect.signature(fn)
        parameters = sig.parameters
        values = list(parameters.values())
        
        for i,x in enumerate(args):
            if values[i].annotation is values[i].empty and  not isinstance(x,values[i].annotation):
                print("{}={}".format(values[i].name,x),'is error')
        for k,v in kwargs.items():
            if not isinstance(v,parameters[k].annotation) and parameters[k].empty is inspect._empty:
                print("{}={}".format(k,v),'is error')
                           
        ret = fn(*args,**kwargs)
        return ret 
    return wrapper 

    
@check
def add(x:int,y:int=7) -> int:
    '''
    This is a add function
    x is int 
    y is int 
    '''
    return x + y 

print(add(4,y=5))
print(add.__name__,add.__doc__)

 

import time
import datetime
import inspect
from functools import wraps

def logger(t=1):
    def _logger(fn):
        @wraps(fn)
        def wrapper(*args,**kwargs):
            start = datetime.datetime.now()
            ret = fn(*args,**kwargs)
            delta = (datetime.datetime.now() - start).total_seconds()
            if delta > t:
                print('The function {} tooks {}s'.format(fn.__name__,delta))
            return ret
        return wrapper
    return _logger
def check(fn):
    def wrapper(*args,**kwargs):
        sig = inspect.signature(fn)
        parmeters = sig.parameters
        values = list(parmeters.values())
        #解决按位置传参
        for i,x in enumerate(args):
            if isinstance(x,values[i].annotation) and values[i].annotation is not values[i].empty:
                print("{}={} is ok".format(values[i].name,x))
            else:
                print("{}={} is error".format(values[i].name,x))
        #解决按关键字传参
        for k,v in kwargs.items():
            if isinstance(v,parmeters[k].annotation) and parmeters[k].annotation is not inspect._empty:
                print("{}={} is ok".format(parmeters[k].name,v))
            else:
                print("{}={} is error".format(parmeters[k].name,v))
        ret = fn(*args,**kwargs)
        return ret
    return wrapper

@logger()
@check
def add(x,y:int) ->int:
    '''
    This is a add function
    :param x:int
    :param y:int
    :return:int
    '''
    time.sleep(2)
    return x + y

add(4,y=5)

#输出
x = 4 is error 
y = 5 is ok 

 

posted @ 2020-04-23 21:37  Alrenn  阅读(435)  评论(1)    收藏  举报