Python之类型注解(增加代码可读性,同时调试起来方便点)


这里是一段防爬虫文本,请读者忽略。
本文原创首发于CSDN,作者IDYS
博客首页:https://blog.csdn.net/weixin_41633902/
本文链接:https://blog.csdn.net/weixin_41633902/article/details/108007811
未经授权,禁止转载!恶意转载,后果自负!尊重原创,远离剽窃!


写在开头的话

  • 请记住:实践是掌握知识的最快方法
  • 如果你只是怀着看看的态度去快速浏览文章,而不去认认真真的把文章里面讲的任何一个知识点去实践一遍,那么你永远也掌握不了它
  • 生命不息,折腾不止!

Python类型注解

00. 函数定义的弊端

  • Python是动态语言,变量随时可以被赋值,且能赋值为不同的类型
  • Python不是静态编译型语言,变量类型是在运行器决定的
  • 动态语言很灵活,但是这种特性也是弊端
def add(x, y):
    return x + y
print(add(4, 5))
print(add('hello', 'world'))
add(4, 'hello')
  • 难发现:由于不做任何类型检查,直到运行期问题才显现出来,或者线上运行时才能暴露出问题
  • 难使用:函数的使用者看到函数的时候,并不知道你的函数的设计,并不知道应该传入什么类 型的数据

01. 函数注解Function Annotations

  • 如何解决这种动态语言定义的弊端呢?
  • 增加文档Documentation String 这只是一个惯例,不是强制标准,不能要求程序员一定为函数提供说明文档
  • 函数定义更新了,文档未必同步更新
def add(x, y):
    ''' 
    :param x:int 
    :param y: int 
    :return: int 
    ''' 
    return x + y
print(help(add))

  • 函数注解
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'))

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

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

