Python 3的“函数注释”有什么好的用途?

函数注释: PEP-3107

我偶然看到一段演示 Python 3的函数注释的代码片段。这个概念很简单,但是我想不出为什么要在 Python 3中实现它们,或者它们有什么好的用途。也许这样可以启发我?

工作原理:

def foo(a: 'x', b: 5 + 6, c: list) -> max(2, 9):
... function body ...

参数后冒号后面的所有内容都是“注释”,->后面的信息是函数返回值的注释。

Func _ annotions 将返回一个字典:

{'a': 'x',
'b': 11,
'c': list,
'return': 9}

有这个有什么意义吗?

57638 次浏览

我觉得这样挺好的。

出于学术背景,我可以告诉你,注释已经证明了它们对于像 Java 这样的语言启用智能静态分析器是非常有价值的。例如,您可以定义诸如状态限制、允许访问的线程、体系结构限制等等的语义,然后有相当多的工具可以读取这些内容并处理它们,以提供超出您从编译器获得的保证。您甚至可以编写检查前置条件/后置条件的代码。

我觉得在 Python 中特别需要这样的东西,因为它的类型比较弱,但是实际上没有任何构造可以使它直接成为正式语法的一部分。

除了保证之外,注释还有其他用途。我可以看到如何将基于 Java 的工具应用于 Python。例如,我有一个工具可以让你为方法分配特殊的警告,当你调用它们的时候,它会提示你应该阅读它们的文档(例如,假设你有一个方法,它不能用负值来调用,但是从名字来看它不是直观的)。通过注释,我可以在技术上为 Python 编写类似的东西。类似地,如果有正式语法,则可以编写一个工具,该工具根据标记在大型类中组织方法。

Uri 已经给出了一个正确的答案,所以这里有一个不那么严肃的答案: 所以你可以把你的文档串缩短。

只要从我的答案 给你中添加一个好用的具体例子,再加上装饰器,一个简单的多方法机制就可以实现了。

# This is in the 'mm' module


registry = {}
import inspect


class MultiMethod(object):
def __init__(self, name):
self.name = name
self.typemap = {}
def __call__(self, *args):
types = tuple(arg.__class__ for arg in args) # a generator expression!
function = self.typemap.get(types)
if function is None:
raise TypeError("no match")
return function(*args)
def register(self, types, function):
if types in self.typemap:
raise TypeError("duplicate registration")
self.typemap[types] = function


def multimethod(function):
name = function.__name__
mm = registry.get(name)
if mm is None:
mm = registry[name] = MultiMethod(name)
spec = inspect.getfullargspec(function)
types = tuple(spec.annotations[x] for x in spec.args)
mm.register(types, function)
return mm

以及一个使用的例子:

from mm import multimethod


@multimethod
def foo(a: int):
return "an int"


@multimethod
def foo(a: int, b: str):
return "an int and a string"


if __name__ == '__main__':
print("foo(1,'a') = {}".format(foo(1,'a')))
print("foo(7) = {}".format(foo(7)))

这可以通过向装饰器添加类型来完成,如 圭多的原帖所示,但是注释参数本身更好,因为它避免了参数和类型错误匹配的可能性。

注意 : 在 Python 中,可以以 function.__annotations__而不是 function.func_annotations的形式访问注释,因为在 Python 3中已经删除了 func_*样式。

函数注释是由它们组成的。

它们可用于记录:

def kinetic_energy(mass: 'in kilograms', velocity: 'in meters per second'):
...

它们可用于预先条件检查:

def validate(func, locals):
for var, test in func.__annotations__.items():
value = locals[var]
msg = 'Var: {0}\tValue: {1}\tTest: {2.__name__}'.format(var, value, test)
assert test(value), msg




def is_int(x):
return isinstance(x, int)


def between(lo, hi):
def _between(x):
return lo <= x <= hi
return _between


def f(x: between(3, 10), y: is_int):
validate(f, locals())
print(x, y)




