器→工具, 编程语言

PEP 3107:函数注解 Function Annotations

钱魏Way · · 0 次浏览

PEP 3107,全称为 “Function Annotations”,是 Python 编程语言中的一项重要提案,它在 Python 3.0 版本中被引入。这个提案由 Talin 在 2006 年提出,主要目的是为 Python 函数添加注解(annotations)功能。以下是关于 PEP 3107 的详细介绍:

PEP 3107 的主要内容

  • 函数注解(Function Annotations):
    • 允许开发者在函数定义中添加注解。这些注解可以是任何表达式,不仅限于类型。
    • 注解可用于函数的参数和返回值。
  • 语法:
    • 参数注解:在函数参数后添加冒号和表达式。例如:def foo(x: expression, y: expression):
    • 返回值注解:在函数定义的参数列表和冒号之间添加 -> 符号和表达式。例如:def bar(x: int) -> str:
  • 注解的访问:
    • 注解存储在函数的 __annotations__ 属性中,是一个字典格式。

PEP 3107 的目的

  • 增强函数的元数据信息:注解提供了一种方式,可以将额外的信息与函数参数和返回值相关联。
  • 灵活性和可扩展性:注解不仅限于类型信息,开发者可以根据需要使用它们来存储各种元数据。
  • 并非强制性:函数注解是完全可选的,不强制要求在代码中使用。

应用和影响

  • 为类型提示铺路:尽管 PEP 3107 本身并不专门针对类型提示,但它为后来的 PEP 484(类型提示)提供了基础。
  • 多种用途:注解的用途不仅限于类型检查,也可以用于文档生成、验证框架、IDE 辅助功能等。

摘要(Abstract)

本 PEP 引入了一种语法,用于为 Python 函数添加元数据注解(metadata annotation)

原由(Rationale)

因为 Python 2.x 没有为函数参数和返回值提供标准的注解方式,貌似出现了多种工具软件和程序库来填补这一空白。 有些工具利用 PEP 318 中引入的装饰器,而另一些则对函数的文档字符串(docstring)进行解析,以期找到注解信息。

到目前为止已有机制和语法之间存在着巨大差异,本 PEP 旨在提供一种单一的、标准的注解定义方式,以期减少这些差异所引起的混乱。

函数注解的基础(Fundamentals of Function Annotations)

在开始精确讨论 Python 3.0 函数注释的来龙去脉之前 ,先来大致讨论一下什么是注解:

  • 函数注解(包括参数和返回值)完全是可选内容的。
  • 函数注解只是一种在编译阶段将 Python 表达式与函数的各个部分建立关联的方式,仅此而已。
    • Python 本身不会为注解关联任何特定的含义或意义。注解表达式是独立存在的,Python 只是让这些表达式可供使用,用法如下文“函数注解的读取”所述。
    • 仅当被第三方库解释时,注解才能发挥作用。使用者可以随意使用函数的注解。比如某个程序库可能会把字符串形式的注解用于提供更好的帮助消息,如下所示:
def compile(source: "something compilable",
            filename: "where the compilable thing comes from",
            mode: "is this a single statement or a suite?"):
    ...

另一个程序库或许会被用于对 Python 函数和方法提供类型检查功能。它可以利用注解将函数应有的输入和返回类型标识出来,格式可能如下:

def haul(item: Haulable, *vargs: PackAnimal) -> Distance:
    ...

不过,不论是第一个示例中的字符串还是第二个示例中的类型信息,本身都没有任何意义,一切含义都单独来自第三方库。

  • 继续第2点,即便是对内置类型,本 PEP 也不会引入任何标准语义。这项工作将留给第三方库去完成。

语法(Syntax)

参数的注解(Parameters)

参数的注解采用可选表达式的格式,跟在参数名的后面:

def foo(a: expression, b: expression = 5):
    ...

若用伪语法(pseudo-grammar)表示,现在参数看起来类似 identifier [: expression] [= expression]。也就是说,注解始终位于参数默认值之前,注解和默认值都是可选的。就像用等号标出默认值一样,这里用冒号把注解标出来。就像默认值一样,在函数定义得以运行时将会对所有注解表达式进行求值。

