Python3的“功能注释”有什么用处

时间:2010-06-14 14:25:51

标签: python function annotations python-3.x

功能注释:PEP-3107

我遇到了一段代码,演示了Python3的功能注释。这个概念很简单,但我想不出为什么这些在Python3中实现或者对它们有任何好用。也许SO可以启发我吗?

工作原理:

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}

有这个有什么意义吗?

11 个答案:

答案 0 :(得分:83)

我认为这实际上很棒。

从学术背景来看,我可以告诉你,注释已证明对于为Java这样的语言启用智能静态分析器非常宝贵。例如,您可以定义语义,如状态限制,允许访问的线程,体系结构限制等,并且有很多工具可以读取这些并处理它们以提供超出编译器的保证。你甚至可以编写检查前置条件/​​后置条件的东西。

我认为在Python中特别需要这样的东西,因为它的键入较弱,但实际上没有任何构造使这个直接成为官方语法的一部分。

除了保证之外,注释还有其他用途。我可以看到如何将基于Java的工具应用于Python。例如,我有一个工具,可以让你为方法分配特殊警告,并在你调用它们时给你指示你应该阅读他们的文档(例如,假设你有一个不能用负值调用的方法,但它是名字不直观)。使用注释,我可以为Python编写类似的东西。类似地,如果存在官方语法,则可以编写基于标记在大类中组织方法的工具。

答案 1 :(得分:83)

功能注释就是你对它们所做的。

它们可用于文档:

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

答案 2 :(得分:35)

这是一种迟到的答案,但AFAICT,功能注释的当前最佳用途是PEP-0484MyPy

  

Mypy是Python的可选静态类型检查器。您可以使用即将推出的Python 3.5 beta 1(PEP 484)中引入的类型注释标准向Python程序添加类型提示,并使用mypy进行静态类型检查。

像这样使用:

command = r"elev.cmd {0}\data\nssm.exe install service {1}\data\service.exe".format(targetdir,targetdir)

答案 3 :(得分:23)

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

# 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's original post所示,但注释参数本身更好,因为它避免了参数和类型错误匹配的可能性。

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

答案 4 :(得分:20)

Uri已经给出了正确答案,所以这里不太严肃:所以你可以缩短你的文档。

答案 5 :(得分:12)

我第一次看到注释时,我认为很棒!最后我可以选择进行某种类型检查!&#34;当然,我没有注意到注释实际上并没有被强制执行。

所以我决定write a simple function decorator to enforce them

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'>

我将其添加到Ensure库。

答案 6 :(得分:3)

很长一段时间以来被问到这个问题,但是问题中给出的示例片段(如那里所述)来自PEP 3107,并且在PEP示例结束时也给出了可以回答问题的用例。 PEPs的观点;)

以下内容来自PEP3107

使用案例

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

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

有关特定点(及其参考资料)的更多信息,请参阅PEP

答案 7 :(得分:1)

作为一个延迟答案,我的一些软件包(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

对文档字符串或类型转换函数的“Bare”支持允许更容易地与其他具有注释感知的库混合。 (即,有一个使用类型转换的Web控制器,它也恰好作为命令行脚本公开。)

编辑添加:我还开始使用TypeGuard包使用开发时断言进行验证。好处:在启用“优化”(-O / PYTHONOPTIMIZE env var)的情况下运行时,省略了可能很昂贵(例如递归)的检查,并认为您已正确测试了应用程序开发所以在生产中不需要检查。

答案 8 :(得分:1)

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袖珍参考,第五版

示例:

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

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

https://github.com/ceronman/typeannotations

答案 9 :(得分:1)

尽管此处描述了所有用法,但可强制执行且最有可能强制使用的注释将用于type hints

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

引用What about existing uses of annotations?

  

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

虽然我还没有看到3.6中的任何静默弃用,但很可能会碰到3.7。

所以,即使可能还有一些其他好的用例,如果你不想在未来存在这种限制的情况下改变所有内容,最好将它们仅用于类型提示。

答案 10 :(得分:-2)

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

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