什么是Python3的“function注释”

function注释: PEP-3107

我碰到了一段代码,演示了Python3的函数注释。 这个概念很简单,但我想不出为什么这些是在Python3中实现的,或者它们的好用处。 也许这样可以开导我吗?

怎么运行的:

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

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

foo.func_annotations会返回一个字典:

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

有这个可用的意义是什么?

我觉得这其实很棒。

从学术背景来看,我可以告诉你,注释已经被certificate是非常有价值的,可以为诸如Java之类的语言启用智能静态分析器。 例如,你可以定义语义,比如状态限制,允许访问的线程,体系结构限制等等,然后有相当多的工具可以读取这些语句并对它们进行处理,以提供超出编译器的保证。 你甚至可以写一些检查先决条件/后置条件的东西。

我觉得像Python这样的东西在Python中是特别需要的,因为它的input较弱,但是实际上没有使这个简单的部分和官方语法成为一部分的结构。

还有其他用途来保证无法进行注释。 我可以看到如何将我的基于Java的工具应用到Python。 例如,我有一个工具,可以让你给方法指定特殊的警告,当你给他们打电话时,给你提示你应该阅读他们的文档(例如,假设你有一个方法,不能用负值调用,但是从名字不直观)。 有了注释,我可以写一些类似于Python的东西。 同样,如果有官方语法,则可以编写一个基于标签的大型组织方法的工具。

function注释是你所做的。

它们可以用于文档:

 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/了解实现types检查的方法。;

这是一种较迟的答案,但AFAICT,function注释的最佳使用是PEP-0484和MyPy 。

Mypy是Python的一个可选的静态types检查器。 您可以使用Python 3.5 beta 1(PEP 484)中引入的即将推出的types注释标准,将types提示添加到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 

只是从我的答案中添加一个好用的特定示例,再加上装饰器,可以完成一个简单的multimethods机制。

 # 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))) 

这可以通过Guido的原始文章显示添加types到装饰者来完成,但是注释参数本身更好,因为它避免了参数和types错误匹配的可能性。

注意 :在Python中,您可以通过function.__annotations__而不是function.func_annotations来访问注释,因为在Python 3上删除了func_*样式。

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

我第一次看到注释,我想“太好了,最后我可以select进行一些types的检查!” 当然,我没有注意到注释并没有被强制执行。

所以我决定写一个简单的函数装饰器来执行它们 :

 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引用

用例

在讨论注释的过程中,提出了一些用例。 其中一些是在这里介绍的,按照它们传达的信息分类。 还包括可以使用注释的现有产品和软件包的示例。

  • 提供打字信息
    • types检查([3],[4])
    • 让IDE显示函数期望和返回的types([17])
    • 函数重载/generics函数([22])
    • 外语桥梁([18],[19])
    • 适应([21],[20])
    • 谓词逻辑function
    • 数据库查询映射
    • RPC参数编组([23])
  • 其他信息
    • 参数和返回值的文档([24])

有关具体要点的更多信息(以及参考资料),请参阅PEP 。

作为一个延迟答案,我的一些软件包(marrow.script,WebCore等)使用注释来声明types转换(即从web转换input值,检测哪些参数是布尔开关等)以执行参数的附加标记。

Marrow脚本为任意函数和类build立一个完整的命令行界面,允许通过注释来定义文档,转换和callback派生的默认值,使用修饰器来支持较早的运行时。 我所有使用注释的库都支持这些表单:

 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 

对文档string或types转换function的“裸”支持可以更容易地与其他注释感知的库混合。 (即有一个使用types转换的web控制器,这也正好暴露在命令行脚本中。)

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

 def run(param1: int): """ Does things. :param param1: Needed for counting. """ pass 

我们可以向用户请求一个名为“param1”的东西,它是“需要计数”,应该是一个“int”。 最后,我们甚至可以将用户给出的string转换为所需的types,以获得最简单的免费体验。

看到我们的函数元数据对象的开放源代码类,这有助于这一点,可以自动检索所需的值,并将其转换为任何所需的types(因为注释是一种转换方法)。 即使IDE显示autocompletions正确,并认为types是根据注释 – 一个完美的契合。

Python 3.X(only)也泛化函数定义,以允许参数和返回值用对象值进行注释以用于扩展。

注释编码为: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袖珍参考,第五版

如果你看一下Cython的好处列表,主要的一点是能够告诉编译器Python对象是哪一种types。

我可以设想一个未来,Cython(或编译你的一些Python代码的类似工具)将使用注释语法来实现他们的魔力。

尽pipe在这里描述了所有的用法,但是可执行的和最有可能强制使用的注释将用于types提示 。

目前这还没有被强制执行,但从PEP 484来看,Python的未来版本将只允许types作为注释的值。

引用注释的现有用法呢? :

我们希望types提示最终将成为注释的唯一用途,但是这将需要在使用Python 3.5初始推出打字模块之后进行额外的讨论和弃用期。 在Python 3.6发布之前,当前的PEP将具有暂时的状态(见PEP 411)。 最快的可以想象的scheme将在3.6中引入对非types提示注释的静态弃用,在3.7中完全弃用,并且将types提示声明为Python 3.8中唯一允许使用的注释。

尽pipe在3.6版本中我还没有看到任何沉默的贬低,但是这可能会被撞到3.7。

所以,尽pipe可能还有其他一些很好的用例,但是如果你不希望在将来限制这个限制的时候去改变所有的东西,那么最好只保留它们。