Python中的函数注释
Python3.0中引入的函数注释添加了一项功能,该功能使您可以向函数参数和返回值添加任意元数据。从python3开始,功能注释已正式添加到python(PEP-3107)中。主要目的是拥有一种将元数据链接到函数参数和返回值的标准方法。
功能注释的基础
让我们了解函数注释的一些基础知识-
函数注释对于参数和返回值都是完全可选的。
函数注释提供了一种在编译时将函数的各个部分与任意python表达式相关联的方法。
PEP-3107不会尝试引入任何类型的标准语义,即使是对于内置类型也是如此。所有这些工作留给第三方库。
语法
简单参数注释
参数的注释采用以下形式-
def foo(x: expression, y: expression = 20): ….
多余参数的注释为-
def foo(**args: expression, **kwargs: expression): …..
对于嵌套参数,注释始终跟随参数名称,而不是直到最后一个括号为止。不需要注释嵌套参数的所有参数。
def foo(x1, y1: expression), (x2: expression, y2: expression)=(None, None)): ……
重要的是要了解python不提供带注释的任何语义。它仅提供用于关联元数据的良好语法支持以及一种简单的访问方式。另外,注释不是必需的。
>>> def func(x:'annotating x', y: 'annotating y', z: int) -> float: print(x + y + z)
在上面的示例中,函数func()接受名为x,y和z的三个参数,最后输出它们的总和。第一个参数x用字符串“annotatingx”注释,第二个参数y用字符串“annotatingy”注释,第三个参数z用int类型注释。返回值用float类型注释。这里的“->”语法用于注释返回值。
输出结果
>>> func(2,3,-4) 1 >>> func('Function','-','Annotation') Function-Annotation
在上面,我们两次调用func(),一次是使用int参数,一次是使用字符串参数。在这两种情况下,func()都会做正确的事情,而注释将被忽略。因此,我们看到注释对函数func()的执行没有影响。
访问功能注释
所有注释都存储在名为__annotations__的字典中,该字典本身是函数的属性-
>>> def func(x:'annotating x', y: 'annotating y', z: int) -> float: print(x + y + z) >>> func.__annotations__ {'x': 'annotating x', 'y': 'annotating y', 'z': <class 'int'>, 'return': <class 'float'>}
正如我们在前面的代码示例中看到的那样,注释不是类型化声明,尽管它们可以肯定地用于此目的,并且它们类似于某些其他语言中使用的键入语法,如下所示-
>>> def func(a: 'python', b: {'category: ' 'language'}) -> 'yep': pass >>> func.__annotations__ {'a': 'python', 'b': {'category: language'}, 'return': 'yep'} >>>
它们是任意表达式,这意味着可以将任意值存储在__annotations__字典中。虽然,它们对python本身没有太大的意义,只是它应该存储值。也就是说,定义参数和返回类型是函数注释的常见用法。
@no_type_check装饰器
如果您发现自己使用的工具假定注释是类型声明,但又想将其用于其他目的,请使用标准的@no_type_check装饰器将函数从此类处理中免除,如下所示:
>>> from typing import no_type_check >>> @no_type_check def func(a: 'python', b: {'category: ' 'language'}) -> 'yep': pass >>>
通常,这不是必需的,因为大多数使用注释的工具都可以识别用于注释的工具。装饰器用于保护模棱两可的情况。
注释作为功能装饰器的输入
注释与修饰符很好地结合在一起,因为注释值是向修饰符提供输入的好方法,并且修饰符生成的包装器是放置可赋予注释含义的代码的好地方。
from functools import wraps def adapted(func): @wraps(func) def wrapper(**kwargs): final_args = {} for name, value in kwargs. items(): adapt = func.__annotations__.get(name) if adapt is not None: final_args[name] = adapt(value) else: final_args[name] = value result = func(**final_args) adapt = func.__annotations__.get('result') if adapt is not None: return adapt(result) return result return wrapper @adapted def func(a: int, b: repr) -> str: return a
因此,经过修饰的装饰器将函数封装在包装器中。该包装器仅接受关键字参数,这意味着即使原始函数可以接受位置参数,也必须通过名称指定它们。
包装函数后,包装程序还将在函数的参数注释中查找适配器,并在将参数传递给实际函数之前应用适配器。
函数返回后,包装器将检查返回值适配器;如果找到一个,它将在最终返回之前将其应用于返回值。
当我们考虑到这里发生的事情的含义时,它们令人印象深刻。实际上,我们已经修改了将参数传递给函数或返回值的含义。
关键字参数
有时,一个方法的一个或多个参数不需要进行任何处理,只需将它们分配给self的属性即可。我们可以使用装饰器和注释使它自动发生吗?当然,我们可以。
from functools import wraps def store_args(func): @wraps(func) def wrapper(self, **kwargs): for name, value in kwargs.items(): attrib = func.__annotations__.get(name) if attrib is True: attrib = name if isinstance(attrib, str): setattr(self, attrib, value) return func(self, **kwargs) return wrapper class A: @store_args def __init__(self, first: True, second: 'example'): pass a = A(first = 5, second = 6) assert a.first == 5 assert a.example == 6