跳转至

PEP 484 —— 类型注解

PEP 484 —— Type Hints

摘要

Abstract

PEP 3107 引入了函数注解的语法,但语义保留为未定义。目前第三方的静态类型分析应用工具已经足够多了,社区开发者采用标准用语和标准库中的基线工具将会获益良多。

PEP 3107 introduced syntax for function annotations, but the semantics were deliberately left undefined. There has now been enough 3rd party usage for static type analysis that the community would benefit from a standard vocabulary and baseline tools within the standard library.

本 PEP 引入了一个假定的模块以提供这些标准的定义和工具,还有一些对于注解不可用的情形的约定。

This PEP introduces a provisional module to provide these standard definitions and tools, along with some conventions for situations where annotations are not available.

请注意,本 PEP 仍然明确不会阻止注解的其他用途,也不需要(或禁止)对注解的进行任何特定处理,即使它们符合本规范也是如此。正如 PEP 333 对 Web 框架的约定,这只是为了能更好地相互合作。

Note that this PEP still explicitly does NOT prevent other uses of annotations, nor does it require (or forbid) any particular processing of annotations, even when they conform to this specification. It simply enables better coordination, as PEP 333 did for web frameworks.

下面这个简单函数,其参数和返回值的类型在注解中进行了声明:

For example, here is a simple function whose argument and return type are declared in the annotations:

def greeting(name: str) -> str:
    return 'Hello ' + name

虽然这些注解可以在运行时通过常规属性 __annotations__ 访问到,但运行时并不会进行类型检查。相反,本提案假定存在一个独立的离线类型检查器,用户可以自愿地使用它对源代码进行类型检查。在本质上,这样的类型检查器充当了一个非常强大的静态代码分析工具。(虽然一些用户可以在运行时使用类似的检查工具来实现契约式设计和 JIT 优化,但目前这些工具都还不够成熟。)

While these annotations are available at runtime through the usual __annotations__ attribute, no type checking happens at runtime. Instead, the proposal assumes the existence of a separate off-line type checker which users can run over their source code voluntarily. Essentially, such a type checker acts as a very powerful linter. (While it would of course be possible for individual users to employ a similar checker at run time for Design By Contract enforcement or JIT optimization, those tools are not yet as mature.)

这份提案受到 mypy 的强烈启发。例如,“整数序列”的类型使用 Sequence[int] 表示。使用方括号意味着不需要在语言中添加新的语法。示例中使用了从纯 Python 模块 typing 中导入的定制类型 Sequence。通过在其元类中实现 __getItem __()Sequence[int] 标记得以在检查工具运行时生效(但这个标记主要是对离线类型检查器有意义)。

The proposal is strongly inspired by mypy [mypy]. For example, the type "sequence of integers" can be written as Sequence[int]. The square brackets mean that no new syntax needs to be added to the language. The example here uses a custom type Sequence, imported from a pure-Python module typing. The Sequence[int] notation works at runtime by implementing __getitem__() in the metaclass (but its significance is primarily to an offline type checker).

类型系统支持联合类型、范型类型、以及名为 Any 的可与任何类型兼容的特殊类型(可以赋值给任何类型,也可以被任何类型赋值)。Any 的特性取自渐进定型(gradual typing)的理念。PEP 483 中解释了渐进定型和全类型系统。

The type system supports unions, generic types, and a special type named Any which is consistent with (i.e. assignable to and from) all types. This latter feature is taken from the idea of gradual typing. Gradual typing and the full type system are explained in PEP 483.

PEP 482 中描述了我们借鉴的一些方案或对比的方案。

Other approaches from which we have borrowed or to which ours can be compared and contrasted are described in PEP 482.

理由和目标

Rationale and Goals

PEP 3107 添加了对任意函数定义部分的注解支持。尽管没有给注解的实际含义,但已经有了一个隐含的目标,即将注解用于类型提示 gvr-artima,这在 PEP 3107 中是被列出的第一个可能用例。

PEP 3107 added support for arbitrary annotations on parts of a function definition. Although no meaning was assigned to annotations then, there has always been an implicit goal to use them for type hinting [gvr-artima], which is listed as the first possible use case in said PEP.

本 PEP 旨在为类型注解提供标准的语法,使 Python 代码可以更轻松地进行静态分析和重构、潜在的运行时类型检查,以及(可能在某些上下文中)使用类型信息生成代码。

This PEP aims to provide a standard syntax for type annotations, opening up Python code to easier static analysis and refactoring, potential runtime type checking, and (perhaps, in some contexts) code generation utilizing type information.

这些目标中静态分析是最重要的。这包括支持如 mypy 之类的离线类型检查器,以及提供可由 IDE 使用的标准表示法,用于代码补全和重构。

Of these goals, static analysis is the most important. This includes support for off-line type checkers such as mypy, as well as providing a standard notation that can be used by IDEs for code completion and refactoring.

非目标

Non-goals

虽然本提案所提出的 typing 模块将包含一些用于运行时类型检查的构建块——特别是 get_type_hints() 函数——必须开发第三方包才能实现特定的运行时类型检查功能,例如使用装饰器或元类。至于使用类型提示进行性能优化,就留下作为读者的练习吧。

While the proposed typing module will contain some building blocks for runtime type checking -- in particular the get_type_hints() function -- third party packages would have to be developed to implement specific runtime type checking functionality, for example using decorators or metaclasses. Using type hints for performance optimizations is left as an exercise for the reader.

还应强调,Python 仍然是一门动态类型的语言,作者也不强求必须使用类型注解。

It should also be emphasized that Python will remain a dynamically typed language, and the authors have no desire to ever make type hints mandatory, even by convention.

注解的含义

The meaning of annotations

对任何类型检查器而言,任何没有注解的函数都应该被视为可能拥有最为通用的类型,或是直接忽略。使用 @no_type_check 装饰的函数视为没有任何注解。

Any function without annotations should be treated as having the most general type possible, or ignored, by any type checker. Functions with the @no_type_check decorator should be treated as having no annotations.

建议但是不必须要求被检查的函数的所有参数和返回值都有注解。对于一个被检查的函数,其参数和返回值的默认注释都为 Any。一个问题:实例方法和类方法的第一个参数,如果其没有注解,就假定实例方法第一个参数为所属类的类型,类方法的第一个参数。例如,在 class A 中,实例方法第一个参数的类型隐含为 A,在类方法中,第一个参数的精确类型无法用类型注解描述。

It is recommended but not required that checked functions have annotations for all arguments and the return type. For a checked function, the default annotation for arguments and for the return type is Any. An exception is the first argument of instance and class methods. If it is not annotated, then it is assumed to have the type of the containing class for instance methods, and a type object type corresponding to the containing class object for class methods. For example, in class A the first argument of an instance method has the implicit type A. In a class method, the precise type of the first argument cannot be represented using the available type notation.

