Python类型注解(inspect模块)

函数定义的弊端

Python是动态语言,变量随时可以被赋值,且能赋值为不同的类型,同时Python不是静态编译型语言,变量类型是在运行器决定的,动态语言很灵活,但是这种特性也是弊端。

def add(x, y):
    return x + y

print(add(4, 5))
print(add(‘hello‘, ‘world‘))
add(4, ‘hello‘) #

结果为:
9
helloworld
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-124-8b7c71b24ec5> in <module>
      4 print(add(4, 5))
      5 print(add(‘hello‘, ‘world‘))
----> 6 add(4, ‘hello‘) #

<ipython-input-124-8b7c71b24ec5> in add(x, y)
      1 def add(x, y):
----> 2     return x + y
      3
      4 print(add(4, 5))
      5 print(add(‘hello‘, ‘world‘))

TypeError: unsupported operand type(s) for +: ‘int‘ and ‘str‘

上面的第三个add报错,很多时候,这样的错误很难发现,由于不做任何类型检查,直到运行期问题才显现出来,或者线上运行时才能暴露出问题,而且函数的使用者看到函数的时候,并不知道你的函数的设计,并不知道应该传入什么类型的数据。那应该如何解决这种动态语言定义的弊端呢?

这个时候就需要增加一个文档,documentation string ,当然,这只是一个惯例,并不是强制标准,不能要求程序员一定要为函数提供说明文档。但有时候,函数定义更新了,文档未必同步更新。

def add(x, y):
    ‘‘‘
    :param x: int
    :param y: int
    :return: int
    ‘‘‘
    return x + y

print(help(add))

结果为:

Help on function add in module __main__:

add(x, y)
    :param x: int
    :param y: int
    :return: int

None

函数注解(function annotation)

函数注解可以解决这种动态语言定义的弊端。

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‘))

结果为:

Help on function add in module __main__:

add(x: int, y: int) -> int
    :param x: int
    :param y: int
    :return: int

None
9
magedu

函数注解是Python3.5以后引入的,它是对函数的参数进行类型注解,同时对函数的返回值进行类型注解,它只对函数参数做一个辅助的说明,并不对函数参数进行类型检查。提供给第三方工具,做代码分析,发现隐藏的bug,含住注解的信息,保存在__annotations__属性中。

def add(x:int , y:int) -> int :
    ‘‘‘
    :param x: int
    :param y: int
    :return: int
    ‘‘‘
    return x + y

add.__annotations__

结果为:
{‘x‘: int, ‘y‘: int, ‘return‘: int}

同时在Python3.6以后引入了变量注解,i : int = 3。

i:int =5
print(i)

结果为:
5

业务应用

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

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

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) # OrderedDict有序字典
print(‘return : ‘, sig.return_annotation)
print(sig.parameters[‘y‘], type(sig.parameters[‘y‘]))
print(sig.parameters[‘x‘].annotation)
print(sig.parameters[‘args‘])
print(sig.parameters[‘args‘].annotation)
print(sig.parameters[‘kwargs‘])
print(sig.parameters[‘kwargs‘].annotation)

结果为:

(x: int, y: int, *args, **kwargs) -> int <class ‘inspect.Signature‘>#函数签名
params :  OrderedDict([(‘x‘, <Parameter "x: int">), (‘y‘, <Parameter "y: int">), (‘args‘, <Parameter "*args">), (‘kwargs‘, <Parameter "**kwargs">)])
return :  <class ‘int‘>
y: int <class ‘inspect.Parameter‘>
<class ‘int‘>
*args
<class ‘inspect._empty‘>
**kwargs
<class ‘inspect._empty‘>

inspect.isfunction(add),是否是函数
inspect.ismethod(add)),是否是类的方法
inspect.isgenerator(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‘)

结果为:

(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 check(fn):
    def wrapper(*args,**kwargs):
        #实参检查
        #print(*args,**kwargs)#这句会出错,比如,4,y = 7,解构后相当于y = 7是print函数的关键字参数,但是没有这个关键字参数。
        print(*args,kwargs)

        sig = inspect.signature(add)#这写错了,add应该是fn
        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‘)

        ret = fn(*args,**kwargs)
        return ret
    return wrapper

@check
def add(x:int,y:int=7) ->int:
    return x+y

add(4,8)

结果为:
4 8 {}
(*args, **kwargs)
params :  OrderedDict([(‘args‘, <Parameter "*args">), (‘kwargs‘, <Parameter "**kwargs">)])
return :  <class ‘inspect._empty‘>
~~~~~~~~~~~~~~~~
1 args <class ‘inspect._empty‘> VAR_POSITIONAL <class ‘inspect._empty‘>
True

2 kwargs <class ‘inspect._empty‘> VAR_KEYWORD <class ‘inspect._empty‘>
True

Out[6]:

12

怎么判断?

import inspect

def check(fn):
    def wrapper(*args,**kwargs):
        #实参检查
        #print(*args,**kwargs)#这句会出错,比如,4,y = 7,解构后相当于y = 7是print函数的关键字参数,但是没有这个关键字参数。
        print(*args,kwargs)

        sig = inspect.signature(fn)
        print(sig)
        print(‘params : ‘, sig.parameters)
        print(‘return : ‘, sig.return_annotation)
        print(‘~~~~~~~~~~~~~~~~‘)
        params = sig.parameters#有序字典

       # for param in sig.parameters.values():
           # print(param.name,param)
           # print(param.name, param.annotation, param.kind, param.default) 只是为了参照

        #关键字传参处理
        for k,v in kwargs.items():
            if isinstance(v,params[k].annotation):
                print("==")

        #位置参数处理
        param_list=list(params.keys())
        for i,x in enumerate(args):
            k = param_list[i]
            if isinstance(v,params[k].annotation):
                print("==")    

        ret = fn(*args,**kwargs)
        return ret
    return wrapper

