首页 文章

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

提问于
浏览
139

功能注释:PEP-3107

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

这个怎么运作:

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

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

foo.func_annotations将返回一个字典:

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

有这个有什么意义?

12 回答

  • 23

    我认为这实际上很棒 .

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

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

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

  • 76

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

    它们可用于文档:

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

  • 12

    这是一个迟到的答案,但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
    
  • 20

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

  • -2

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

  • 0

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

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

  • 1

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

    以下内容摘自PEP3107

    Use Cases

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

    • 提供打字信息

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

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

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

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

    • 适应([21],[20])

    • 谓词逻辑函数

    • 数据库查询映射

    • RPC参数编组([23])

    • 其他信息

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

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

  • 81

    作为一个延迟回答,我的几个包(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控制器也恰好作为命令行脚本公开 . )

  • 34

    尽管此处描述了所有用法,但是可强制执行且很可能强制使用注释的是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 .

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

  • -2

    Python 3.X(仅)也推广了函数定义,以允许参数和返回值使用对象值 for use in extensions 进行批注 .

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

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

    EXAMPLE:

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

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

    https://github.com/ceronman/typeannotations

  • 0

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

    def run(param1: int):
        """
        Does things.
    
        :param param1: Needed for counting.
        """
        pass
    

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

    有关一个开源类,请参阅our function metadata object,它可以自动检索所需的值并将它们转换为任何所需的类型(因为注释是一种转换方法) . 甚至IDE也能正确显示自动填充功能,并假设类型符合注释 - 完美契合 .

  • 0

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

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

相关问题