Python3的"功能注释"有什么用处

ags*_*ala 154 python annotations function python-3.x

功能注释:PEP-3107

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

这个怎么运作:

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

参数后面后面的所有内容都是"注释",后面的信息->是函数返回值的注释.

foo.func_annotations将返回一个字典:

{'a': 'x',
 'b': 11,
 'c': list,
 'return': 9}
Run Code Online (Sandbox Code Playgroud)

有这个有什么意义?

Uri*_*Uri 89

我认为这实际上很棒.

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

我认为在Python中特别需要这样的东西,因为它的键入较弱,但实际上没有任何构造使得这个简单明了并且是官方语法的一部分.

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

  • ISTM这些理论上的好处只有在标准库和第三方模块都使用函数注释并使用具有一致含义并使用经过深思熟虑的注释系统时才能实现.直到那一天(永远不会到来),Python函数注释的主要用途将是其他答案中描述的一次性用法.目前,您可以忘记智能静态分析器,编译器保证,基于Java的工具链等. (32认同)
  • 快进到2015年,https://www.python.org/dev/peps/pep-0484/和http://mypy-lang.org/开始证明所有反对者都是错误的. (8认同)
  • 即使没有使用函数注释的所有内容,您仍然可以在代码中使用它们进行静态分析,这些代码在其输入上具有它们,并且调用其他类似注释的代码.在更大的项目或代码库中,这仍然是执行基于注释的静态分析的非常有用的代码体. (3认同)
  • @DustinWyatt 我很高兴这个预测是错误的 :-) 我们确实从 PEP 484 和一个带有 *typeshed* 的大部分注释标准库中获得了标准化类型。然而,OP 对 Java 风格工具的愿望清单大多尚未实现。 (2认同)

Ray*_*ger 87

函数注释就是你对它们的看法.

它们可用于文档:

def kinetic_energy(mass: 'in kilograms', velocity: 'in meters per second'):
     ...
Run Code Online (Sandbox Code Playgroud)

它们可用于前置条件检查:

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
Run Code Online (Sandbox Code Playgroud)

另请参阅http://www.python.org/dev/peps/pep-0362/以获取实现类型检查的方法.

  • 这比文档的文档字符串或函数中的显式类型检查更好吗?这似乎无缘无故地使语言复杂化. (18认同)
  • @endolith我们当然可以不使用函数注释.它们只是提供访问注释的标准方法.这使得它们可以访问help()和工具提示,并使它们可用于内省. (10认同)
  • 您可以创建"Mass"和"Velocity"类型,而不是传递数字. (4认同)

Dus*_*att 41

这是一个迟到的答案,但AFAICT,功能注释目前最好的用途是PEP-0484MyPy.

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
Run Code Online (Sandbox Code Playgroud)

  • 这可能是目前最好的答案. (3认同)
  • 更多示例[Mypy示例](http://www.mypy-lang.org/examples.html)和此处[如何从类型提示中获益](http://blog.pirx.ru/media/files/ 2015 /类型提示通话/类型hinting.html#1) (2认同)

Muh*_*uri 23

我想补充从我的回答很好地利用的一个具体的例子在这里,加上装饰可以做的多方法的简单机制.

# 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
Run Code Online (Sandbox Code Playgroud)

和使用的一个例子:

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)))
Run Code Online (Sandbox Code Playgroud)

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

:在Python中,你可以访问注解function.__annotations__,而不是function.func_annotations因为func_*风格是关于Python 3去除.

  • 有趣的应用程序,虽然我担心`function = self.typemap.get(types)`在涉及子类时不起作用.在这种情况下,您可能需要使用`isinnstance`循环遍历`typemap`.我想知道[`@overload`](https://pypi.python.org/pypi/overload)是否正确处理了这个问题 (2认同)

JAB*_*JAB 20

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

  • 我不建议使用注释作为文档字符串中Args:section或@param行或类似内容的替换(无论您选择使用何种格式).虽然文档注释是一个很好的例子,但它会玷污注释的潜在力量,因为它可能妨碍其他更强大的用途.此外,您不能在运行时省略注释以减少内存消耗(python -OO),就像使用docstrings和assert语句一样. (8认同)
  • 爱它.+1.但是,最后,编写文档字符串仍然是我使代码可读的第一种方式,但是,如果你要实现任何类型的静态或动态检查,那么拥有它是很好的.也许我可能会找到它的用途. (2认同)
  • @gps:就像我说的那样,这是一个不那么严肃的答案. (2认同)
  • 严肃地说,这是一种更好的方式来记录您期望的类型,同时仍然坚持DuckTyping. (2认同)

wea*_*ver 13

我第一次看到注释,我觉得"很棒!最后我可以选择进行某种类型的检查!" 当然,我没有注意到注释实际上并没有被强制执行.

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

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'>
Run Code Online (Sandbox Code Playgroud)

我将它添加到Ensure库中.