首页 > Python基础教程 >
-
PEP 484 类型提示 -- Python官方文档译文 [原创]
PEP 484 -- 类型提示(Type Hints)
PEP: 484
Title: Type Hints
Author: Guido van Rossum <guido at python.org>, Jukka Lehtosalo <jukka.lehtosalo at iki.fi>, ?ukasz Langa <lukasz at python.org>
BDFL-Delegate: Mark Shannon
Discussions-To: Python-Dev <python-dev at python.org>
Status: Provisional
Type: Standards Track
Created: 29-Sep-2014
Python-Version: 3.5
Post-History: 16-Jan-2015、20-Mar-2015、17-Apr-2015、20-May-2015、22-May-2015
Resolution: https://mail.python.org/pipermail/python-dev/2015-May/140104.html
- 摘要(Abstract)
-
原由和目标(Rationale and goals)
- 非目标(Non-goals)
- 注解的含义(The meaning of annotations)
-
类型定义的语法(Type definition syntax)
- 可接受的类型提示(Acceptable type hints)
-
None
的用法(Using None) - 类型别名(Type aliases)
- Callable
- 泛型(Generics)
- 用户自定义的泛型类型(User-defined generic types)
- 类型变量的作用域规则(Scoping rules for type variables)
- 泛型类的实例化及类型清除(Instantiating generic classes and type erasure)
- 用任意泛型类型作为基类(Arbitrary generic types as base classes))
- 抽象泛型类型(Abstract generic types)
- 带有类型上界的类型变量(Type variables with an upper bound)
- 协变和逆变(Covariance and contravariance)
- 数值类型的继承关系(The numeric tower)
- 向前引用(Forward references)
- Union 类型(Union types)
- 用 Union 实现单实例类型的支持(Support for singleton types in unions)
-
Any
类型(TheAny
type) -
NoReturn
类型(The NoReturn type) - 类对象的类型(The type of class objects)
- 为实例和类方法加类型注解(Annotating instance and class methods)
- 版本和平台检查(Version and platform checking)
- 运行时检查还是类型检查?(Runtime or type checking?)
- 可变参数列表和默认参数值(Arbitrary argument lists and default argument values)
- 只采用位置参数(Positional-only arguments)
- 注解生成器函数和协程(Annotating generator functions and coroutines)
-
与函数注解其他用法的兼容性(
Compatibility with other uses of function annotations) - 类型注释(Type comments)
- 指定类型(Cast)
-
NewType
工具函数(NewType helper function) -
存根文件(Stub Files)
- 函数/方法重载(Function/method overloading)
- 存根文件的存储和发布(Storing and distributing stub files)
- typeshed 库(The Typeshed Repo)
- 异常(Exceptions)
- typing 模块(The typing Module)
- Python 2.7 和跨版本代码的建议语法(Suggested syntax for Python 2.7 and straddling code)
-
未被接受的替代方案(Rejected Alternatives)
- 泛型参数该用什么括号?(Which brackets for generic type parameters?)
- 已存在的注解怎么办(What about existing uses of annotations?)
- 前向声明的问题(The problem of forward declarations)
- 双冒号(The double colon)
- 其他一些新语法格式(Other forms of new syntax)
- 其他的向下兼容约定(Other backwards compatible conventions)
- PEP 开发过程(PEP Development Process)
- 致谢(Acknowledgements)
- 参考文献(References)
- 版权(Copyright)
摘要(Abstract)
PEP 3107 已经引入了函数注解(annotation)的语法,但有意将语义(semantic)保留为未定义(undefined)。目前第三方的静态类型分析应用工具已经足够多了,社区人员采用标准用语和标准库中的基线(baseline)工具就将获益良多。
为了提供标准定义和工具,本 PEP 引入了一个临时(provisional)模块,并且列出了一些不适用注解情形的约定。
请注意,即便注解符合本规范,本 PEP 依然明确不会妨碍注解的其他用法,也不要求(或禁止)对注解进行任何特殊处理。正如 PEP 333 对 Web 框架的约定,这只是为了能更好地相互合作。
比如以下这个简单的函数,其参数和返回类型都在注解给出了声明:
def greeting(name: str) -> str:
return 'Hello ' + name
虽然在运行时通过常规的 __annotations__
属性可以访问到上述注解,但运行时并不会进行类型检查。本提案假定存在一个独立的脱机类型检查程序,用户可以自愿对源代码运行此检查程序。这种类型检查程序实质上就是一种非常强大的查错工具(linter)。当然某些用户是可以在运行时采用类似的检查程序实现“契约式设计”或JIT优化,但这些工具尚未完全成熟。
本提案受到 mypy 的强烈启发。例如,“整数序列”类型可以写为 Sequence[int]
。方括号表示无需向语言添加新的语法。上述示例用到了自定义类型 Sequence
,是从纯 Python 模块 typing
中导入的。通过实现元类(metaclass)中的 __getitem__()
方法,Sequence[int]
表示法在运行时得以生效(但主要是对脱机类型检查程序有意义)。
类型系统支持类型组合(Union)、泛型类型(generic type)和特殊类型 Any
,Any
类型可与所有类型相容(即可以赋值给所有类型,也可以从所有类型赋值)。Any
类型的特性取自渐进定型(gradual typing)的理念。渐进定型和全类型系统已在 PEP 483 中有所解释。
在 PEP 482 中,还介绍了其他一些已借鉴或可比较的方案。
原由和目标(Rationale and Goals)
PEP 3107 已加入了为函数定义中的各个部分添加注解的支持。尽管没有为注解定义什么含义,但已经隐隐有了一个目标,即把注解用于类型提示 gvr-artima,在 PEP 3107 中这被列为第一个可能应用的场景。
本 PEP 旨在为类型注解提供一种标准语法,让 Python 代码更加开放、更易于静态分析和重构,提供一种潜在的运行时类型检查方案,以及(或许在某些上下文中)能利用类型信息生成代码。
在这些目标中,静态分析是最重要的。包括了对 mypy 这类脱机类型检查程序的支持,以及可供 IDE 使用的代码自动补全和重构的标准表示法。
非目标(Non-goals)
虽然本提案的 typing
模块将包含一些用于运行时类型检查的功能模块,特别是 get_type_hints()
函数,但必须开发第三方程序包才能实现特定的运行时类型检查功能,比如使用装饰器(decorator)或元类。至于如何利用类型提示进行性能优化,就留给读者当作练习吧。
还有一点应该强调一下,Python 仍将保持为一种动态类型语言,并且按惯例作者从没希望让类型提示成为强制特性。
注解的含义(The meaning of annotations)
不带注解的函数都应被视为其类型可能是最通用的,或者应被所有类型检查器忽略的。具有 @no_type_check
装饰器的函数应被视为不带注解的。
建议但不强求被检查函数的全部参数和返回类型都带有注解。被检查函数的参数和返回类型的缺省注解为 Any
。不过有一种例外情况,就是实例和类方法的第一个参数。如果未带注解,则假定实例方法第一个参数的类型就是所在类(的类型),而类方法第一个参数的类型则为所在对象类(的类型)。例如,在类 A 中,实例方法第一个参数的类型隐含为 A。在类方法中,第一个参数的精确类型没法用类型注解表示。
请注意,__init__
的返回类型应该用 -> None
进行注解。原因比较微妙。如果假定__init__
缺省用 -> None
作为返回类型注解,那么是否意味着无参数、不带注解的__init__
方法还需要做类型检查?与其任其模棱两可或引入异常,还不如规定 __init__
应该带有返回类型注解,默认表现与其他方法相同。
类型检查程序应该对函数主体和所给注解的一致性进行检查。这些注解还可以用于检查其他被检函数对该函数的调用是否正确。
类型检查程序应该尽力推断出尽可能多的信息。最低要求是能够处理内置装饰器 @ property
、@ staticmethod
和 @classmethod
。
类型定义的语法(Type definition syntax)
这里的语法充分利用了 PEP 3107 风格的注解,并加入了以下章节介绍的一些扩展。类型提示的基本格式就是,把类名填入函数注解的位置:
def greeting(name: str) -> str:
return 'Hello ' + name
以上表示参数 name
的预期类型为 str
。类似地,预期的函数返回类型为 str
。
其类型是特定参数类型的子类型的表达式也被该参数接受。
可接受的类型提示(Acceptable type hints)
类型提示可以是内置类(含标准库或第三方扩展模块中定义的)、抽象基类、types
模块中提供的类型和用户自定义类(含标准库或第三方库中定义的)。
虽然注解通常是类型提示的最佳格式,但有时更适合用特殊注释(comment)或在单独发布的存根文件中表示。示例参见下文。
注解必须是有效的表达式,其求值过程不会让定义函数时引发异常,不过向前引用(forward reference)的用法还请参见下文。
注解应尽量保持简单,否则静态分析工具可能无法对其进行解析。例如,动态计算出来的类型就不大能被理解。本项要求是有意含糊其辞的,根据讨论结果可以在本 PEP 的未来版本中加入某些包含和排除项。
此外,以下结构也是可以用作类型注解的:None
、Any
、Union
、Tuple
、Callable
、用于构建由 typing
导出的类(如 Sequence
和 Dict
)的所有抽象基类(ABC)及其替代物(stand-in)、类型变量和类型别名。
以下章节介绍的特性当中,所有用于提供支持的新引入类型名(例如 Any
和 Union
)都在 typing
模块中给出了。
None
的用法(Using None)
当 None
用于类型提示中时,表达式 None
视作与 type(None)
等价。
类型别名(Type aliases)
定义类型别名很简单,只要用变量赋值语句即可:
Url = str
def retry(url: Url, retry_count: int) -> None: ...
请注意,类型别名建议首字母用大写,因为代表的是用户自定义类型,用户自定义的名称(如用户自定义的类)通常都用这种方式拼写。
类型别名的复杂程度可以和注解中的类型提示一样,类型注解可接受的内容在类型别名中均可接受:
from typing import TypeVar, Iterable, Tuple
T = TypeVar('T', int, float, complex)
Vector = Iterable[Tuple[T, T]]
def inproduct(v: Vector[T]) -> T:
return sum(x*y for x, y in v)
def dilate(v: Vector[T], scale: T) -> Vector[T]:
return ((x * scale, y * scale) for x, y in v)
vec = [] # type: Vector[float]
以上语句等同于:
from typing import TypeVar, Iterable, Tuple
T = TypeVar('T', int, float, complex)
def inproduct(v: Iterable[Tuple[T, T]]) -> T:
return sum(x*y for x, y in v)
def dilate(v: Iterable[Tuple[T, T]], scale: T) -> Iterable[Tuple[T, T]]:
return ((x * scale, y * scale) for x, y in v)
vec = [] # type: Iterable[Tuple[float, float]]
Callable
如果软件框架需要返回特定签名的回调函数,则可以采用 Callable [[Arg1Type,Arg2Type] ReturnType]
的形式作为类型提示。例如:
from typing import Callable
def feeder(get_next_item: Callable[[], str]) -> None:
# Body
def async_query(on_success: Callable[[int], None],
on_error: Callable[[int, Exception], None]) -> None:
# Body
在声明返回 Callable
类型时也可以不指定调用签名,只要用省略号(3个句点)代替参数列表即可:
def partial(func: Callable[..., str], *args) -> Callable[..., str]:
# Body
请注意,省略号两侧并不带方括号。在这种情况下,回调函数的参数完全没有限制,并且照样可以使用带关键字(keyword)的参数。
因为带关键字参数的回调函数并不常用,所以当前不支持指定 Callable
类型的带关键字参数。同理,也不支持参数数量可变的回调函数签名。
因为 type.Callable
带有双重职能,用于替代 collections.abc.Callable
,所以 isinstance(x, typing.Callable)
的实现与 isinstance(x, collections.abc.Callable)
兼容。但是,isinstance(x, typing.Callable[...])
是不受支持的。
泛型(Generics)
因为容器中的对象类型信息无法以通用的方式做出静态推断,所以抽象基类已扩展为支持预约(subscription)特性,以标明容器内元素的预期类型。例如:
from typing import Mapping, Set
def notify_by_email(employees: Set[Employee], overrides: Mapping[str, str]) -> None: ...
利用 typing
模块中新提供的工厂函数 TypeVar
,可以对泛型实现参数化。例如:
from typing import Sequence, TypeVar
T = TypeVar('T') # Declare type variable
def first(l: Sequence[T]) -> T: # Generic function
return l[0]
以上就是约定了返回值的类型与集合内的元素保持一致。
TypeVar()
表达式只能直接赋给某个变量(不允许用其组成其他表达式)。TypeVar()
的参数必须是一个字符串,该字符等于分配给它的变量名。类型变量不允许重定义(redefine)。
TypeVar
支持把参数可能的类型限为一组固定值(注意:这里的类型不能用类型变量实现参数化)。例如,可以定义某个类型变量只能是 str
和 bytes
。默认情况下,类型变量会覆盖所有可能的类型。以下是一个约束类型变量范围的示例:
from typing import TypeVar, Text
AnyStr = TypeVar('AnyStr', Text, bytes)
def concat(x: AnyStr, y: AnyStr) -> AnyStr:
return x + y
concat
函数对两个 str
或两个 bytes
参数都可以调用,但不能混合使用 str
和 bytes
参数。
只要存在约束条件,就至少应该有两个,不允许只指定单个约束条件。
在类型变量的上下文中,类型变量约束类型的子类型应被视作显式给出的对应基本类型。参见以下示例:
class MyStr(str): ...
x = concat(MyStr('apple'), MyStr('pie'))
上述调用是合法的,只是类型变量 AnyStr
将被设为 str
而非 MyStr
。实际上,赋给 x
的返回值,其推断类型也会是 str
。
此外,Any
对于所有类型变量而言都是合法值。参见以下示例:
def count_truthy(elements: List[Any]) -> int:
return sum(1 for elem in elements if elem)
上述语句相当于省略了泛型注解,只写了 elements: List
。
用户自定义的泛型类型(User-defined generic types)
把 Generic
基类包含进来,即可将用户自定义类定义为泛型类。例如:
from typing import TypeVar, Generic
from logging import Logger
T = TypeVar('T')
class LoggedVar(Generic[T]):
def __init__(self, value: T, name: str, logger: Logger) -> None:
self.name = name
self.logger = logger
self.value = value
def set(self, new: T) -> None:
self.log('Set ' + repr(self.value))
self.value = new
def get(self) -> T:
self.log('Get ' + repr(self.value))
return self.value
def log(self, message: str) -> None:
self.logger.info('{}: {}'.format(self.name, message))
作为基类的 Generic[T]
定义了带有1个类型参数 T
的 LoggedVar
类。这也使得 T
能在类的体内作为类型来使用。
Generic
基类会用到定义了 __getitem__
的元类,以便 LoggedVar[t]
能作为类型来使用:
from typing import Iterable
def zero_all_vars(vars: Iterable[LoggedVar[int]]) -> None:
for var in vars:
var.set(0)
同一个泛型类型所赋的类型变量可以是任意多个,而且类型变量还可以用作约束条件。以下语句是合法的:
from typing import TypeVar, Generic
...
T = TypeVar('T')
S = TypeVar('S')
class Pair(Generic[T, S]):
...
Generic
的每个类型变量参数都必须唯一。因此,以下语句是非法的:
from typing import TypeVar, Generic
...
T = TypeVar('T')
class Pair(Generic[T, T]): # INVALID
...
在比较简单的场合,没有必要用到 Generic[T]
,这时可以继承其他的泛型类并指定类型变量参数:
from typing import TypeVar, Iterator
T = TypeVar('T')
class MyIter(Iterator[T]):
...
以上类的定义等价于:
class MyIter(Iterator[T], Generic[T]):
...
可以对 Generic
使用多重继承:
from typing import TypeVar, Generic, Sized, Iterable, Container, Tuple
T = TypeVar('T')
class LinkedList(Sized, Generic[T]):
...
K = TypeVar('K')
V = TypeVar('V')
class MyMapping(Iterable[Tuple[K, V]],
Container[Tuple[K, V]],
Generic[K, V]):
...
如未指定类型参数,则泛型类的子类会假定参数的类型均为 Any
。在以下示例中,MyIterable
就不是泛型类,而是隐式继承自 Iterable[Any]
:
from typing import Iterable
class MyIterable(Iterable): # Same as Iterable[Any]
...
泛型元类不受支持。
类型变量的作用域规则(Scoping rules for type variables)
类型变量遵循常规的名称解析规则。但在静态类型检查的上下文中,存在一些特殊情况:
- 泛型函数中用到的类型变量可以被推断出来,以便在同一代码块中表示不同的类型。例如:
from typing import TypeVar, Generic
T = TypeVar('T')
def fun_1(x: T) -> T: ... # T here
def fun_2(x: T) -> T: ... # and here could be different
fun_1(1) # This is OK, T is inferred to be int
fun_2('a') # This is also OK, now T is str
- 当泛型类的方法中用到类型变量时,若该变量正好用作参数化类,那么此类型变量一定是绑定不变的。例如:
from typing import TypeVar, Generic
T = TypeVar('T')
class MyClass(Generic[T]):
def meth_1(self, x: T) -> T: ... # T here
def meth_2(self, x: T) -> T: ... # and here are always the same
a = MyClass() # type: MyClass[int]
a.meth_1(1) # OK
a.meth_2('a') # This is an error!
- 如果某个方法中用到的类型变量与所有用于参数化类的变量都不相符,则会使得该方法成为返回类型为该类型变量的泛型函数:
T = TypeVar('T')
S = TypeVar('S')
class Foo(Generic[T]):
def method(self, x: T, y: S) -> S:
...
x = Foo() # type: Foo[int]
y = x.method(0, "abc") # inferred type of y is str
- 在泛型函数体内不应出现未绑定的类型变量,在类中除方法定义以外的地方也不应出现:
T = TypeVar('T')
S = TypeVar('S')
def a_fun(x: T) -> None:
# this is OK
y = [] # type: List[T]
# but below is an error!
y = [] # type: List[S]
class Bar(Generic[T]):
# this is also an error
an_attr = [] # type: List[S]
def do_something(x: S) -> S: # this is OK though
...
- 如果泛型类的定义位于某泛型函数内部,则其不允许使用参数化该泛型函数的类型变量:
from typing import List
def a_fun(x: T) -> None:
# This is OK
a_list = [] # type: List[T]
...
# This is however illegal
class MyGeneric(Generic[T]):
...
嵌套的泛型类不能使用相同的类型变量。外部类的类型变量,作用域不会覆盖内部类:
T = TypeVar('T')
S = TypeVar('S')
class Outer(Generic[T]):
class Bad(Iterable[T]): # Error
...
class AlsoBad:
x = None # type: List[T] # Also an error
class Inner(Iterable[S]): # OK
...
attr = None # type: Inner[T] # Also OK
实例化通用类和类型清除(Instantiating generic classes and type erasure)
当然可以对用户自定义的泛型类进行实例化。假定编写了以下继承自 Generic[T]
的 Node
类:
from typing import TypeVar, Generic
T = TypeVar