问题:Python3的“函数注释”有什么好的用处

功能注释: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}

拥有此功能有什么意义?

Function Annotations: PEP-3107

I ran across a snippet of code demonstrating Python3’s function annotations. The concept is simple but I can’t think of why these were implemented in Python3 or any good uses for them. Perhaps SO can enlighten me?

How it works:

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

Everything following the colon after an argument is an ‘annotation’, and the information following the -> is an annotation for the function’s return value.

foo.func_annotations would return a dictionary:

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

What’s the significance of having this available?


回答 0

我认为这实际上很棒。

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

我觉得这样的事情在Python中特别需要,因为它的输入较弱,但是实际上没有任何结构可以使它简单明了,并且成为正式语法的一部分。

注解还有其他用途,无法保证。我可以看到如何将基于Java的工具应用于Python。例如,我有一个工具,可让您为方法分配特殊警告,并在调用它们时向您提供指示,指示您应阅读其文档(例如,假设您有一个不能用负值调用的方法,但是从名称上不直观)。通过注释,我可以为Python技术性地编写类似的内容。同样,如果存在正式语法,则可以编写基于标签将大型方法组织起来的工具。

I think this is actually great.

Coming from an academic background, I can tell you that annotations have proved themselves invaluable for enabling smart static analyzers for languages like Java. For instance, you could define semantics like state restrictions, threads that are allowed to access, architecture limitations, etc., and there are quite a few tools that can then read these and process them to provide assurances beyond what you get from the compilers. You could even write things that check preconditions/postconditions.

I feel something like this is especially needed in Python because of its weaker typing, but there were really no constructs that made this straightforward and part of the official syntax.

There are other uses for annotations beyond assurance. I can see how I could apply my Java-based tools to Python. For instance, I have a tool that lets you assign special warnings to methods, and gives you indications when you call them that you should read their documentation (E.g., imagine you have a method that must not be invoked with a negative value, but it’s not intuitive from the name). With annotations, I could technicall write something like this for Python. Similarly, a tool that organizes methods in a large class based on tags can be written if there is an official syntax.


回答 1

函数批注就是您对它们所做的。

它们可以用于文档:

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

Function annotations are what you make of them.

They can be used for documentation:

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

They can be used for pre-condition checking:

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

Also see http://www.python.org/dev/peps/pep-0362/ for a way to implement type checking.


回答 2

这是一个较晚的答案,但是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

This is a way late answer, but AFAICT, the best current use of function annotations is PEP-0484 and MyPy.

Mypy is an optional static type checker for Python. You can add type hints to your Python programs using the upcoming standard for type annotations introduced in Python 3.5 beta 1 (PEP 484), and use mypy to type check them statically.

Used like so:

from typing import Iterator

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

回答 3

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

# 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的原始文章所示,但是对参数本身进行注释会更好,因为这样可以避免错误地匹配参数和类型。

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

Just to add a specific example of a good use from my answer here, coupled with decorators a simple mechanism for multimethods can be done.

# 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

and an example of use:

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

This can be done by adding the types to the decorator as Guido’s original post shows, but annotating the parameters themselves is better as it avoids the possibility of wrong matching of parameters and types.

Note: In Python you can access the annotations as function.__annotations__ rather than function.func_annotations as the func_* style was removed on Python 3.


回答 4

Uri已经给出了正确的答案,所以下面是一个不太严重的答案:这样您可以缩短文档字符串。

Uri has already given a proper answer, so here’s a less serious one: So you can make your docstrings shorter.


回答 5

第一次看到注释时,我以为“很棒!最后我可以选择进行类型检查!” 当然,我没有注意到注解实际上并没有执行。

因此,我决定编写一个简单的函数装饰器来实施它们

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

我已将其添加到“ 确保”库中。

The first time I saw annotations, I thought “great! Finally I can opt in to some type checking!” Of course, I hadn’t noticed that annotations are not actually enforced.