>>> f(0, 31.1)
Traceback (most recent call last):
...
AssertionError: Var: y  Value: 31.1 Test: is_int

有关实现类型检查的方法,请参见 http://www.python.org/dev/peps/pep-0362/

我第一次看到注释的时候,我想“太棒了!我终于可以选择进行一些类型检查了!”当然,我没有注意到注释实际上并没有强制执行。

所以我决定选择 编写一个简单的函数修饰符来实现它们:

def ensure_annotations(f):
from functools import wraps
from inspect import getcallargs
@wraps(f)
def wrapper(*args, **kwargs):
for arg, val in getcallargs(f, *args, **kwargs).items():
if arg in f.__annotations__:
templ = f.__annotations__[arg]
msg = "Argument {arg} to {f} does not match annotation type {t}"
Check(val).is_a(templ).or_raise(EnsureError, msg.format(arg=arg, f=f, t=templ))
return_val = f(*args, **kwargs)
if 'return' in f.__annotations__:
templ = f.__annotations__['return']
msg = "Return value of {f} does not match annotation type {t}"
Check(return_val).is_a(templ).or_raise(EnsureError, msg.format(f=f, t=templ))
return return_val
return wrapper


@ensure_annotations
def f(x: int, y: float) -> float:
return x+y


print(f(1, y=2.2))


>>> 3.2


print(f(1, y=2))


>>> ensure.EnsureError: Argument y to <function f at 0x109b7c710> does not match annotation type <class 'float'>

我把它添加到 确保库中。

这个问题已经问了很长时间了,但是问题中给出的例子片段是来自 PEP 3107(如上所述) ,在 PEP 示例的最后,还给出了用例,从 PEP 的角度来回答这个问题;)

下面引自 PEP3107

用例

在讨论注释的过程中,提出了许多用例。这里列出了其中的一些,按照它们所传达的信息类型进行分组。还包括可以使用注释的现有产品和包的示例。

  • 提供打字信息
    • 类型检查([3] ,[4])
    • 让 IDE 显示函数所期望的类型并返回([17])
    • 函数重载/通用函数([22])
    • 外语桥梁([18] ,[19])
    • 适应([21] ,[20])
    • 谓词逻辑函数
    • 数据库查询映射
    • RPC 参数封送处理([23])
  • 其他资料
    • 参数和返回值的文档([24])

有关特定要点(以及它们的参考文献)的更多信息,请参见 PEP

作为一个延迟的答案,我的几个包(marrow.script,WebCore 等)在可用的地方使用注释来声明类型转换(例如,转换从 web 传入的值,检测哪些参数是布尔开关,等等)以及执行额外的参数标记。

Marrow Script 为任意函数和类构建了一个完整的命令行界面,并允许通过注释定义文档、强制转换和回调派生的默认值,还有一个装饰符来支持旧的运行时。我所有使用注释的库都支持表单:

any_string  # documentation
any_callable  # typecast / callback, not called if defaulting
(any_callable, any_string)  # combination
AnnotationClass()  # package-specific rich annotation object
[AnnotationClass(), AnnotationClass(), …]  # cooperative annotation

对文档字符串或类型转换函数的“裸”支持可以更容易地与其他支持注释的库进行混合。(也就是说,有一个使用类型转换的 Web 控制器,该控制器恰好也作为命令行脚本公开。)

编辑后添加: 我还开始使用 打字机包,使用开发时断言进行验证。好处: 当启用“优化”(-O/PYTHONOPTIMIZE env var)运行时,省略了代价可能很高的检查(例如递归) ,这是因为你已经在开发中正确地测试了你的应用程序,所以这些检查在生产中应该是不必要的。

注释可以用于轻松地模块化代码。例如,我正在维护的一个程序的模块可以定义一个方法,比如:

def run(param1: int):
"""
Does things.


:param param1: Needed for counting.
"""
pass

我们可以要求用户输入一个名为“ param1”的东西,它是“ need for count”,并且应该是一个“ int”。最后,我们甚至可以将用户给出的字符串转换为所需的类型,以获得最麻烦的免费体验。