(注意:__init__ 函数的返回值应当使用 -> None 注解,理由十分微妙,如果假定 __init__-> None 进行返回值注解,那是否意味着无参的、无注解的 __init__ 函数仍需要被类型检查?与其模棱两可或是引入异常,不如我们规定 __init__ 应当有一个返回值注解,默认行为与其他函数保持一致。)

(Note that the return type of __init__ ought to be annotated with -> None. The reason for this is subtle. If __init__ assumed a return annotation of -> None, would that mean that an argument-less, un-annotated __init__ method should still be type-checked? Rather than leaving this ambiguous or introducing an exception to the exception, we simply say that __init__ ought to have a return annotation; the default behavior is thus the same as for other methods.)

111111111

A type checker is expected to check the body of a checked function for consistency with the given annotations. The annotations may also be used to check correctness of calls appearing in other checked functions.

111111111

Type checkers are expected to attempt to infer as much information as necessary. The minimum requirement is to handle the builtin decorators @property, @staticmethod and @classmethod.

类型定义的语法

Type Definition Syntax

1111

The syntax leverages PEP 3107-style annotations with a number of extensions described in sections below. In its basic form, type hinting is used by filling function annotation slots with classes:

这里的语法充分利用了 PEP 3107 风格的注解,并加入了以下章节介绍的一些扩展。基本格式就是把类名填到函数注解的位置:

def greeting(name: str) -> str:
    return 'Hello ' + name

This states that the expected type of the name argument is str. Analogically, the expected return type is str.

以上表示参数 name 的预期类型为 str。类似地,函数的预期返回类型为 str

Expressions whose type is a subtype of a specific argument type are also accepted for that argument.

接受的类型提示

Acceptable type hints

11111

Type hints may be built-in classes (including those defined in standard library or third-party extension modules), abstract base classes, types available in the types module, and user-defined classes (including those defined in the standard library or third-party modules).

类型提示可以是内置类(包括标准库或第三方扩展模块中定义的类),抽象基类,类型模块中可用的类型,以及用户定义的类(包括标准库中定义的类或第三类) 派对模块)。

While annotations are normally the best format for type hints, there are times when it is more appropriate to represent them by a special comment, or in a separately distributed stub file. (See below for examples.)

虽然注释通常是类型提示的最佳格式,但有时会在特殊的评论中或在单独分布的存根文件中更合适地表示它们。 (见下面的例子。)

Annotations must be valid expressions that evaluate without raising exceptions at the time the function is defined (but see below for forward references).

注释必须是有效的表达式,在不介绍定义函数时的情况下进行评估(但是看到以下用于转发引用)。

Annotations should be kept simple or static analysis tools may not be able to interpret the values. For example, dynamically computed types are unlikely to be understood. (This is an intentionally somewhat vague requirement, specific inclusions and exclusions may be added to future versions of this PEP as warranted by the discussion.)

注释应该保持简单或静态分析工具可能无法解释值。 例如,动态计算的类型不太可能被理解。 (这是一个故意有些模糊要求,可以通过讨论所需的本文的未来版本添加特定的夹杂物和排除。)

In addition to the above, the following special constructs defined below may be used: None, Any, Union, Tuple, Callable, all ABCs and stand-ins for concrete classes exported from typing (e.g. Sequence and Dict), type variables, and type aliases.

除了上面的外,可以使用以下定义的以下特殊构造:无,从键入(例如序列和区域),型变量和类型的混凝土类中,任何,Union,元组,可调用,所有abcs和its-ince 别名。

All newly introduced names used to support features described in following sections (such as Any and Union) are available in the typing module.

用于支持以下部分(例如AnyUnion)中描述的功能的新引入的名称都在键入模块中提供。

使用 None

Using None

None 用于类型提示中时,表达式 None 视作与 type(None) 等价。

When used in a type hint, the expression None is considered equivalent to type(None).

类型的别名

Type aliases

简单的变量声明就可以定义类型的别名:

Type aliases are defined by simple variable assignments:

Url = str

def retry(url: Url, retry_count: int) -> None: ...

Note that we recommend capitalizing alias names, since they represent user-defined types, which (like user-defined classes) are typically spelled that way.

Type aliases may be as complex as type hints in annotations -- anything that is acceptable as a type hint is acceptable in a type alias:

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]

This is equivalent to:

这等同于:

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

如果框架期待具体特征的回调函数(callback functions),可以使用Callable[[Arg1Type, Arg2Type], ReturnType]来进行类型提示。例如:

Frameworks expecting callback functions of specific signatures might be type hinted using Callable[[Arg1Type, Arg2Type], ReturnType]. Examples:

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

可以通过代替参数列表的文字省略(三个点)来声明可调用的返回类型,而无需指定呼叫签名:

It is possible to declare the return type of a callable without specifying the call signature by substituting a literal ellipsis (three dots) for the list of arguments:

def partial(func: Callable[..., str], *args) -> Callable[..., str]:
    # Body

Note that there are no square brackets around the ellipsis. The arguments of the callback are completely unconstrained in this case (and keyword arguments are acceptable).

请注意,省略号周围没有方形括号。 回调的参数在这种情况下完全无规定(并且关键字参数是可接受的)。

Since using callbacks with keyword arguments is not perceived as a common use case, there is currently no support for specifying keyword arguments with Callable. Similarly, there is no support for specifying callback signatures with a variable number of arguments of a specific type.

由于使用带关键字参数的回调不被视为常用用例,因此目前不支持使用可调用的可调用关键字参数。 类似地,没有支持使用特定类型的可变数量的参数指定回调签名。

Because typing.Callable does double-duty as a replacement for collections.abc.Callable, isinstance(x, typing.Callable) is implemented by deferring to isinstance(x, collections.abc.Callable). However, isinstance(x, typing.Callable[...]) is not supported.

因为 type.Callable 带有双重职能,用于替代 collections.abc.Callable,所以 isinstance(x, typing.Callable) 的实现与 isinstance(x, collections.abc.Callable) 兼容。但是,isinstance(x, typing.Callable[...]) 是不受支持的。

泛型

Generics

因为容器中保存的对象的类型信息不能以通用的方式静态推断,抽象基类被扩展为支持预定(subscription ),以标明容器内元素的期望类型。例如:

Since type information about objects kept in containers cannot be statically inferred in a generic way, abstract base classes have been extended to support subscription to denote expected types for container elements. Example:

from typing import Mapping, Set

def notify_by_email(
    employees: Set[Employee], 
    overrides: Mapping[str, str]
) -> None: ...

泛型可以通过使用typing模块提供的新的工厂函数TypeVar 的来进行参数化。 例如:

Generics can be parameterized by using a new factory available in typing called TypeVar. Example:

from typing import Sequence, TypeVar

T = TypeVar('T')      # 定义类型变量T

def first(l: Sequence[T]) -> T:   # Generic function
    return l[0]

上面的例子约定了返回值的类型与集合中元素的类型保持一致。

In this case the contract is that the returned value is consistent with the elements held by the collection.

TypeVar()表达式必须直接赋值给一个变量(不应该组成更大表达式),TypeVar()的参数必须是一个字符串,并且等于分配给的变量名称。类型变量不允许重新定义(redefine)。

A TypeVar() expression must always directly be assigned to a variable (it should not be used as part of a larger expression). The argument to TypeVar() must be a string equal to the variable name to which it is assigned. Type variables must not be redefined.

TypeVar 支持把参数可能的类型限为一组固定值(注意:固定值类型不能用类型变量实现参数化)。例如,我们可以定义一个仅包含strbytes的类型变量。默认情况下,TypeVar定义的类型变量会包含所有可能的类型。以下是一个约束类型变量范围的示例:

TypeVar supports constraining parametric types to a fixed set of possible types (note: those types cannot be parameterized by type variables). For example, we can define a type variable that ranges over just str and bytes. By default, a type variable ranges over all possible types. Example of constraining a type variable:

from typing import TypeVar, Text

AnyStr = TypeVar('AnyStr', Text, bytes)

def concat(x: AnyStr, y: AnyStr) -> AnyStr:
    return x + y

concat函数可以使用两个str类型的参数或两个bytes类型的参数调用,但不能将strbytes类型参数混合后调用。

The function concat can be called with either two str arguments or two bytes arguments, but not with a mix of str and bytes arguments.

只要存在约束条件,就至少应该有两个,不允许只指定单个约束条件。

There should be at least two constraints, if any; specifying a single constraint is disallowed.

在类型变量的上下文中,类型变量约束类型的子类型应被视作显式给出的对应基本类型。参见下面这个示例:

Subtypes of types constrained by a type variable should be treated as their respective explicitly listed base types in the context of the type variable. Consider this example:

class MyStr(str): ...

x = concat(MyStr('apple'), MyStr('pie'))

上述调用是合法的,只是类型变量 AnyStr 将被设为 str 而非 MyStr。实际上,赋给 x 的返回值,其推断类型也会是 str

The call is valid but the type variable AnyStr will be set to str and not MyStr. In effect, the inferred type of the return value assigned to x will also be str.

此外,由于Any是任何类型变量的有效值。 考虑下面这个例子:

Additionally, Any is a valid value for every type variable. Consider the following:

def count_truthy(elements: List[Any]) -> int:
    return sum(1 for elem in elements if elem)

这相当于删除了泛型注解直接写上:elements: List

This is equivalent to omitting the generic notation and just saying elements: List.

自定义的泛型类型

User-defined generic types

通过继承 Generic 基类,可将用户自定义类定义为泛型类。例如:

You can include a Generic base class to define a user-defined class as generic. Example:

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] 为基类定义了 LoggedVar 类,它接受一个类型参数 T. 这在类内部同时令 T 成为一个合法的类型。

Generic[T] as a base class defines that the class LoggedVar takes a single type parameter T. This also makes T valid as a type within the class body.

Generic 基类会用到定义了 __getitem__ 的元类,以便 LoggedVar[t] 能作为类型来使用:

The Generic base class uses a metaclass that defines __getitem__ so that LoggedVar[t] is valid as a type:

from typing import Iterable

def zero_all_vars(vars: Iterable[LoggedVar[int]]) -> None:
    for var in vars:
        var.set(0)

同一个泛型类型所赋的类型变量可以是任意多个,而且类型变量还可以用作约束条件。以下语句是合法的:

A generic type can have any number of type variables, and type variables may be constrained. This is valid:

from typing import TypeVar, Generic
...

T = TypeVar('T')
S = TypeVar('S')

class Pair(Generic[T, S]):
    ...

泛型的每个类型变量参数都必须不同。 因此,这无效:

Each type variable argument to Generic must be distinct. This is thus invalid:

from typing import TypeVar, Generic
...

T = TypeVar('T')

class Pair(Generic[T, T]):   # INVALID
    ...

在比较简单的场合,没有必要用到 Generic[T],这时可以继承其他的泛型类并指定类型变量参数:

The Generic[T] base class is redundant in simple cases where you subclass some other generic class and specify type variables for its parameters:

from typing import TypeVar, Iterator

T = TypeVar('T')

class MyIter(Iterator[T]):
    ...

以上类的定义等价于:

That class definition is equivalent to:

class MyIter(Iterator[T], Generic[T]):
    ...

可以对 Generic 使用多重继承:

You can use multiple inheritance with 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]

Subclassing a generic class without specifying type parameters assumes Any for each position. In the following example, MyIterable is not generic but implicitly inherits from Iterable[Any]:

from typing import Iterable

class MyIterable(Iterable):  # Same as Iterable[Any]
    ...

泛型元类不受支持。

Generic metaclasses are not supported.

类型变量的作用域规则

Scoping rules for type variables

类型变量遵循通常的名称解析规则。但是,在静态类型检查的上下文中,存在一些特殊的情况:

Type variables follow normal name resolution rules. However, there are some special cases in the static typechecking context:

  • 即使是在相同的代码块中,泛型函数中使用的类型变量也可以被推断为不同的类型,例如:
  • A type variable used in a generic function could be inferred to represent different types in the same code block. Example:
from typing import TypeVar, Generic

T = TypeVar('T')

def fun_1(x: T) -> T: ...  # T here
def fun_2(x: T) -> T: ...  # 此处的T可以和上面的不同

fun_1(1)                   # This is OK, T is inferred to be int
fun_2('a')                 # This is also OK, now T is str
  • A type variable used in a method of a generic class that coincides with one of the variables that parameterize this class is always bound to that variable. Example:
  • 在泛型类中,函数中的类型变量将始终和参数化这个类的变量保持一致,例如:
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: ...  # 此处T和上面的保持一致

a = MyClass()  # type: MyClass[int]
a.meth_1(1)    # OK
a.meth_2('a')  # This is an error!
  • A type variable used in a method that does not match any of the variables that parameterize the class makes this method a generic function in that variable:
  • 在泛型类中,若函数中的某类型变量和参数化这个类的任何变量都不匹配,将使该函数成为一个使用该类型的泛型函数。
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")  # 推断y的类型为str
  • Unbound type variables should not appear in the bodies of generic functions, or in the class bodies apart from method definitions:
  • 未绑定的类型变量不应出现在泛型函数内,类的内部中的非函数定义区域也是如此:
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
        ...
  • A generic class definition that appears inside a generic function should not use type variables that parameterize the generic function:
  • 在一个泛型函数内,泛型类的定义中不应该使用参数化这个函数的类型变量:
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]):
        ...
  • A generic class nested in another generic class cannot use same type variables. The scope of the type variables of the outer class doesn't cover the inner one:
  • 嵌套在其他泛型类中的泛型类不能使用相同的类型变量,外部类中类型变量的作用域并不能覆盖到内部类中:
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

