PEP 3107 -- 函数注解[译]
本文是PIP 3137的翻译。
背景
Python 2.x系列缺乏一个标准的方式来说明一个函数的参数和返回值,各种工具和库的出现填补了这一空白。一些工具使用了“PEP 318”中的装饰器,而其他的工具则去解析函数的文档注释,寻找注解。 在这一点上,众多已存在的机制和语法造成了很大的混乱。本PEP的目的是提供一个单一的,标准的方式指定这些信息,减少这些混乱。
函数注解的基础知识
在仔细讨论Python 3.0的函数注解的细节之前,首先让我们大致讨论下注解是什么、不是什么:
- 函数注解,无论是对参数的还是对返回值的函数注解,完全是可选的。
- 函数注解无非是一种方法,用来在编译时将任意Python表达式和函数的不同部分关联起来。就这点而言,Python并没有给注解赋予特殊的意义和重要性。Python仅仅让这些表达式可以被访问而已,以一种像本文的“访问函数注解”中描述的方法。注解对含义产生作用的唯一方式是当它们被第三方库解释的时候。这些注解的使用者可以用这些函数注解做他们想做的任何事情。例如,一个库可以利用字符串类型的注解来提供改进过的帮助信息: def compile(source: “something compilable”,
另一个库可以用来提供Python函数和方法的类型检查。这个库可以使用注解来说明函数的预期输入和返回值的类型: def haul(item: Haulable, *vargs: PackAnimal) -> Distance:filename: "where the compilable thing comes from", mode: "is this a single statement or a suite?"): ...
然而,不论是第一个例子中的字符串还是第二个例子中的类型信息,它们自己没有任何含义;含义来自第三方库。...
- 根据第二点,该PEP不会试图引入一种标准的语义,即使是为那些内置类型。这个工作留给第三方库。
语法
参数
对参数的注解跟随着参数名,采用了一种可选表达式的形式:
def foo(a: expression, b: expression = 5):
…
在伪语法中,参数现在看起来像identifier [: expression] [= expression] 。即,注解在参数的默认值之前,而且两者都是可选的。就像等号是用来标记一个默认值的,冒号用来标记注解。像默认值一样,所有的注解表达式都会在函数定义的时候被求值。 “多余”的参数(例如:*args 和**kwargs )的注解以同样的方式标记:
def foo((x1, y1: expression),
(x2: expression, y2: expression)=(None, None)):
…
返回值
到目前为止,例子都忽略了如何注解一个函数的返回值类型。方法如下:
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 的语法来支持注解,即要求用圆括号围住参数列表。但是,已经决定不进行此项更改,因为:
- 这将是一个不兼容的更改。
- 不论如何,Lambda会被阉割。
- 一个Lambda总是可以被改为一个函数。
访问函数注解
一旦被编译,函数注解是可以通过函数的func_annotations 属性来访问的。该属性是一个可变字典类型,参数名称映射到一个对象,这个对象代表求值后的注解。 return 在func_annotations 中是一个特殊的键。只有在为函数的返回值提供了注解的情况下,return 键才会出现。 例如,如下注解:
def foo(a: ‘x’, b: 5 + 6, c: list) -> max(2, 9):
…
将会产生一个这样的func_annotation 映射:
{‘a’: ‘x’,
‘b’: 11,
‘c’: list,
‘return’: 9}
选择return 键,是因为它不会和参数名称冲突。任何用return 作为函数参数名的尝试都会导致语法错误(SyntaxError ) 如果函数没有注解,或者函数创建自一个lamba 表达式,则func_annotations 是一个空的、可变的字典。
使用案例
在讨论注解的过程中,一些使用案例已经出现。有些在这里列出,以它们要传达的信息来分组。还包括在现有的产品和包中使用注解的例子。
PEP 3107 -- 函数注解[译]