Python0503-类型注解

函数定义的弊端

  • 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__)  # {'return': <class 'int'>, 'y': <class 'int'>, 'x': <class 'int'>}
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'>}
  • 变量注解
    • Python 3.6引入
      • i : int = 3

业务应用

  • 函数参数类型检查
  • 思路
    • 函数参数的检查,一定是在函数外
    • 函数应该作为参数,传入到检查函数中
    • 检查函数拿到函数传入的实际参数,与形参声明对比
    • 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)) # 函数签名,<Signature类型>注意是尖括号,
print(2, 'params : ', sig.parameters) # OrderedDict
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,可变位置参数,对应*args
    • KEYWORD_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 # ('x', <Parameter "x">) 拆成name='x', param=<Parameter "x">
    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
        # OrderedDict([('x', < Parameter "x" >), ('y', < Parameter "y:int=7" >)])
        values = list(params.values())
        #[<Parameter "x">, <Parameter "y:int=7">]
        for i, p in enumerate(args):  # 可变位置参数判断
            if isinstance(p, values[i].annotation):
                # 实参p和形参(values[i].annotation)声明一致
                print('==')
        for k, v in kwargs.items():  # 可变关键字参数判断
            if isinstance(v, params[k].annotation):
                # 实参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)
    # 这里使用了装饰器,所以注意包装函数的问题
    # wrapper = functools.update_wrapper(fn, wrapper)
    def wrapper(*args, **kwargs):
        # 检查
        sig = inspect.signature(fn)
        params = sig.parameters  # 有序的字典
        # keys = [x for x in params.keys()]  # params.keys()返回一个可迭代对象
        keys = list(params.keys())
        # 这个和keys = [x for x in params.keys()]是一样的代码效果
        values = list(params.values())  # 参数对象列表
        # ['x', 'y'], 这个是keys返回的,和(4, 'abc')一一对应
        # (4, 'abc') args 这个是values返回

        # 可变位置参数
        for i, val in enumerate(args):
            # params[val]  # 这是错误的写法
            # print(values[i].annotation, inspect._empty)
            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():
            # print(params[k].annotation, params[k].empty)
            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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值