请参阅 我们的函数元数据对象获得一个开源类,它有助于实现这一点,并且可以自动检索所需的值,并将它们转换为 任何所需的类型(因为注释是一种转换方法)。即使 IDE 显示自动补全也是正确的,并且假定类型与注释相符——这是一个完美的匹配。

Python 3.X (仅)还将函数定义泛化为允许 参数和要用对象值注释的返回值 用于扩展名

解释它的 META 数据,更明确地说明函数值。

后,注释被编码为 :value 参数名称之前,默认值为 ->value 论点清单。

它们被收集到函数的 __annotations__属性中,但 Python 本身并不将其视为特殊属性:

>>> def f(a:99, b:'spam'=None) -> float:
... print(a, b)
...
>>> f(88)
88 None
>>> f.__annotations__
{'a': 99, 'b': 'spam', 'return': <class 'float'>}

来源: Python Pocket Reference,第五版

例子:

typeannotations模块提供了一组用于 Python 代码的类型检查和类型推断的工具。它还提供了一组用于对函数和对象进行注释的类型。

这些工具主要被设计用于静态分析器,如 linters、代码完成库和 IDE。此外,还提供了用于进行运行时检查的装饰器。在 Python 中,运行时类型检查并不总是一个好主意,但在某些情况下它可能非常有用。

Https://github.com/ceronman/typeannotations

键入如何帮助编写更好的代码

键入可以帮助你做静态程序分析来捕捉键入错误 在您将代码发送到生产环境并防止您从 有一些工具,比如 mypy,你可以把它们添加到你的 工具箱作为软件生命周期的一部分 通过部分或全部地运行代码库来更正类型。 Mypy 还可以帮助您检测 bug,比如检查“无”类型 当从函数返回值时,键入有助于使您的 而不是使用注释来记录代码,其中 如果在 docstring 中指定类型,则可以使用类型而不使用任何 绩效成本。

干净的 Python: Python 中的优雅编码 ISBN: ISBN-13(pbk) : 978-1-4842-4877-5

PEP 526——可变注释的语法

Https://www.python.org/dev/peps/pep-0526/

Https://www.attrs.org/en/stable/types.html

这是一个很晚的答案,但是 AFAICT,目前最好的函数注释使用是 PEP-0484MyPy。还有来自微软的 权利,它被 VSCode 使用,也可以通过 CLI 获得。

Mypy 是 Python 的一个可选的静态类型检查器。您可以使用 Python 3.5 beta 1(PEP 484)中引入的类型注释标准向 Python 程序添加类型提示,并使用 mypy 静态地检查它们。

用法如下:

from typing import Iterator


def fib(n: int) -> Iterator[int]:
a, b = 0, 1
while a < n:
yield a
a, b = b, a + b

如果你看一下 Cython 的好处列表,一个主要的好处是能够告诉编译器 Python 对象的类型。

我可以想象将来 Cython (或者编译一些 Python 代码的类似工具)将使用注释语法来完成它们的魔术。

尽管这里描述了所有的用法,但是注释的一个可执行的,而且最有可能是执行的用法是用于 类型提示

目前还没有以任何方式强制执行,但是从 PEP 484判断,Python 的未来版本将只允许类型作为注释的值。

引用 注释的现有用途是什么?:

我们确实希望类型提示最终成为注释的唯一用途,但是这需要进一步的讨论,并且在 Python 3.5初始化类型模块之后需要一段时间。在 Python 3.6发布之前,当前的 PEP 将处于临时状态(参见 PEP 411)。可以想到的最快方案是在3.6中引入对非类型提示注释的无声弃用,在3.7中引入完全弃用,并将类型提示声明为 Python 3.8中唯一允许使用的注释。

虽然我还没有在3.6中看到任何无声的嘲讽,但是这个版本很可能会升级到3.7。

因此,尽管可能还有其他一些好的用例,但是如果您不想在将来有这种限制的情况下改变所有内容,那么最好仅仅为了类型提示而保留它们。