Python中的函数注释

Python 3.0中引入的函数注释添加了一项功能,该功能使您可以向函数参数和返回值添加任意元数据。从python 3开始,功能注释已正式添加到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用字符串“ annotating x”注释,第二个参数y用字符串“ annotating y”注释,第三个参数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