def foo(*args: expression, **kwargs: expression):
    ...

嵌套参数的注解总是跟在参数名的后面,而不是在右括号后面。嵌套参数中的参数不需要全要带有注解:

def foo((x1, y1: expression),
        (x2: expression, y2: expression)=(None, None)):
    ...

返回值的注解(Return Values)

到目前为止,还没有给出如何注解函数返回值的示例。以下便是:

def sum() -> expression:
    ...

也就是说,现在参数列表后面可以跟一个 -> 和一个 Python 表达式。像参数的注解一样,函数定义得以运行时将会对该表达式进行求值。

函数定义的语法现在变成了:

decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
decorators: decorator+
funcdef: [decorators] 'def' NAME parameters ['->' test] ':' suite
parameters: '(' [typedargslist] ')'
typedargslist: ((tfpdef ['=' test] ',')*
                ('*' [tname] (',' tname ['=' test])* [',' '**' tname]
                 | '**' tname)
                | tfpdef ['=' test] (',' tfpdef ['=' test])* [','])
tname: NAME [':' test]
tfpdef: tname | '(' tfplist ')'
tfplist: tfpdef (',' tfpdef)* [',']

Lambda 表达式

lambda 表达式的语法不支持注解。当然 lambda 表达式的语法可以改成支持注解的格式,这需要在参数列表两侧加上括号。但因为以下原因,还是决定不做改动了【12】:

  • 该改动不具兼容性。
  • 不管怎么说,lambda 已式微了。
  • lambda 表达式一定是能变换成函数的。

函数注解的读取(Accessing Function Annotations)

只要一经编译,函数注解就可以通过函数的 __annotations__ 属性访问到了。该属性是一个可变(mutable)字典,将参数名称映射为一个代表已求值注解表达式的对象。

在 __annotations__ 映射中有一个特殊键 “return”。仅当提供了函数返回值的注解时,它才会有效。

例如,以下注解:

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

将会生成如下 __annotations__ 映射:

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

选用 return 键是因为不能与参数名称发生冲突,任何时候想把 return 用于参数名称,都会引发 SyntaxError。

如果函数没有带注解或是由 lambda 表达式生成的,则 __annotations__ 将是一个空的可变字典。

应用案例(Use Cases)

在注解的讨论过程中,已经给出了很多应用案例。下面给出其中一些案例,按其表达的信息做了分组。这里还包括了一些可能用到注解的现有产品和包示例。

  • 为以下特性提供类型信息
    • 类型检查
    • 让 IDE 能够显示函数应该的参数类型和返回类型
    • 函数重载 / 泛型函数
    • 与其他语言的桥梁(bridge)
    • 适配器(adaptation)模式
    • 谓词逻辑(predicate logic)函数
    • 数据库查询映射(map)
    • RPC 参数封送(marshal)
  • 其他信息
    • 参数和返回值的文档说明

标准库(Standard Library)

pydoc 和 inspect

在显示函数帮助信息时,pydoc 模块应能显示函数的注解。inspect 模块应改为支持注解信息的获取。

与其他 PEP 的关联(Relation to Other PEPs)

函数签名对象(Function Signature Objects)

函数签名对象应该公开函数的注解。Parameter 对象或其他地方可能需要做出改动。

实现(Implementation)

参考实现已作为修订(revision) 53170签入 py3k(以前名为“p3yk”)分支中。

未被接受的提案(Rejected Proposals)

  • BDFL 拒绝了一种特殊语法的想法,该语法要为生成器(generator)添加注解,因为它“太难看了”
  • 尽管之前进行过讨论,但为注解生成器函数和更高层函数(higher-order)在 stdlib 中纳入特殊对象提案最终被拒,因其更适合用第三方库去实现,将他们包含在标准库中引发了太多棘手的问题。
  • 虽然对标准类型参数化的语法进行过大量讨论,但仍决定应将其留给第三方库去实现。
  • 尽管尚有更多讨论,但仍决定不对注解的互操作(interoperability)机制作标准化规定。目前对互操作性作标准化约定还为时过早。宁可让这些约定根据实际使用情况和必要性野蛮生长(organically)一番,也不妄图强行让所有用户都采用某些造作的方案。

参考链接:

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注