if __name__ == "__main__":
    print(add.__annotations__)
    
  • 运行结果
{'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}

  • 变量注解
    • Python 3.6引入
    • i : int = 3

02. 业务应用

  • 函数参数类型检查
  • 思路
    • 函数参数的检查,一定是在函数外
    • 函数应该作为参数,传入到检查函数中
    • 检查函数拿到函数传入的实际参数,与形参声明对比
    • __annotations__属性是一个字典,其中包括返回值类型的声明。假设要做位置参数的判断,无法和字典中的声明对应。使用inspect模块
  • inspet模块
    • 提供获取对象信息的函数,可以检查函数和类、类型检查

03. inspect模块

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

  • 代码演示

import inspect


def add(x: int, y: int, *args, **kwargs) -> int:
    return x + y


if __name__ == "__main__":
    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模块帮助

  • 演示
import inspect
import parameter


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


def my_gen():
    yield 1
    yield 2
    yield 3

def inspect_perform():
    print(inspect.isfunction(add))
    print(inspect.ismethod(add))
    print(inspect.isgenerator((x for x in range(10))))
    print(inspect.isgenerator([x for x in range(10)]))
    print(inspect.isgeneratorfunction((x for x in range(10))))
    print(inspect.isclass(add))
    print(inspect.ismodule(parameter))
    print(inspect.ismodule(inspect))
    print(inspect.isbuiltin(print))
    print(inspect.isgeneratorfunction(my_gen))


if __name__ == "__main__":
    inspect_perform()
  • 运行结果
True
False
True
False
False
False
True
True
True
True

  • Parameter对象
    • 保存在元组中,是只读的
    • name,参数的名字
    • annotation,参数的注解,可能没有定义
    • default,参数的缺省值,可能没有定义
    • empty,特殊的类,用来标记default属性或者注释annotation属性的空值
    • kind,实参如何绑定到形参,就是形参的类型
      • POSITIONAL_ONLY,值必须是位置参数提供 # 未实现
      • POSITIONAL_OR_KEYWORD,值可以作为关键字或者位置参数提供
      • VAR_POSITIONAL,可变位置参数,对应*args
      • KEYWORD_ONLYkeyword-only参数,对应*或者*args之后的出现的非可变关键字参数
      • VAR_KEYWORD,可变关键字参数,对应**kwargs

  • 代码演示
import inspect


def type_perform(x, y: int, z: int = 2, *args, m, k=5, **kwargs) -> int:
    return x + y


sig = inspect.signature(type_perform)
print(sig)

print(sig.parameters)
print(sig.return_annotation)
print(type_perform.__annotations__)

for i, item in enumerate(sig.parameters.items()):
    par, my_type = item
    print(i+1, par, my_type.annotation, my_type.default, my_type.kind)
    print(my_type.default is my_type.empty, end="\n")
    print(my_type.annotation is my_type.empty, end="\n\n")

x = 1
print(sig.parameters["y"].annotation is type(x))
print(type(x) is int)

  • 运行结果
(x, y: int, z: int = 2, *args, m, k=5, **kwargs) -> int
OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y: int">), ('z', <Parameter "z: int = 2">), ('args', <Parameter "*args">), ('m', <Parameter "m">), ('k', <Parameter "k=5">), ('kwargs', <Parameter "**kwargs">)])
<class 'int'>
{'y': <class 'int'>, 'z': <class 'int'>, 'return': <class 'int'>}
1 x <class 'inspect._empty'> <class 'inspect._empty'> POSITIONAL_OR_KEYWORD
True
True

2 y <class 'int'> <class 'inspect._empty'> POSITIONAL_OR_KEYWORD
True
False

3 z <class 'int'> 2 POSITIONAL_OR_KEYWORD
False
False

4 args <class 'inspect._empty'> <class 'inspect._empty'> VAR_POSITIONAL
True
True

5 m <class 'inspect._empty'> <class 'inspect._empty'> KEYWORD_ONLY
True
True

6 k <class 'inspect._empty'> 5 KEYWORD_ONLY
False
True

7 kwargs <class 'inspect._empty'> <class 'inspect._empty'> VAR_KEYWORD
True
True

True
True

04. 业务功能

  • 有函数如下
def add(x, y:int=7) -> int: 
    return x + y 
  • 请检查用户输入是否符合参数注解的要求?

  • 思路

    • 调用时,判断用户输入的实参是否符合要求
    • 调用时,用户感觉上还是在调用add函数
    • 对用户输入的数据和声明的类型进行对比,如果不符合,提示用户

  • 代码演示
import functools
import inspect


def logger(fn):
    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        sig = inspect.signature(fn)
        sig_parameter = sig.parameters
        para_tup = tuple(sig_parameter.keys())
        for i, v in enumerate(args):  # 对
            key = para_tup[i]
            if isinstance(v, sig_parameter[key].annotation):
                print("{}{}{}".format(v, "type is ", sig_parameter[key].annotation))
            else:
                error = "{}\t{}{}".format(v, "type is not ", sig_parameter[key].annotation)
                print(error)
                raise TypeError(error)
        if len(args) + len(kwargs) > len(sig_parameter):  # 判断用户输入的参数是否多了,如果输入的参数多了,那么就调整使其不报错。仍然能够运算
            new_backup = list(args)
            new_backup.extend(kwargs.values())
            args = tuple(new_backup[:2])
            kwargs.clear()
        for kw_key, kw_value in kwargs.items():  # 对kwargs的类型进行判断
            if isinstance(kw_value, sig_parameter[kw_key].annotation):
                print("{}{}{}".format(kw_value, "type is ", sig_parameter[kw_key].annotation))
            else:
                error = "{}{}{}".format(kw_value, "type is not", sig_parameter[kw_key].annotation)
                print(error)
                raise TypeError(error)
        ret = fn(*args, **kwargs)
        return ret
    return wrapper


@logger
def add(x: int, y: int = 8) -> int:
    """

    :param x: int
    :param y: int
    :return: int
    """
    return x + y


if __name__ == "__main__":
    print(add(3, y=3, z=10, h=30))
    print(add(34, y=15))
    print(add(3, 5))
    print(add(2, 5.0))

  • 运行结果
3	type is <class 'int'>
6
34	type is <class 'int'>
15	type is <class 'int'>
49
3	type is <class 'int'>
5	type is <class 'int'>
8
2	type is <class 'int'>
5.0	type is not <class 'int'>
Traceback (most recent call last):
  File "E:/Users/dayuanshuai/PycharmProjects/douban/parameter/demo06.py", line 56, in <module>
    print(add(2, 5.0))
  File "E:/Users/dayuanshuai/PycharmProjects/douban/parameter/demo06.py", line 23, in wrapper
    raise TypeError(error)
TypeError: 5.0	type is not <class 'int'>


写在最后的话:

  • 无论每个知识点的难易程度如何,我都会尽力将它描绘得足够细致
  • 欢迎关注我的CSDN博客,IDYS’BLOG
  • 持续更新内容
    linux基础 | 数据通信(路由交换,WLAN) | Python基础 | 云计算
  • 如果你有什么疑问,或者是难题。欢迎评论或者私信我。你若留言,我必回复!
  • 虽然我现在还很渺小,但我会做好每一篇内容。谢谢关注!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值