So I decided to 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'>

I added it to the Ensure library.


回答 6

自问起以来已经有很长时间了,但问题中给出的示例摘录(也如此处所述)来自PEP 3107,并且在thas PEP示例结尾处也给出了用例,它们可能会从PEP角度回答问题。查看;)

以下引自PEP3107

用例

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

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

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

It a long time since this was asked but the example snippet given in the question is (as stated there as well) from PEP 3107 and at the end of thas PEP example Use cases are also given which might answer the question from the PEPs point of view ;)

The following is quoted from PEP3107

Use Cases

In the course of discussing annotations, a number of use-cases have been raised. Some of these are presented here, grouped by what kind of information they convey. Also included are examples of existing products and packages that could make use of annotations.

  • Providing typing information
    • Type checking ([3], [4])
    • Let IDEs show what types a function expects and returns ([17])
    • Function overloading / generic functions ([22])
    • Foreign-language bridges ([18], [19])
    • Adaptation ([21], [20])
    • Predicate logic functions
    • Database query mapping
    • RPC parameter marshaling ([23])
  • Other information
    • Documentation for parameters and return values ([24])

See the PEP for more information on specific points (as well as their references)


回答 7

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代码的类型检查和类型推断的工具。它还提供了一组用于注释功能和对象的类型。

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

https://github.com/ceronman/typeannotations

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

键入可以帮助您进行静态代码分析,以在将代码发送到生产环境之前捕获类型错误,并防止出现一些明显的错误。有些工具例如mypy,可以将其添加到工具箱中,作为软件生命周期的一部分。mypy可以通过部分或完全针对您的代码库运行来检查类型是否正确。mypy还可以帮助您检测错误,例如从函数返回值时检查None类型。键入有助于使代码更整洁。您可以在不增加性能成本的情况下使用类型,而不必使用注释在文档字符串中指定类型的方式来记录代码。

干净的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

Python 3.X (only) also generalizes function definition to allow arguments and return values to be annotated with object values for use in extensions.

Its META-data to explain, to be more explicit about the function values.

Annotations are coded as :value after the argument name and before a default, and as ->value after the argument list.

They are collected into an __annotations__ attribute of the function, but are not otherwise treated as special by Python itself:

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

Source: Python Pocket Reference, Fifth Edition

EXAMPLE:

The typeannotations module provides a set of tools for type checking and type inference of Python code. It also a provides a set of types useful for annotating functions and objects.

These tools are mainly designed to be used by static analyzers such as linters, code completion libraries and IDEs. Additionally, decorators for making run-time checks are provided. Run-time type checking is not always a good idea in Python, but in some cases it can be very useful.

https://github.com/ceronman/typeannotations

How Typing Helps to Write Better Code

Typing can help you do static code analysis to catch type errors before you send your code to production and prevent you from some obvious bugs. There are tools like mypy, which you can add to your toolbox as part of your software life cycle. mypy can check for correct types by running against your codebase partially or fully. mypy also helps you to detect bugs such as checking for the None type when the value is returned from a function. Typing helps to make your code cleaner. Instead of documenting your code using comments, where you specify types in a docstring, you can use types without any performance cost.

Clean Python: Elegant Coding in Python ISBN: ISBN-13 (pbk): 978-1-4842-4877-5

PEP 526 — Syntax for Variable Annotations

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

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


回答 8

尽管在此描述了所有用法,但注释的一种可执行且最有可能的强制使用将是类型提示

目前尚未以任何方式强制执行此操作,但从PEP 484判断,Python的未来版本将仅允许类型作为注释的值。

引用注释的现有用法如何?

我们确实希望类型提示最终将成为注释的唯一用法,但这在使用Python 3.5首次键入类型模块之后,将需要进行额外的讨论和弃用期。当前的PEP将具有临时状态(请参阅PEP 411),直到发布Python 3.6。最快的可能方案将在3.6中引入对非类型提示注释的静默弃用,在3.7中引入完全弃用,并将类型提示声明为Python 3.8中唯一允许使用的注释。