@check
def add(x:int,y:int=7) ->int:
    return x+y

add(4,y=8)
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

check(add)(20,10)

结果为:
==
30

check(add)(20,y=10)
结果为:
===
30

check(add)(y=10,x=20)
结果为:
===
30

业务需求是参数有注解就要求实参类型和声明应该一致,没有注解的参数不比较,如何修改代码?

import inspect

def check(fn):
    def wrapper(*args, **kwargs):
        sig = inspect.signature(fn)
        params = sig.parameters
        values = list(params.values())
        for i,p in enumerate(args):
            param = values[i]
            if param.annotation is not param.empty and not isinstance(p, param.annotation):
                print(p,‘!==‘,values[i].annotation)
        for k,v in kwargs.items():
            if params[k].annotation is not inspect._empty and not isinstance(v, params[k].annotation):
                print(k,v,‘!===‘,params[k].annotation)

        else:
errstr = "{} {} {}".format(v,"is not",params[k].annotation)
print(errstr)
raise TypeError(errstr)

return fn(*args, **kwargs)
    return wrapper
@check
def add(x, y:int=7) -> int:
    return x + y

add(20,10)

结果为:
30

add(20,y=10)
结果为:
30

add(y=10,x=20)
结果为:
30

原文地址:https://www.cnblogs.com/xpc51/p/11710646.html

时间: 2024-10-07 16:45:55

Python类型注解(inspect模块)的相关文章

python 类型注解

函数定义的弊端 python 是动态语言,变量随时可以被赋值,且能赋值为不同类型 python 不是静态编译型语言,变量类型是在运行器决定的 动态语言很灵活,但是这种特性也是弊端 def add(x,y): return x+y print(add(4,5)) print(add('hello','world')) print(add(4,'hello')) #报错,TypeError: unsupported operand type(s) for +: 'int' and 'str' 难发现

python类型注解

function annotation 写法: 使用冒号 : 加类型代表参数类型 默认值参数示例:b: int = 2 使用 -> 加类型代表返回值类型 python解释器运行时并不会检查类型,类型不对也不会抛异常,仅仅是注解而已.示例: def plus(a: int, b: int = 2) -> int: return a + b python 解析器并不会在意类型注解,严格来说这是不对的,Python 会把类型信息放在 __annotations__ 属性中: >>>

Python inspect模块学习

今天发现Python inspect模块中一个有趣的功能, 可以让我们方便地检视Python库中的源代码, 知道模块具体是怎样实现的, 满足了像我这样有偷窥欲的人-.- 那就是inspect中的getsource 它的用法如下: 例如要检视Python的The Zen of Python 我们可以: In [1]: import inspect In [2]: import this The Zen of Python, by Tim Peters Beautiful is better tha

python的inspect模块2

转自:http://blog.csdn.net/mldxs/article/details/8652010 一.inspect模块主要提供了四种用处: (1).对是否是模块,框架,函数等进行类型检查. (2).获取源码 (3).获取类或函数的参数的信息 (4).解析堆栈 使用inspect模块可以提供自省功能,下面是关于自省的一些介绍, 首先通过一个例子来看一下本文中可能用到的对象和相关概念. import sys                  # 模块,sys指向这个模块对象 def fo

python的inspect模块

一.type and members 1. inspect.getmembers(object[, predicate]) 第二个参数通常可以根据需要调用如下16个方法: 返回值为object的所有成员,以(name,value)对组成的列表 inspect.ismodule(object): 是否为模块inspect.isclass(object):是否为类inspect.ismethod(object):是否为方法(bound method written in python)inspect

18、【转载】python中inspect模块

转载自:https://blog.csdn.net/weixin_35955795/article/details/53053762 前言 我在学习到实战Day5 - python教程 - 廖雪峰的官方网站时,遇到了inspect模块,之前对这个inspect模块一无所知啊,所以本着打破砂锅问到底的精神,决定对inspect模块做一些探究. 根据度娘搜到的,inspect模块主要提供了四种用处: (1). 对是否是模块,框架,函数等进行类型检查. (2). 获取源码 (3). 获取类或函数的参数

?Python 3 新特性:类型注解——类似注释吧,反正解释器又不做校验

?Python 3 新特性:类型注解 Crossin ? 上海交通大学 计算机应用技术硕士 95 人赞同了该文章 前几天有同学问到,这个写法是什么意思: def add(x:int, y:int) -> int: return x + y 我们知道 Python 是一种动态语言,变量以及函数的参数是不区分类型.因此我们定义函数只需要这样写就可以了: def add(x, y): return x + y 这样的好处是有极大的灵活性,但坏处就是对于别人代码,无法一眼判断出参数的类型,IDE 也无法

inspect模块详解

inspect模块主要提供了四种用处: (1).对是否是模块,框架,函数等进行类型检查. (2).获取源码 (3).获取类或函数的参数的信息 (4).解析堆栈 使用inspect模块可以提供自省功能,下面是关于自省的一些介绍: 首先通过一个例子来看一下本文中可能用到的对象和相关概念. #coding: UTF-8 import sys # 模块,sys指向这个模块对象 import inspect def foo(): pass # 函数,foo指向这个函数对象 class Cat(object

python基础31[常用模块介绍]

python基础31[常用模块介绍] python除了关键字(keywords)和内置的类型和函数(builtins),更多的功能是通过libraries(即modules)来提供的. 常用的libraries(modules)如下: 1)python运行时服务 * copy: copy模块提供了对复合(compound)对象(list,tuple,dict,custom class)进行浅拷贝和深拷贝的功能. * pickle: pickle模块被用来序列化python的对象到bytes流,从