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

2025-03-04 08:24:00
admin
原创
65
摘要:问题描述:函数注释:PEP-3107我偶然发现了一段演示 Python3 函数注释的代码。这个概念很简单,但我想不出为什么要在 Python3 中实现它们,也想不出它们有什么好的用途。也许 SO 可以启发我?工作原理:def foo(a: 'x', b: 5 + 6, c: list) -> max(2...

问题描述:

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

拥有这个功能有何意义?


解决方案 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}    Value: {1}    Test: {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:

我认为这真的很棒。

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

我觉得这样的东西在 Python 中尤其需要,因为它的类型较弱,但实际上没有任何构造可以使其变得简单并成为官方语法的一部分。

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

解决方案 3:

这是一个很晚的答案,但据我所知,目前函数注释的最佳用法是PEP-0484和MyPy。还有来自微软的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

解决方案 4:

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

# 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_annotationsfunc_*在 Python 3 上删除的样式。

解决方案 5:

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

解决方案 6:

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

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

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库中。

解决方案 7:

这个问题问了很久了,但问题中给出的示例片段(如那里所述)来自 PEP 3107,并且在该 PEP 示例的末尾还给出了用例,可以从 PEP 的角度回答这个问题;)

以下内容引自 PEP3107

使用案例

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

  • 提供打字信息

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

    • 让 IDE 显示函数需要和返回的类型 ([17])

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

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

    • 改编([21],[20])

    • 谓词逻辑函数

    • 数据库查询映射

    • RPC 参数编组([23])

  • 其他信息

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

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

解决方案 8:

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 袖珍指南,第五版

例子:

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

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

https://github.com/ceronman/typeannotations

类型如何帮助编写更好的代码

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

简洁的 Python:用 Python 优雅地编码 ISBN:ISBN-13(平装):978-1-4842-4877-5

PEP 526——变量注释的语法

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

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

解决方案 9:

尽管这里描述了所有用途,但注解的一种可执行且最有可能强制使用的用途是类型提示。

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

引用注释的现有用途如何?:

我们确实希望类型提示最终成为注解的唯一用途,但这需要在 Python 3.5 首次推出类型模块后进行额外的讨论和一段弃用期。在 Python 3.6 发布之前,当前的 PEP 将处于临时状态(参见 PEP 411)。最快的可想象方案是在 3.6 中引入非类型提示注解的静默弃用,在 3.7 中完全弃用,并将类型提示声明为 Python 3.8 中唯一允许的注解用途。

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

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

解决方案 10:

作为一个有点延迟的答案,我的几个包(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/ PYTHONOPTIMIZEenv var)的情况下运行时,可能很昂贵的检查(例如递归)将被省略,因为您已经在开发过程中正确测试了您的应用,因此在生产过程中检查应该是不必要的。

解决方案 11:

我可能错了,但可能的好处之一就是关注点分离!

我可以离开

(a)将有关 args、kwargs 和返回值类型的信息添加到函数注释中(以便在尝试使用现代 IDE 时可以轻松看到这些信息,并且在使用 Sphinx 等工具构建的文档中也可以很容易地看到这些信息)

(b) 将函数/方法的目的描述(以及任何算法或实现细节)添加到文档字符串中,而不涉及此处的类型提示。

下面是实现这一目标的一个非常简单的示例代码。

"""
Possible benefit of annotations
"""

def f1_return_a_float(a: float, b: int)->float:
    """
    returns sum of two numbers typecasted to float
    """
    return float(a+b)

if __name__ == "__main__":
    print(f1_return_a_float.__doc__)
    print(f1_return_a_float.__annotations__)

如您所见,文档和注释提供了有关代码的独立且互补的详细信息。当您尝试调用该函数时,请不要忘记 IDE 提供的帮助。这比通过文档字符串寻找方法要好得多。

在生成的 sphinx 文件上看起来也更好。

在此处输入图片描述

解决方案 12:

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

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

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

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

请参阅我们的函数元数据对象,这是一个开源类,它可帮助您实现这一点,并可自动检索所需值并将其转换为任何所需类型(因为注释是一种转换方法)。甚至 IDE 也能正确显示自动完成功能,并假设类型符合注释 - 完美契合。

解决方案 13:

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

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

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   3848  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   2709  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Freshdesk、ClickUp、nTask、Hubstaff、Plutio、Productive、Targa、Bonsai、Wrike。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在项目管理过程中面临着诸多痛点,如任务分配不...
项目管理系统   35  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Monday、TeamGantt、Filestage、Chanty、Visor、Smartsheet、Productive、Quire、Planview。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多项目经理和团队在管理复杂项目时,常...
开源项目管理工具   28  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Smartsheet、GanttPRO、Backlog、Visor、ResourceGuru、Productive、Xebrio、Hive、Quire。在当今快节奏的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在选择项目管理工具时常常面临困惑:...
项目管理系统   35  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用