ChatGPT解决这个技术问题 Extra ChatGPT

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

函数注释: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}

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

@SilentGhost:不幸的是,许多与实际用例的链接都被破坏了。是否有任何地方可能存储了内容,或者它已经永远消失了?
python3 中的 foo.func_annotations 不应该是 foo.__annotations__ 吗?
注释没有特殊意义。 Python 唯一要做的就是将它们放入注解字典中。任何其他操作都取决于您。
def foo(a: 'x', b: 5 + 6, c: list) -> max(2, 9): 是什么意思?

R
Raymond Hettinger

函数注释就是你对它们所做的。

它们可用于文档:

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


这比用于文档的文档字符串或函数中的显式类型检查更好吗?这似乎无缘无故地使语言复杂化。
@endolith 我们当然可以不用函数注释。它们只是提供访问注释的标准方式。这使得它们可以访问 help() 和工具提示,并使它们可用于自省。
您可以创建类型 MassVelocity 而不是传递数字。
为了充分证明这一点,我还需要 def kinetic_energy(mass: 'in kilograms', velocity: 'in meters per second') -> float: 来显示返回类型。这是我最喜欢的答案。
@user189728 你是对的。要么需要将返回值保存到变量中,要么需要将整个函数包装在验证装饰器中。
c
codeforester

我认为这实际上很棒。

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

我觉得在 Python 中特别需要这样的东西,因为它的类型较弱,但实际上没有任何结构可以让这种简单直接成为官方语法的一部分。

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


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

这是一个迟到的答案,但是 AFAICT,函数注释的当前最佳用途是 PEP-0484MyPy。还有来自 Microsoft 的 PyRight,它被 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

更多示例在此处 Mypy Examples 和此处 How You Can Benefit from Type Hints
另请参阅 pytype - 考虑到 PEP-0484 构建的另一个静态分析器。
不幸的是,该类型没有被强制执行。如果我在您的示例函数中键入 list(fib('a')),Python 3.7 会愉快地接受该参数并抱怨无法比较字符串和 int。
@DenisdeBernardy 正如 PEP-484 解释的那样,Python 仅提供类型注释。要强制执行类型,您必须使用 mypy。
C
Community

只是从我的答案 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_* 样式。


有趣的应用程序,但我担心当涉及子类时 function = self.typemap.get(types) 将不起作用。在这种情况下,您可能必须使用 isinnstance 遍历 typemap。我想知道 @overload 是否正确处理了这个问题
我认为如果函数具有返回类型,这将被破坏
__annotations__ 是不能确保参数顺序的 dict,因此此代码段有时会失败。我建议将 types = tuple(...) 更改为 spec = inspect.getfullargspec(function) 然后 types = tuple([spec.annotations[x] for x in spec.args])
你说得很对,@xoolive。你为什么不编辑答案来添加你的修复?
@xoolive:我注意到了。有时,编辑在管理编辑时会使用繁重的手。我已编辑问题以包含您的修复。实际上,我对此有过 a discussion 的意见,但无法拒绝该修复程序。顺便感谢您的帮助。
J
JAB

Uri 已经给出了正确的答案,所以这里有一个不太严重的答案:所以你可以让你的文档字符串更短。


爱它。 +1。然而,最后,编写文档字符串仍然是我使代码可读的第一方式,但是,如果您要实现任何类型的静态或动态检查,那么拥有它是很好的。也许我会找到它的用途。
我不建议在您的文档字符串中使用注释来替代 Args: 部分或 @param 行或类似内容(无论您选择使用什么格式)。虽然文档注释是一个很好的例子,但它会损害注释的潜在力量,因为它可能会妨碍其他更强大的用途。此外,您不能在运行时省略注释以减少内存消耗(python -OO),就像使用文档字符串和断言语句一样。
@gps:就像我说的,这是一个不那么严肃的答案。
说真的,这是一种更好的方法来记录您期望的类型,同时仍然遵守 DuckTyping。
@gps 我不确定在 99.999% 的情况下文档字符串的内存消耗是否值得担心。
w
weaver

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

所以我决定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 库中。


在我退出相信 Python 终于有了类型检查之后,我也有同样的失望。最终将不得不继续进行自制的类型检查实施。
k
klaas

自从提出这个问题以来已经有很长时间了,但是问题中给出的示例片段(如那里所述)来自 PEP 3107,并且在 PEP 示例的末尾还给出了用例,这些用例可能会从 PEP 的角度回答问题看法 ;)

以下引自 PEP3107

用例

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

提供类型信息 类型检查 ([3], [4]) 让 IDE 显示函数期望和返回的类型 ([17]) 函数重载/泛型函数 ([22]) 外语桥梁 ([18], [19] ]) 适配 ([21], [20]) 谓词逻辑函数 数据库查询映射 RPC 参数编组 ([23])

类型检查 ([3], [4])

让 IDE 显示函数期望和返回的类型 ([17])

函数重载/泛型函数([22])

外语桥梁 ([18], [19])

适应([21],[20])

谓词逻辑函数

数据库查询映射

RPC 参数封送处理 ([23])

其他信息 参数和返回值文档 ([24])

参数和返回值的文档 ([24])

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


如果不赞成票的人至少留下简短的评论,我真的很感激导致反对票的原因。这真的对(至少我)有很大的改进。
T
The Demz

Python 3.X(仅)还泛化了函数定义,以允许使用对象值注释参数和返回值,以便在扩展中使用。

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

注释在参数名称之后和默认值之前编码为 :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


@BlackJack,“用于扩展”不清楚?
很清楚,但没有回答恕我直言的问题。这就像用“在程序中使用”来回答“什么是类的好用途?”。这很清楚,正确,但是询问方对于什么是好的具体用途并不真正明智。您的答案是再通用不过的了,其示例与问题中已经存在的示例基本相同。
D
Dimitris Fasarakis Hilliard

尽管此处描述了所有用途,但注释的一种可强制使用且最有可能强制使用的是 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,而不是。

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


a
amcgregor

作为一个延迟的答案,我的几个包(marrow.script、WebCore 等)在可用的情况下使用注释来声明类型转换(即转换来自网络的传入值、检测哪些参数是布尔开关等)以及以执行参数的附加标记。

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 / PYTHONOPTIMIZE env var)的情况下运行时,可能会很昂贵(例如递归)的检查被省略,因为您已经在开发中正确测试了您的应用程序,因此检查在生产中应该是不必要的。


L
Lasse Schuirmann

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

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

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

我们可以向用户询问一个名为“param1”的东西,它是“需要计数”并且应该是一个“int”。最后,我们甚至可以将用户给出的字符串转换为所需的类型,以获得最轻松的体验。

请参阅 our function metadata object 了解有助于解决此问题的开源类,它可以自动检索所需的值并将它们转换为 any 所需的类型(因为注释是一种转换方法)。甚至 IDE 也能正确显示自动完成功能,并假设类型是根据注释进行的 - 完美契合。


b
boardrider

如果您查看 Cython 的优点列表,其中一个主要优点是能够告诉编译器 Python 对象是哪种类型。

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


RPython Annotator 是一种适合 Python 风格的方法示例;在生成应用程序的图形后,它可以计算出每个变量的类型并(对于 RPython)强制执行单一类型安全。 OTOH 它需要“装箱”或其他解决方案/变通方法以允许动态丰富的值。当 'na' * 8 + ' batman!' 完全有效时,我是谁来强制我的 multiply 函数只对整数起作用? ;)