• Python函数注解


    以下内容基于Python 3x

    涉及的知识前提

    • 建议理解Python装饰器后学习此内容

    函数注解概述

    函数注解可以针对函数的参数、返回值添加元数据,其为注解。

    python是动态语言,变量赋值时不会强制声明类型,且能随时重新赋值。无法像静态编译型语言一样,在编译时发现基本问题。

    函数的参数要求,没有详细的doc string或更新没跟上,以至后续的使用者不能够清晰明白设计者要求的参数类型。以上行为导致的出错率变高,难以使用等问题。

    函数注解可以对参数的要求类型进行注解,对函数返回值进行注解;其只是对做一个辅助性的说明,并不强制进行类型检查

    一些第三方工具(PyCharm)会对已定义的函数注解进行检查标记提示等,在编写代码时同样也可以通过编写一个装饰器,来进行一些检查。

    实际应用

    inspect模块

    注解信息保存在函数的__annotations__属性中,函数的参数检查,一定是在函数实际运行之前,检查传递进来的形参与原有的注解类型是否匹配。

    __annotations__属性是一个字典,对于位置参数的判断,较为复杂,可直接使用inspect模块获取函数签名信息。

    # 注解x和y参数要求为int类型,返回结果为int类型
    # Syntax:
    
    def foo(x: int, y: int) -> int:
        return x + y
    

    查看其__annotations__属性信息:

    def foo(x: int, y: int) -> int:
        return x + y
    
    
    print(type(foo.__annotations__))
    print(foo.__annotations__)
    
    # 输出结果:
    <class 'dict'>
    {'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}
    

    使用inspect模块获取签名信息:

    inspect.signature返回的是一个OrderedDict(有序字典)

    import inspect
    
    
    def foo(x: int, y: int) -> int:
        return x + y
    
    
    sig = inspect.signature(foo)
    print(sig.parameters)
    
    # 输出结果:
    OrderedDict([('x', <Parameter "x:int">), ('y', <Parameter "y:int">)])
    

    使用parameter方法,获取更详细的信息:

    import inspect
    
    
    def foo(x: int , y: int, z=20) -> int:
        return x + y + z
    
    
    sig = inspect.signature(foo)
    par = sig.parameters
    print(par.items())
    print(par["x"].name, "|-|", par["x"].annotation, "|-|", par["x"].default, "|-|", par["x"].empty, "|-|", par["x"].kind)
    print(par["y"].name, "|-|", par["y"].annotation, "|-|", par["y"].default, "|-|", par["y"].empty, "|-|", par["y"].kind)
    print(par["z"].name, "|-|", par["z"].annotation, "|-|", par["z"].default, "|-|", par["z"].empty, "|-|", par["z"].kind)
    
    #输出结果:
    odict_items([('x', <Parameter "x:int">), ('y', <Parameter "y:int">), ('z', <Parameter "z=20">)])
    x |-| <class 'int'> |-| <class 'inspect._empty'> |-| <class 'inspect._empty'> |-| POSITIONAL_OR_KEYWORD
    y |-| <class 'int'> |-| <class 'inspect._empty'> |-| <class 'inspect._empty'> |-| POSITIONAL_OR_KEYWORD
    z |-| <class 'inspect._empty'> |-| 20 |-| <class 'inspect._empty'> |-| POSITIONAL_OR_KEYWORD
    

    parameter输出结果的信息保存在元组中,只读;

    name:获取参数名字;

    annotation:获取参数的注解,有可能没有定义,则为_empty;

    default:返回参数的缺省值,如其中z参数的20即为缺省值;

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

    kind:实参如何绑定到形参。以下是形参的类型:

    • POSITIONAL_ONLY,值必须是位置参数提供的(Python中并没有绝对的位置参数,未实现)。

    • POSITIONAL_OR_KEYWORD,值可以作为关键字或者未知参数提供。

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

    • KEYWORD_ONLY,keyword-only参数,对应*args之后出现的非可变关键字参数。

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

    业务代码

    了解以上方法后,编写一个函数装饰器用来检测传参是否规范。

    • 首先在函数运行之前,获取到函数签名、及签名的parameter信息将parameter信息。
    • kv结构的value转换为列表保存。
    • 使用for循环检查每个对应的parameter内参数(实际传的参数)注解是否不为空并且同时是否不是已知类型(isinstance函数可以检查传的值是否是已知类型,返回True或False,签名中参数的annotation类型为已知的)。
    • 匹配通过,则说明传递的参数和注解要求类型一致,否则则抛出TypeError类型的自定义错误信息。
    import inspect
    import functools
    
    
    def parameter_check(fn):
        @functools.wraps(fn)
        def wrapper(*args, **kwargs):
            sig = inspect.signature(fn)
            parameter = sig.parameters
            value = list(parameter.values())
            for i, par in enumerate(args):
                if value[i].annotation is not value[i].empty and not isinstance(par, value[i].annotation):
                    raise TypeError("Parameter type error")
    
            for k, v in kwargs.items():
                if parameter[k].annotation is not parameter[k].empty and not isinstance(v, parameter[k].annotation):
                    raise TypeError("Parameter type error")
    
            ret = fn(*args, **kwargs)
            return ret
        return wrapper
    
    
    @parameter_check
    def foo(x: int, y: int, z=20) -> int:
        return x + y + z
    
    
    print(foo(1, 50, 29))
    
    # 输出结果:
    80
    
    # 更改传参,查看效果:
    print(foo("hhh", 50, 29))
    
    # 输出结果:
    Traceback (most recent call last):
      File "E:/project/python/test.py", line 29, in <module>
        print(foo("hhh", 50, 29))
      File "E:/project/python/test.py", line 13, in wrapper
        raise TypeError("Parameter type error")
    TypeError: Parameter type error
    

    总结

    通过对函数注解的了解,能够更加规范代码,提高效率,避免出错。在实际应用中亦可方便的展开使用。

    参考链接:PEP 3107 — Function Annotations

  • 相关阅读:
    实现websocket中遇到的恶心问题。
    移动js框架使用报告
    超级难用的wireshark。
    三国演义LBS 20110406 本次清明节解决问题列表。
    【原创意】一个市值估算超亿的创意——愤怒的小猪(谢绝抄袭和冒名顶替)
    一个小游戏 让你感受“如何等待成功”!
    js 游戏引擎 + canvas 入门
    javascript 中的反射
    使用HTML5进行地理位置定位。误差在+500m
    【原创意】新浪微博都感到巨大鸭梨的全新创意 —— 二维码社区"神码"
  • 原文地址:https://www.cnblogs.com/ioops/p/14313386.html
Copyright © 2020-2023  润新知