尽管我还没有在3.6中看到任何过时的贬值,但是很可能会升至3.7。

因此,即使可能还有其他一些很好的用例,如果您不想在将来有此限制的情况下四处更改所有内容,最好还是仅将它们保留为类型提示。

Despite all uses described here, the one enforceable and, most likely, enforced use of annotations will be for type hints.

This is currently not enforced in any way but, judging from PEP 484, future versions of Python will only allow types as the value for annotations.

Quoting What about existing uses of annotations?:

We do hope that type hints will eventually become the sole use for annotations, but this will require additional discussion and a deprecation period after the initial roll-out of the typing module with Python 3.5. The current PEP will have provisional status (see PEP 411 ) until Python 3.6 is released. The fastest conceivable scheme would introduce silent deprecation of non-type-hint annotations in 3.6, full deprecation in 3.7, and declare type hints as the only allowed use of annotations in Python 3.8.

Though I haven’t seen any silent deprecations in 3.6 yet, this could very well be bumped to 3.7, instead.

So, even though there might be some other good use-cases, it is best to keep them solely for type hinting if you don’t want to go around changing everything in a future where this restriction is in place.


回答 9

作为一个延迟回答的问题,我的一些软件包(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控制器,它也恰巧作为命令行脚本公开。)

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

As a bit of a delayed answer, several of my packages (marrow.script, WebCore, etc.) use annotations where available to declare typecasting (i.e. transforming incoming values from the web, detecting which arguments are boolean switches, etc.) as well as to perform additional markup of arguments.

Marrow Script builds a complete command-line interface to arbitrary functions and classes and allows for defining documentation, casting, and callback-derived default values via annotations, with a decorator to support older runtimes. All of my libraries that use annotations support the forms:

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” support for docstrings or typecasting functions allows for easier mixing with other libraries that are annotation-aware. (I.e. have a web controller using typecasting that also happens to be exposed as a command-line script.)

Edited to add: I’ve also begun making use of the TypeGuard package using development-time assertions for validation. Benefit: when run with “optimizations” enabled (-O / PYTHONOPTIMIZE env var) the checks, which may be expensive (e.g. recursive) are omitted, with the idea that you’ve properly tested your app in development so the checks should be unnecessary in production.


回答 10

注释可用于轻松地模块化代码。例如,我要维护的程序模块可以只定义以下方法:

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

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

我们可以要求用户输入一个名为“ param1”的东西,该东西“需要计数”并且应该是“ int”。最后,我们甚至可以将用户提供的字符串转换为所需的类型,以获取最轻松的体验。

请参阅我们的函数元数据对象以获取开放源代码类,该类对此有所帮助,并且可以自动检索所需的值并将其转换为任何所需的类型(因为注释是一种转换方法)。甚至IDE都显示正确的自动完成功能,并假定类型符合注释-非常合适。

Annotations can be used for easily modularizing code. E.g. a module for a program which I’m maintaining could just define a method like:

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

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

and we could ask the user for a thing named “param1” which is “Needed for counting” and should be an “int”. In the end we can even convert the string given by the user to the desired type to get the most hassle free experience.

See our function metadata object for an open source class which helps with this and can automatically retrieve needed values and convert them to any desired type (because the annotation is a conversion method). Even IDEs show autocompletions right and assume that types are according to annotations – a perfect fit.


回答 11

如果您查看Cython的好处列表,那么主要的一项功能就是能够告诉编译器Python对象的类型。

我可以预见一个未来,Cython(或编译某些Python代码的类似工具)将使用注释语法来发挥作用。

If you look at the list of benefits of Cython, a major one is the ability to tell the compiler which type a Python object is.

I can envision a future where Cython (or similar tools that compile some of your Python code) will use the annotation syntax to do their magic.


声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。