User-defined generic classes can be instantiated. Suppose we write a Node class inheriting from Generic[T]:

from typing import TypeVar, Generic

T = TypeVar('T')

class Node(Generic[T]):
    ...

To create Node instances you call Node() just as for a regular class. At runtime the type (class) of the instance will be Node. But what type does it have to the type checker? The answer depends on how much information is available in the call. If the constructor (__init__ or __new__) uses T in its signature, and a corresponding argument value is passed, the type of the corresponding argument(s) is substituted. Otherwise, Any is assumed. Example:

from typing import TypeVar, Generic

T = TypeVar('T')

class Node(Generic[T]):
    x = None  # type: T # Instance attribute (see below)
    def __init__(self, label: T = None) -> None:
        ...

x = Node('')  # Inferred type is Node[str]
y = Node(0)   # Inferred type is Node[int]
z = Node()    # Inferred type is Node[Any]

In case the inferred type uses [Any] but the intended type is more specific, you can use a type comment (see below) to force the type of the variable, e.g.:

# (continued from previous example)
a = Node()  # type: Node[int]
b = Node()  # type: Node[str]

Alternatively, you can instantiate a specific concrete type, e.g.:

# (continued from previous example)
p = Node[int]()
q = Node[str]()
r = Node[int]('')  # Error
s = Node[str](0)   # Error

Note that the runtime type (class) of p and q is still just Node -- Node[int] and Node[str] are distinguishable class objects, but the runtime class of the objects created by instantiating them doesn't record the distinction. This behavior is called "type erasure"; it is common practice in languages with generics (e.g. Java, TypeScript).

Using generic classes (parameterized or not) to access attributes will result in type check failure. Outside the class definition body, a class attribute cannot be assigned, and can only be looked up by accessing it through a class instance that does not have an instance attribute with the same name:

# (continued from previous example)
Node[int].x = 1  # Error
Node[int].x      # Error
Node.x = 1       # Error
Node.x           # Error
type(p).x        # Error
p.x              # Ok (evaluates to None)
Node[int]().x    # Ok (evaluates to None)
p.x = 1          # Ok, but assigning to instance attribute

Generic versions of abstract collections like Mapping or Sequence and generic versions of built-in classes -- List, Dict, Set, and FrozenSet -- cannot be instantiated. However, concrete user-defined subclasses thereof and generic versions of concrete collections can be instantiated:

data = DefaultDict[int, bytes]()

Note that one should not confuse static types and runtime classes. The type is still erased in this case and the above expression is just a shorthand for:

data = collections.defaultdict()  # type: DefaultDict[int, bytes]

It is not recommended to use the subscripted class (e.g. Node[int]) directly in an expression -- using a type alias (e.g. IntNode = Node[int]) instead is preferred. (First, creating the subscripted class, e.g. Node[int], has a runtime cost. Second, using a type alias is more readable.)

Arbitrary generic types as base classes

Generic[T] is only valid as a base class -- it's not a proper type. However, user-defined generic types such as LinkedList[T] from the above example and built-in generic types and ABCs such as List[T] and Iterable[T] are valid both as types and as base classes. For example, we can define a subclass of Dict that specializes type arguments:

from typing import Dict, List, Optional

class Node:
    ...

class SymbolTable(Dict[str, List[Node]]):
    def push(self, name: str, node: Node) -> None:
        self.setdefault(name, []).append(node)

    def pop(self, name: str) -> Node:
        return self[name].pop()

    def lookup(self, name: str) -> Optional[Node]:
        nodes = self.get(name)
        if nodes:
            return nodes[-1]
        return None

SymbolTable is a subclass of dict and a subtype of Dict[str, List[Node]].

If a generic base class has a type variable as a type argument, this makes the defined class generic. For example, we can define a generic LinkedList class that is iterable and a container:

from typing import TypeVar, Iterable, Container

T = TypeVar('T')

class LinkedList(Iterable[T], Container[T]):
    ...

Now LinkedList[int] is a valid type. Note that we can use T multiple times in the base class list, as long as we don't use the same type variable T multiple times within Generic[...].

Also consider the following example:

from typing import TypeVar, Mapping

T = TypeVar('T')

class MyDict(Mapping[str, T]):
    ...

In this case MyDict has a single parameter, T.

Abstract generic types

The metaclass used by Generic is a subclass of abc.ABCMeta. A generic class can be an ABC by including abstract methods or properties, and generic classes can also have ABCs as base classes without a metaclass conflict.

绑定上界的类型变量

Type variables with an upper bound

一个类型变量可以使用bound=<type>指定一个上界类型(注意这里的<type>不能使用类型变量参数化)。这意味着,显式或隐式替换类型变量的实际类型必须是上界类型的子类型,例如:

A type variable may specify an upper bound using bound= (note: itself cannot be parameterized by type variables). This means that an actual type substituted (explicitly or implicitly) for the type variable must be a subtype of the boundary type. Example:

from typing import TypeVar, Sized

ST = TypeVar('ST', bound=Sized)

def longer(x: ST, y: ST) -> ST:
    if len(x) > len(y):
        return x
    else:
        return y

longer([1], [1, 2])  # ok, return type List[int]
longer({1}, {1, 2})  # ok, return type Set[int]
longer([1], {1, 2})  # ok, return type Collection[int]

An upper bound cannot be combined with type constraints (as in used AnyStr, see the example earlier); type constraints cause the inferred type to be exactly one of the constraint types, while an upper bound just requires that the actual type is a subtype of the boundary type.

Covariance and contravariance

Consider a class Employee with a subclass Manager. Now suppose we have a function with an argument annotated with List[Employee]. Should we be allowed to call this function with a variable of type List[Manager] as its argument? Many people would answer "yes, of course" without even considering the consequences. But unless we know more about the function, a type checker should reject such a call: the function might append an Employee instance to the list, which would violate the variable's type in the caller.

It turns out such an argument acts contravariantly, whereas the intuitive answer (which is correct in case the function doesn't mutate its argument!) requires the argument to act covariantly. A longer introduction to these concepts can be found on Wikipedia [wiki-variance] and in PEP 483; here we just show how to control a type checker's behavior.

By default generic types are considered invariant in all type variables, which means that values for variables annotated with types like List[Employee] must exactly match the type annotation -- no subclasses or superclasses of the type parameter (in this example Employee) are allowed.

To facilitate the declaration of container types where covariant or contravariant type checking is acceptable, type variables accept keyword arguments covariant=True or contravariant=True. At most one of these may be passed. Generic types defined with such variables are considered covariant or contravariant in the corresponding variable. By convention, it is recommended to use names ending in _co for type variables defined with covariant=True and names ending in _contra for that defined with contravariant=True.

A typical example involves defining an immutable (or read-only) container class:

from typing import TypeVar, Generic, Iterable, Iterator

T_co = TypeVar('T_co', covariant=True)

class ImmutableList(Generic[T_co]):
    def __init__(self, items: Iterable[T_co]) -> None: ...
    def __iter__(self) -> Iterator[T_co]: ...
    ...

class Employee: ...

class Manager(Employee): ...

def dump_employees(emps: ImmutableList[Employee]) -> None:
    for emp in emps:
        ...

mgrs = ImmutableList([Manager()])  # type: ImmutableList[Manager]
dump_employees(mgrs)  # OK

The read-only collection classes in typing are all declared covariant in their type variable (e.g. Mapping and Sequence). The mutable collection classes (e.g. MutableMapping and MutableSequence) are declared invariant. The one example of a contravariant type is the Generator type, which is contravariant in the send() argument type (see below).

Note: Covariance or contravariance is not a property of a type variable, but a property of a generic class defined using this variable. Variance is only applicable to generic types; generic functions do not have this property. The latter should be defined using only type variables without covariant or contravariant keyword arguments. For example, the following example is fine:

from typing import TypeVar

class Employee: ...

class Manager(Employee): ...

E = TypeVar('E', bound=Employee)

def dump_employee(e: E) -> None: ...

dump_employee(Manager())  # OK

while the following is prohibited:

B_co = TypeVar('B_co', covariant=True)

def bad_func(x: B_co) -> B_co:  # Flagged as error by a type checker
    ...

数字类型的层级关系

The numeric tower

PEP 3141定义了Python数字类型的层级关系,stdlib模块中的numbers实现了相应的抽象基类(Number, Complex, Real, RationalIntegral)。关于这些抽象基类还有一些争议,但是内置的数字类型如complexfloat还有int已经在普遍使用了(尤其是后两者)。

PEP 3141 defines Python's numeric tower, and the stdlib module numbers implements the corresponding ABCs (Number, Complex, Real, Rational and Integral). There are some issues with these ABCs, but the built-in concrete numeric classes complex, float and int are ubiquitous (especially the latter two :-).

与其让用户去写 import numbers 来导入然后使用如 Float 等数字类,本PEP提供了一种简洁、高效的途径:只要注解为 float 类型的参数,使用 int 类型也是合法的,同理,注解为 complex 类型的参数,使用 floatint 类型也是合法的。但缺陷是无法处理实现了上述抽象基类的类或是实现了 fractions.Fraction 的类,不过我们相信这种情况十分罕见。

Rather than requiring that users write import numbers and then use numbers.Float etc., this PEP proposes a straightforward shortcut that is almost as effective: when an argument is annotated as having type float, an argument of type int is acceptable; similar, for an argument annotated as having type complex, arguments of type float or int are acceptable. This does not handle classes implementing the corresponding ABCs or the fractions.Fraction class, but we believe those use cases are exceedingly rare.

前向引用

Forward references

当类型提示包含尚还未定义的名称时,未定义名称可以先表示为字符串字面量,稍后再作解析。

When a type hint contains names that have not been defined yet, that definition may be expressed as a string literal, to be resolved later.

在定义容器类时,通常就会发生这种情况,这时在某些方法的签名中会出现将要定义的类。例如,以下代码(简单的二叉树实现的开始部分)将无法生效:

A situation where this occurs commonly is the definition of a container class, where the class being defined occurs in the signature of some of the methods. For example, the following code (the start of a simple binary tree implementation) does not work:

class Tree:
    def __init__(self, left: Tree, right: Tree):
        self.left = left
        self.right = right

To address this, we write:

class Tree:
    def __init__(self, left: 'Tree', right: 'Tree'):
        self.left = left
        self.right = right

The string literal should contain a valid Python expression (i.e., compile(lit, '', 'eval') should be a valid code object) and it should evaluate without errors once the module has been fully loaded. The local and global namespace in which it is evaluated should be the same namespaces in which default arguments to the same function would be evaluated.

Moreover, the expression should be parseable as a valid type hint, i.e., it is constrained by the rules from the section Acceptable type hints above.

It is allowable to use string literals as part of a type hint, for example:

class Tree:
    ...
    def leaves(self) -> List['Tree']:
        ...

A common use for forward references is when e.g. Django models are needed in the signatures. Typically, each model is in a separate file, and has methods taking arguments whose type involves other models. Because of the way circular imports work in Python, it is often not possible to import all the needed models directly:

# File models/a.py
from models.b import B
class A(Model):
    def foo(self, b: B): ...

# File models/b.py
from models.a import A
class B(Model):
    def bar(self, a: A): ...

# File main.py
from models.a import A
from models.b import B

Assuming main is imported first, this will fail with an ImportError at the line from models.a import A in models/b.py, which is being imported from models/a.py before a has defined class A. The solution is to switch to module-only imports and reference the models by their module.class name:

# File models/a.py
from models import b
class A(Model):
    def foo(self, b: 'b.B'): ...

# File models/b.py
from models import a
class B(Model):
    def bar(self, a: 'a.A'): ...

# File main.py
from models.a import A
from models.b import B

Union类型

Union types

因为一个参数可接受数量有限的几种预期类型是常见需求,所以系统新提供了一个特殊的工厂类,名为 Union。例如:

Since accepting a small, limited set of expected types for a single argument is common, there is a new special factory called Union. Example:

from typing import Union

def handle_employees(e: Union[Employee, Sequence[Employee]]) -> None:
    if isinstance(e, Employee):
        e = [e]
    ...

Union[T1,T2,...]构造的类型是类型T1T2等的超类型,因此由Union注释的参数是可接受的,这是其中一个类型的值[T1 ,T2,...]

一个常见的Union类型是可选类型。 默认情况下,None是任何类型的一个非法值,除非在函数定义中提供了默认值。 例如:

def handle_employee(e: Union[Employee, None]) -> None: ...

你可以使用Optional[T1]作为Union[T1, None]的简写,例如,上述代码相当于:

from typing import Optional

def handle_employee(e: Optional[Employee]) -> None: ...

通过 Union 支持单例类型

Support for singleton types in unions

单实例通常用于标记某些特殊条件,特别是 None 也是合法变量值的情况下。例如:

_empty = object()

def func(x=_empty):
    if x is _empty:  # 默认参数的情况
        return 0
    elif x is None:  # 入参为None的情况
        return 1
    else:
        return x * 2

为了在这种情况下允许精确设定类型,用户应结合使用 Union 类型和标准库提供的 enum.Enum 类,这样就能静态捕获类型错误了:

from typing import Union
from enum import Enum

class Empty(Enum):
    token = 0
_empty = Empty.token

def func(x: Union[int, None, Empty] = _empty) -> int:

    boom = x * 42  # 类型检查会提示错误

    if x is _empty:
        return 0
    elif x is None:
        return 1
    else:  # 类型检查器知道此处x的类型只能为int
        return x * 2

因为 Enum 的子类无法被继承(subclassed),所以上面的例子中所有分支里,变量x的类型都可以被静态推断。 多个单例对象的情况下也适用相同的方法:可以使用具有多个值的枚举:

class Reason(Enum):
    timeout = 1
    error = 2

def process(response: Union[str, Reason] = '') -> str:
    if response is Reason.timeout:
        return 'TIMEOUT'
    elif response is Reason.error:
        return 'ERROR'
    else:
        # response的类型只能是str, 因为其他所有的可能都被排除了
        return 'PROCESSED: ' + response

Any类型

The Any type

Any是一种特殊的类型,它和任何类型都相符,可以被视为拥有所有的值(values)和方法(methods)。注意,Any和内置模块(builtin)中的对象完全不同。

当某个值的类型为 object 时,类型检查程序将拒绝几乎所有对其进行的操作,将其赋给类型更具体的变量(或将其用作返回值)将是一种类型错误。反之,当值的类型为Any时,类型检查程序将允许对其执行的所有操作,并且Any 类型的值可以赋给类型更具体(constrained)的变量(或用作返回值)。

类型检查器对不带类型注解的函数参数会假定其使用Any 注解。如果在没有指定类型参数的情况下使用泛型(generic type),则也假定其参数类型为 Any

from typing import Mapping

def use_map(m: Mapping) -> None:  # Same as Mapping[Any, Any]
    ...

此规则也适用于元组(Tuple),在类型注解的上下文中,它相当于Tuple[Any, ...]tuple也是如此。此外,在类型注解的上下文中Callable等效于Callable[..., Any]collections.abc.Callable也是如此。

from typing import Tuple, List, Callable

def check_args(args: Tuple) -> bool:
    ...

check_args(())           # OK
check_args((42, 'abc'))  # Also OK
check_args(3.14)         # 类型检查器会标记为错误

# 这个函数的入参为一个任意的可调用的(Callable)列表
def apply_callbacks(cbs: List[Callable]) -> None:
    ...

NoReturn类型

The NoReturn type

typing模块提供一种特殊的类型NoReturn,用于注解一定不会正常返回(return)的函数。例如一个将无条件抛出异常的函数:

from typing import NoReturn

def stop() -> NoReturn:
    raise RuntimeError('no way')

NoReturn注解用于诸如sys.exit等函数,静态类型检查器还将确保以NoReturn注解的函数真正的永不返回,无论是隐式的还是显式的:

import sys
from typing import NoReturn

def f(x: int) -> NoReturn:  # Error, f(0) 隐含的返回None
    if x != 0:
        sys.exit(1)

静态类型检查器还会识别出调用此类函数后面的代码是否可达,并采取相应动作:

# 上接第一个例子
def g(x: int) -> int:
    if x > 0:
        return x
    stop()
    return 'whatever works'  # 一些检查器可能不会提示错误
                             # 因为它们会忽略不可达的代码块中的错误

NoReturn类型仅作为函数的返回注解有效,如果出现在其他位置,则被视为错误:

from typing import List, NoReturn

# 以下都会提示错误
def bad1(x: NoReturn) -> int:
    ...
bad2 = None  # type: NoReturn
def bad3() -> List[NoReturn]:
    ...

类对象的类型

The type of class objects

有些情况会对类对象有疑惑,特别是从给定类继承而来的类对象。当C是一个类,可以使用Type[C]来表示类对象C的类型。当C(在注解中使用的时候)指class C的实例时,Type[C]指的是C的子类。(这类似于对象和类型之间的区别。)

例如,假定我们有下列类:

class User: ...  # Abstract base for User classes
class BasicUser(User): ...
class ProUser(User): ...
class TeamUser(User): ...

再假定我们拥有一个函数,当传入其中的一个类,返回这个类的实例。

def new_user(user_class):
    user = user_class()
    # (此处我们可以把user对象写入数据库)
    return user

不使用Type[]的话,我们给new_user()函数的最好注解就是:

def new_user(user_class: type) -> User:
    ...

通过使用Type[]和一个向上绑定(upper bound)的类型变量,我们可以做的更好:

U = TypeVar('U', bound=User)
def new_user(user_class: Type[U]) -> U:
    ...

现在,我们使用User类的一个明确子类调用new_user(),类型检查器将会推断出返回值的正确类型:

joe = new_user(BasicUser)  # 推断joe的类型为BasicUser

Type[C]相应的值必须是一个实际的类对象,且是C的子类型(subtype),非其特殊形式。换句话说,上述例子中如果这样调用new_user(Union[BasicUser, ProUser])是会被类型检查器拒绝接受的(如果运行的话也会失败,因为Union不能实例化)。

但要注意,在Type[]中使用Union是合法的,比如:

def new_non_team_user(user_class: Type[Union[BasicUser, ProUser]]):
    user = new_user(user_class)
    ...

上述例子中,运行时传入的实际参数必须是一个具体类对象:

new_non_team_user(ProUser)  # OK
new_non_team_user(TeamUser)  # Disallowed by type checker

Type[Any]也是支持的(下面说明它的含义)。

注解实例方法和类方法

Annotating instance and class methods

在绝大多数情形中,类方法和实例方法的第一个参数无需类型注解。因为如果是实例方法,类型检查器会假定它的类型就是所在类(的类型),如果是类方法,它就是所在类对象对应的类型对象(的类型)。需要补充的是,实例方法的第一个参数可以使用类型变量进行注解,这时返回值的类型可以使用相同的类型变量,从而使这个函数成为一个泛型函数。例如:

T = TypeVar('T', bound='Copyable')
class Copyable:
    def copy(self: T) -> T:
        # return a copy of self

class C(Copyable): ...
c = C()
c2 = c.copy()  # c2的类型为C

同样,类方法第一个参数可以使用Type[]进行注解 :

T = TypeVar('T', bound='C')
class C:
    @classmethod
    def factory(cls: Type[T]) -> T:
        # make a new instance of cls

class D(C): ...
d = D.factory()  # d的类型为D

值得注意的是某些类型检查器可能对上述用法施加限制,比如要求类型变量具备合适的向上绑定(upper bound)类型,参见上例。

版本和平台检查

Version and platform checking

类型检查器应当能够理解简单的版本和平台检查语句,例如:

import sys

if sys.version_info[0] >= 3:
    # Python 3的特有定义语句
else:
    # Python 2的特有定义语句

if sys.platform == 'win32':
    # Windows平台的特有定义语句
else:
    # Posix平台的特有定义语句

不要期待检查器能够理解 "".join(reversed(sys.platform)) == "xunil" 这样的晦涩语句。

运行时还是类型检查时?

Runtime or type checking?

有时一些代码必须由类型检查器(或是其他静态分析工具)进行分析,但不能被运行。为了应对这种情况, typing 模块定义了一个常量—— TYPE_CHECKING ,它在类型检查期间(或是静态分析期间)为 True,运行时为 False ,例如:

import typing

if typing.TYPE_CHECKING:
    import expensive_mod

def a_func(arg: 'expensive_mod.SomeClass') -> None:
    a_var = arg  # type: expensive_mod.SomeClass
    ...

(注意类型注解必须使用引号进行包裹,使它成为“前向引用”,以便解释器运行时不去导入 expensive_mod 模块,另外使用 # type 进行类型注释时无需引号。)

这种方式也能够有效的处理循环导入(import cycles)。

可变参数列表和默认参数值

Arbitrary argument lists and default argument values

可变参数列表也可以使用注类型注解,如下:

def foo(*args: str, **kwds: int): ...

下面的函数调用都是可以通过类型检查的:

foo('a', 'b', 'c')
foo(x=1, y=2)
foo('', z=0)

位置参数

Positional-only arguments

一些函数被设计为只接受位置参数,希望调用者不要使用关键字参数。名称以__开头的参数都会被假定为只能按位置访问(positional-only),除非这些参数还以__结尾。

def quux(__x: int, __y__: int = 0) -> None: ...

quux(3, __y__=1)  # This call is fine.

quux(__x=3)  # This call is an error.

Annotating generator functions and coroutines

与函数注解其他用法的兼容性

Compatibility with other uses of function annotations

存在一些函数注解的使用场景,是与类型提示不兼容的。这些场景可能导致静态类型检查器的混乱,但是由于类型检查注解并不在运行时生效(计算注解表达式、将注解存储在函数对象的 __annotations__ 属性中除外),所以这个并不会使程序发生错误——这仅可能造成类型检查器发出虚假的警告和报错。

为了标记一部分代码不被类型提示影响,可以用下面这些方式:

  • # type: ignore 注释;
  • 在类或函数上使用 @no_type_check 装饰器;
  • 在自定义的类或函数装饰器中使用 @no_type_check_decorator 标记;

后续章节将提供更多信息。

为了最大程度的和离线类型检查兼容,将依赖于注解的机制改为其他机制(如装饰器)可能是个更好的主意。不过这在 Python 3.5 中没什么关系。更多讨论请参见后续的“没有被接受的方案”。

类型注释

Type comments

本PEP并未将变量明确标为某个类型提供一等语法支持。为了在复杂情况下进行类型推断,可以采用以下格式的注释:

x = []                # type: List[Employee]
x, y, z = [], [], []  # type: List[int], List[int], List[str]
x, y, z = [], [], []  # type: (List[int], List[int], List[str])
a, b, *c = range(5)   # type: float, float, List[float]
x = [1, 2]            # type: List[int]

类型注释应放在包含变量定义的语句的最后一行中,还可以放在 withfor 语句的冒号后面,例如:

with frobnicate() as foo:  # type: int
    # Here foo is an int
    ...

for x, y in points:  # type: float, float
    # Here x and y are floats
    ...

在存根文件中,不给出初始值而仅申明变量,这可能会有帮助。通过PEP 526的变量注解语法即可实现:

from typing import IO

stream: IO[str]

上面的存根文件中的语法在所有版本的 Python 中均可接受,但是在 Python 3.5 之前的版本的非存根文件中,有一个特例:

from typing import IO

stream = None  # type: IO[str]

尽管 None 与给定的类型不符,类型检查器也不应该对此给出报错,也不应该将类型推断的结果修改为 Optional[...](尽管规则要求默认值为 None 的应该如此申明)。这里假定其他代码保证变量是给定的类型,并且以后的所有调用都假定该变量为该给定类型。

# type: ignore 注释应当放在提示错误的那一行

import http.client
errors = {
    'not_found': http.client.NOT_FOUND  # type: ignore
}

# type: ignore 注释在文件的头一行,或者在任何文档字符串之前,又或者在任何 import 和可执行代码之前,这表示忽略文件中的所有类型检查错误。空行或是其他注释(比如 shebang 注释或是 coding cookies)可以在其之前。

有些情况下,类型注释可能需要与语法检查工具或其他注释同处一行,此时类型注释应位于其他注释和 lint 标记之前:

# type: ignore # <comment or other marker>

如果大多时候类型提示能被证明有用,那么将来版本的 Python 可能会为 typing 变量提供语法。(更新:这些语法已在 Python 3.6 加入,详见PEP-526

注:

x = dict(
    a=1,
    b=2
)  # type: Dict[str, int]

cast 函数

Casts

有时类型检查器需要另一种提示:程序员可能知道某个表达式是比类型检查器所推断的类型更加精确的类型。 例如:

from typing import List, cast

def find_first_str(a: List[object]) -> str:
    index = next(i for i, x in enumerate(a) if isinstance(x, str))
    # We only get here if there's at least one string in a
    return cast(str, a[index])

某些类型检查器可能无法推断 a[index] 的类型是 str,或是只能推断为 objectAny。但我们知道(如果代码运行到此处)它必然是一个字符串。cast(t, x) 这个调用告诉类型检查器,我们确定 x 的类型是 t。 在运行时,cast 始终返回 x 的传入值——它不会检查类型,也不会强制转换该值。

cast 与类型注释(见上一节)不同。 使用类型注释时,类型检查器仍应验证推断类型是否与所述类型一致。 使用 cast 时,类型检查器应该无条件的相信程序员。 此外,cast 可以在表达式中使用,而类型注释仅适用于声明语句。

工具函数 NewType

NewType helper function

某些情形下,程序员可能通过创建一些简单类来避免逻辑错误,例如:

class UserId(int):
    pass

get_by_user_id(user_id: UserId):
    ...

然而这引入了额外的运行时开销,为了避免这种情况,typing.py提供了一个工具函数NewType用以创建简单的类型,而且几乎没有运行时开销。对于静态类型检查器而言Derived = NewType('Derived', Base)相当于如下定义:

class Derived(Base):
    def __init__(self, _x: Base) -> None:
        ...

当处于运行时,NewType('Derived', Base) 返回一个伪函数(dummy function),这个函数直接返回其传入值。当类型检查器期待 UserId 时,需要显式的将 int 转为 UserId,期待 int 时,UserId 会隐式的转换为 int,来看这个例子:

UserId = NewType('UserId', int)

def name_by_id(user_id: UserId) -> str:
    ...

UserId('user')          # Fails type check

name_by_id(42)          # Fails type check
name_by_id(UserId(42))  # OK

num = UserId(5) + 1     # type: int

NewType 只接受两个参数:一个新的唯一的类型名称和一个基类。后者应当为一个恰当的类(不是像 Union 这样的类型构造类,但可以是调用 NewType 生成的其他唯一类型)。NewType 返回的函数仅接受一个参数;这相当于一个只接受一个基类实例的构造器(参见上文)。例如:

class PacketId:
    def __init__(self, major: int, minor: int) -> None:
        self._major = major
        self._minor = minor

TcpPacketId = NewType('TcpPacketId', PacketId)

packet = PacketId(100, 100)
tcp_packet = TcpPacketId(packet)  # OK

tcp_packet = TcpPacketId(127, 0)  # Fails in type checker and at runtime

NewType('Derived', Base) 进行子类化,或是调用 isinstance 以及 issubclass 都会失败,这是因为函数对象不支持这些操作。

存根文件

Stub Files

存根文件是一些包含类型提示的文件,仅被类型检查器使用(运行时不使用)。下面是一些使用存根文件的情形:

  • 扩展模块

  • 一些作者没有添加类型提示的第三方模块

  • 没有写类型提示的标准库模块
  • 一些必须兼容Python 2和 Python 3的模块
  • 出于其他考虑使用注解的模块

存根文件和常规的Python模块拥有相同的语法,typing 模块中有一项特性在存根文件中会有所不同: @overload 装饰器,下面会详细描述。

类型检查器应当只检查存根文件中的函数声明(function signatures),建议存根文件中的函数体只是简单的写上省略号 ...

类型检查器应该支持配置存根文件的搜索路径,一旦类型检查器发现存根文件就不应当再读取真实的模块代码。

尽管存根文件也是语法正确的Python模块,但它们使用 .pyi 后缀,这样存根文件可以和对应的真实模块文件放在同一目录。这样也加强了一个概念:存根文件不会在运行时有任何行为。

一些关于存根文件附加说明:

  • 除了在存根文件中使用 import ... as ... 或等效的 from ... import ... as ... 进行导入模块和变量,其他情况下导入到存根文件中的模块和变量都视为不能导出。(补充:说的更明白一些就是只有使用To clarify, the intention here is that only names imported using the form X as X will be exported, i.e. the name before and after as must be the same.)
  • 然而,上一条有一个例外,所有使用 from ... import * 导入存根文件的对象,都视为能被导出。这使得更容易从给定模块重新导出所有对象,因为这个模块会由于Python版本产生不同。

Function/method overloading

Storing and distributing stub files

The Typeshed Repo

异常

Exceptions

针对本特性可引发的异常,没有语法上的建议。目前本特性唯一已知的应用场景就是作为文档记录,这时建议将信息放入文档字符串(docstring)中。

typing模块

The typing Module

Suggested syntax for Python 2.7 and straddling code

没有被接受的方案

Rejected Alternatives

在讨论此PEP的早期草案期间,提出了各种异议,提出了替代方案。我们在这里讨论其中一些并解释为什么我们拒绝它们。

下面是几个主要的反对意见。

泛型参数该用哪个括号?

Which brackets for generic type parameters?

大多数人都熟悉使用 C++,Java,C# 和 Swift 的语言中的角度括号(例如 List<int>),以表达通用类型的参数化。 这些问题是他们真的很难解析,特别是对于像python这样的简单的解析器。 在大多数语言中,含糊不仅可以通过特定的句法位置中的角度括号来处理歧义,其中不允许常规表达式。 (并且还通过使用非常强大的解析技术,可以在代码的任意部分上返回备份。)

但是在 Python 中,我们希望键入表达式(句法)与其他表达式相同,以便我们可以使用e .. 可变分配来创建类型别名。 考虑此简单类型表达式:

List<int>

从Python Parser的角度来看,表达式以相同的四个令牌(姓名,少,名称,更大)开始,作为链接的比较:

a < b > c  # I.e., (a < b) and (b > c)

我们甚至可以构成一个可以解析两种方式的示例:

a < b > [ c ]

假设我们在语言中有角度括号,这可以解释为以下两个之一:

(a<b>)[c]      # I.e., (a<b>).__getitem__(c)
a < b > ([c])  # I.e., (a < b) and (b > [c])

肯定有可能提出消除此类案件的规则,但对于大多数用户来说,规则将觉得任意和复杂。 它还要求我们大大改变CPython解析器(以及Python的每个其他解析器)。 应该指出的是,Python的当前解析器故意“愚蠢” - 一个简单的语法对用户更容易推理。

出于所有这些原因,方括号(例如,列表[int])是(并且长期以来)的通用类型参数的首选语法。 可以通过在Metaclass上定义__getItem __()方法来实现它们,并且根本不需要新的语法。 此选项在所有最近版本的Python中工作(Python 2.2起)。 Python在这个句法选择中并不孤单 - Scala中的通用类也使用方括号。

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

本 PEP 的最新文稿位于 GitHub 上。另有一个议题跟踪包含了很多技术讨论内容。

GitHub 上的文稿会定期进行小幅更新。通常正式的 PEPS 库只在新文稿发布到 python-dev 时才会更新。

致谢

Acknowledgements

没有 Jim Baker、Jeremy Siek、Michael Matson Vitousek、Andrey Vlasovskikh、Radomir Dopieralski、Peter Ludemann 和BDFL-Delegate、Mark Shannon 的宝贵投入、鼓励和建议,本文就无法完成。

本文受到 PEP 482 中提及的现有语言、库和框架的影响。非常感谢其创建者(按字母序排列):Stefan Behnel、William Edwards、Greg Ewing、Larry Hastings、Anders Hejlsberg、Alok Menghrajani、Travis E. Oliphant、Joe Pamer、Raoul-Gabriel Urma、and Julien Verlaguet。

参考

References

[mypy] http://mypy-lang.org
[gvr-artima] http://www.artima.com/weblogs/viewpost.jsp?thread=85551
[wiki-variance] http://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29
[typeshed] https://github.com/python/typeshed/
[pyflakes] https://github.com/pyflakes/pyflakes/
[pylint] http://www.pylint.org
[roberge] http://aroberge.blogspot.com/2015/01/type-hinting-in-python-focus-on.html
[github] https://github.com/python/typing
[issues] https://github.com/python/typing/issues
[peps] https://hg.python.org/peps/file/tip/pep-0484.txt
[importdocs] https://docs.python.org/3/reference/import.html#submodules

版权

Copyright

本文档已在公共领域开放。

源码: https://github.com/python/peps/blob/master/pep-0484.txt