• PEP 483 类型提示的理论 -- Python官方文档译文 [原创]


    PEP 483 -- 类型提示的理论(The Theory of Type Hints)

    英文原文:https://www.python.org/dev/peps/pep-0483
    采集日期:2020-01-25

    PEP: 483
    Title: The Theory of Type Hints
    Author: Guido van Rossum guido@python.org、Ivan Levkivskyi levkivskyi@gmail.com
    Discussions-To: Python-Ideas python-ideas@python.org
    Status: Final
    Type: Informational
    Created: 19-Dec-2014
    Post-History:
    Resolution:

    目录

    摘要(Abstract)


    本 PEP 列出了 PEP 484 的理论知识。

    简介(Introduction)


    本文档列出了 Python 3.5 新引入的类型提示提案的理论知识。因为尚有很多细节需要制定,所以这还算不上是一个完整的提案或规范,但没有这里罗列的理论,就很难对更详细的规范进行讨论。本文首先将回顾类型理论的基本概念,再解释渐进定型(gradual typing),然后再声明一些通用规则并定义一些可用于注释的的新增特殊类型(如 Union),最后定义了泛型的实现方案并给出类型提示的语用学定义。

    符号约定(Notational conventions)


    • t1t2u1u2 等等均表示某种类型。有时会用 titj 表示“t1t2 等中的任一类型”。
    • T, U 之类表示类型变量(由 TypeVar() 定义,参见下文)。
    • 对象、类由 class 语句定义,实例用标准的 PEP 8 约定进行表示。
    • 本文中类型上的 == 符号意味着两个表达式代表着同一个类型。
    • 注意 PEP 484 对类型和类做了区分(类型是对类型检查程序而言的概念,而类是运行时概念)。本文明晰了这种区别,但避免做不必要的严格区分,以便能更加灵活地实现类型检查程序。

    背景知识(Background)


    类型的概念在文字上有很多定义。这里假定“类型”是一组值和一组可应用于这些值的函数。

    定义类型的方式有很多:

    • 显式列出所有值。比如 TrueFalse 形成了 bool 类型。

    • 定义可用于某类型变量的函数。比如所有带有 __len__ 方法的对象形成了 Sized 类型。[1, 2, 3]'abc' 都属于 Sized 类型,因为可以对其进行 len 调用:

        len([1, 2, 3])  # OK
        len('abc')      # also OK
        len(42)         # not a member of Sized
      
    • 直接定义一个类。比如有了以下类定义,则其所有实例也形成了一个类型:

        class UserID(int):
            pass
      
    • 还有一些比较复杂的类型。比如可以将仅包含 intstr 及其子类数据的所有列表定义为 FancyList 类型,[1, 'abc', UserID(42)] 即为这种类型。

    对于用户而言,重要的是能以类型检查程序可理解的形式定义类型。本文目标是要为变量和函数的类型注解(type annotation)提出一种采用 PEP 3107 语法进行类型定义的系统性方案。作为文档,这些注解可用于避免多种 bug,甚至可能会提高程序的执行速度。本文只重点关注用静态类型检查程序来避免出现 bug。

    子型关系(Subtype relationships)


    静态类型检查程序的关键概念是子型关系。它由以下问题引发:如果 first_varfirst_type 类型,second_varsecond_type 类型,那么 first_var = second_var 是否安全呢?

    何时应该安全的有力准则是:

    • second_type 类型的每个值同时也位于 first_type 类型的值集中;并且
    • first_type 类型的每个函数也都位于 second_type 类型的函数集中。

    上述关系被称为子型关系。

    根据以上定义:

    • 每个类型均为自身的子类型。
    • 在子类化过程中,值的集合会越变越小,而函数的集合会越变越大。

    举个直观的例子:每个 Dog 都是 Animal 类型,Dog 的函数也比 Animal 多,比如会叫,因此 DogAnimal 的子类型。相反 Animal 就不是 Dog 的子类型。

    更正规一点的例子:整数是实数的子类型。其实每个整数当然也是实数,而整数支持更多操作,比如移位操作 <<>>

      lucky_number = 3.14    # type: float
      lucky_number = 42      # Safe
      lucky_number * 2       # This works
      lucky_number << 5      # Fails
    
      unlucky_number = 13    # type: int
      unlucky_number << 5    # This works
      unlucky_number = 2.72  # Unsafe
    

    下面再来看一个棘手的例子:如果 List[int] 表示整数列表类型,那它就不是实数列表类型 List[float] 的子类型。这里子类型的第一个条件成立,但添加实数的操作仅适用于 List[float],因此第二个条件会失败:

      def append_pi(lst: List[float]) -> None:
          lst += [3.14]
    
      my_list = [1, 3, 5]  # type: List[int]
    
      append_pi(my_list)   # Naively, this should be safe...
    
      my_list[-1] << 5     # ... but this fails
    

    在供类型检查程序使用的子类型信息声明方式中,广泛采用的有两种。

    在名义子类型确定(nominal subtyping)时,类型树是基于类树的,也就是说将 UserID 视作 int 的子类型。应该在类型检查程序的控制下进行这种子类型化操作,因为在 Python 中,属性可能以非兼容方式被覆盖掉:

      class Base:
          answer = '42' # type: str
    
      class Derived(Base):
          answer = 5 # should be marked as error by type checker
    

    在声明结构子类型确定(structural subtyping)时,子型关系是由已声明的方法推导出来的,即 UserIDint 将被视作相同类型。尽管有时候这可能会引起混乱,但结构子类型被认为更灵活。这里尽量对这两种方案都提供支持,这样在名义子类型之外还可让结构信息也派上用场。

    渐进定型概述(Summary of gradual typing)


    渐进定型允许只给一部分程序加上注解,因此可以充分利用动态和静态类型确定的各自优势。

    这里定义了一个新的关系:一致(is-consistent-with),它和子类(is-subtype-of)类似,但在遇到新类型 Any 时是不可传递的。(这两种关系都是不对称的。)如果 a_value 的类型与 a_variable 的类型一致,则将 a_value 赋值给 a_variable 没有问题。(请与OO编程基础之一做个比较,即:a_value 的类型是 a_variable 的子类型。)一致关系由以下三条规则定义:

    • 如果 t1t2 的子类型,则 t1 类型与 t2 一致。(但反向则不见得。)
    • Any 与任意类型一致。(但 Any 不是任意类型的子类型。)
    • 任意类型均与 Any 一致。(但任意类型均非 Any 的子类型)

    就这些了!更多解释和成因,请参阅 Jeremy Siek 的博客文章 What is Gradual TypingAny 可被视为包含所有值和所有方法的类型。结合上述的子类型定义,一定程度上 Any 将被置于类型层次结构的最高处(包含所有值)和最底部(包含所有方法)。相较而言,object 与大多数类型都不一致(比如不能在要用 int 的地方把 object() 实例用上)。换句话说,在用作参数注解时 Anyobject 均表示“允许任何类型”,但无论应该是什么类型都只能传 Any(本质上,Any 声明要回退到动态类型确定并禁止静态检查程序的告警)。。

    以下示例演示了上述规则的实际作用:

    假定有 Employee 类及其子类 Manager

      class Employee: ...
      class Manager(Employee): ...
    

    假定变量 worker 声明为 Employee 类型:

      worker = Employee()  # type: Employee
    

    此后将 Manager 实例赋给 worker 是可以的(规则1):

      worker = Manager()
    

    Employee 赋给声明为 Manager 的变量则是不行的:

      boss = Manager()  # type: Manager
      boss = Employee()  # Fails static check
    

    但假设变量类型为 Any

      something = some_func()  # type: Any
    

    则将 something 赋给 worker 是没问题的(规则2):

      worker = something  # OK
    

    当然将 worker 赋给 something 也没问题(规则3),但这不需要用到一致性的概念。

      something = worker  # OK
    

    类型与类的比较 (Types vs. Classes)


    Python 中的类是由 class 语句定义的对象工厂,由内置函数 type(obj) 返回。类是动态的运行时概念。

    类型概念已在上面描述过了,类型出现于变量和函数的类型注解当中,可由后续介绍的构件基块(building block)构造出来,并供静态类型检查程序使用。

    如上所述,每个类都是一种类型。但一个类要能精确表达某个类型的语义,实现起来相当困难且很容易出错,这不是 PEP 484 的目标。PEP 484 中介绍的静态类型不应与运行时的类相互混淆。例如 :

    • int 是类也是类型。
    • UserID 是类也是类型。
    • Union[str, int] 是类型但不是一个合适的类:
        class MyUnion(Union[str, int]): ...  # raises TypeError
    
        Union[str, int]()  # raises TypeError
    

    类型确定接口是用类实现的,即在运行时能够对 Generic[T].__bases__ 这种代码进行计算。但为了强调类和类型之间的区别,请遵循以下通用规则:

    • 下面定义的AnyUnion等类型均不能被实例化,否则会触发 TypeError。但非抽象的 Generic 子类可被实例化。
    • 下面定义的类型均不能被子类化(subclassed),Generic 及其派生类除外。
    • 如果对他们作 isinstanceissubclass 调用,则会触发 TypeError(未参数化的泛型除外)。

    基本的构件基块(Fundamental building blocks)


    • Any。任何类型均与 Any 一致,Any 也与任何类型一致(参见上文)。

    • Union[t1, t2, ...]。只要是 t1 等类型之一的子类型,就是本类型的子类型。

      • Union 的成员都是 t1 等的子类型,则该 Union 是本类型的子类型。比如 Union[int, str] 就是 Union[int, float, str] 的子类型。
      • 参数的顺序无关紧要。例如 Union[int, str] == Union[str, int]
      • 如果 ti 本身是 Union,则结果是将成员展开。例如:Union[int, Union[float, str]] == Union[int, float, str]
      • 如果 titj 具有子型关系,则保留更泛化的类型。例如:Union[Employee, Manager] == Union[Employee]
      • Union[t1] 只会返回 t1Union[]Union[()] 都是合法的。
      • 理所当然,Union[..., object, ...] 将返回 object
    • Optional[t1]Union[t1, None] 的别名,也即 Union[t1, type(None)]

    • Tuple[t1, t2, ..., tn]。由 t1 等类型的实例构成的元组。比如 Tuple[int, float] 表示包含两个成员的元组,第一个成员是 int 类型,第二个则是 float 类型,例如 (42, 3.14)

      • 如果长度相同 n==m,且每个 ui 都是 ti 的子类型,则 Tuple[u1, u2, ..., um]Tuple[t1, t2, ..., tn] 的子类型。
      • 要表达空元组的类型,请使用 Tuple[()]
      • 可变长同类型元组可写为 Tuple[t1, ...]。(这里是三个句点即一个省略号,在 Python 语法中是合法的标记。)
    • Callable[[t1, t2, ..., tn], tr]。带有 t1 等位置参数的函数,返回类型为 tr。参数列表可以为空 n==0。无法声明可选或关键字参数,也不能声明可变长参数,但可以声明完全不对参数列表作检查 Callable[..., tr](同样用一个省略号)。

    以下类型或许会加入:

    • Intersection[t1, t2, ...]。需为 t1 等每个类型的子类型,才是本类型的子类型。(请与 Union 作对比,Union 的定义是 至少有一个 而不是 每个 类型。)

      • 参数的顺序无关紧要。嵌套的 Intersection 将被展开,例如 Intersection[int, Intersection[float, str]] == Intersection[int, float, str]
      • 类型较少的 Intersection 是类型较多的超类型。比如 Intersection[int, str]Intersection[int, float, str] 的超类型。
      • 只有一个参数的 Intersection 就是参数本身的类型。比如 Intersection[int] 就是 int
      • 如果参数包含子型关系,则保留更具体的类型,比如 Intersection[str, Employee, Manager] 就是 Intersection[str, Manager]
      • Intersection[]Intersection[()] 都是合法的。
      • 理所当然,参数列表中的 Any 将会消失,例如 Intersection[int, str, Any] == Intersection[int, str]Intersection[Any, object] 就是 object
      • IntersectionUnion 的关系是比较复杂,但如果理解了普通集合的交集和并集,就没什么可惊讶的了(注意类型的集合大小可以是无限的,因为新建子类的数量没有限制。)

    泛型(Generic types)


    以上定义的构件基块可以用通用类型的方式构件出新的类型。比如 Tuple 的参数实际(concrete)可为 float 类型,生成实际的类型 Vector = Tuple[float, ...],或者可用 UserID 类型作参数,生成实际的类型 Registry = Tuple[UserID, ...]。这种语义被称作通用类型构造函数,它类似于函数的语义,但是函数的参数为值且返回值,而通用类型构造函数的参数为类型并“返回”类型。

    若是某个类或函数以这种通用类型的方式运作,就是很常见的情况。不妨考虑两个例子:

    • 容器类,比如 listdict,典型情况只包含一种类型的数据。此时用户可能想如下进行声明:
        users = [] # type: List[UserID]
        users.append(UserID(42)) # OK
        users.append('Some guy') # Should be rejected by the type checker
    
        examples = {} # type: Dict[str, Any]
        examples['first example'] = object() # OK
        examples[2] = None                   # rejected by the type checker
    
    • 以下函数可能带两个 int 参数并返回一个 int 值,也可能带两个 float 参数并返回一个 float 值,如此等等:
        def add(x, y):
            return x + y
    
        add(1, 2) == 3
        add('1', '2') == '12'
        add(2.7, 3.5) == 6.2
    

    为了能在第一个示例的情况下进行类型注解,内置容器和容器抽象基类用类型参数进行了扩展,以便能充当通用类型构造函数。充当通用型构造函数的类称为泛型。例如:

      from typing import Iterable
    
      class Task:
          ...
    
      def work(todo_list: Iterable[Task]) -> None:
          ...
    

    此处的 Iterable 就是一个泛型,实际参数类型为 Task,返回的实际类型是 Iterable[Task]

    以泛型方式工作的函数(如第二个例子所示)被称为泛型函数。泛型函数的类型注解由类型变量来实现。类型注解中有关泛型的语义和函数参数的语义有些类似。不用为类型变量赋予实际的类型,静态类型检查程序将负责找到可能的类型值,并在无法找到时向用户发出警告。例如:

      def take_first(seq: Sequence[T]) -> T: # a generic function
          return seq[0]
    
      accumulator = 0 # type: int
    
      accumulator += take_first([1, 2, 3])   # Safe, T deduced to be int
      accumulator += take_first((2.7, 3.5))  # Unsafe
    

    类型注解中广泛应用了类型变量,类型检查程序中的内部类型推断机制通常也基于类型变量完成。因此,下面对类型变量进行一下详细讨论。

    类型变量(Type variables)


    X = TypeVar('X') 定义了一种类型变量。类型名称必须与变量名称一致。默认情况下,类型变量涵盖了所有可能的类型。例如:

      def do_nothing(one_arg: T, other_arg: T) -> None:
          pass
    
      do_nothing(1, 2)               # OK, T is int
      do_nothing('abc', UserID(42))  # also OK, T is object
    

    Y = TypeVar('Y', t1, t2, ...) 则仅限 t1 等类型。用法与 Union[t1, t2, ...] 类似。受限类型变量仅涵盖 t1 等类型本身,各受限类型的子类将会在 t1 等类型中选出最近的父类进行替换。例如:

    • 带有受限类型变量的函数类型注解:
        S = TypeVar('S', str, bytes)
    
        def longest(first: S, second: S) -> S:
            return first if len(first) >= len(second) else second
    
        result = longest('a', 'abc')  # The inferred type for result is str
    
        result = longest('a', b'abc')  # Fails static type check
    

    上述例子中,longest() 两个参数的类型必须相同(strstr),而且即便参数是 str 的子类,其返回类型依然会是 str 而非子类(参见以下例子)。

    • 以下例子作为对照,如果类型变量是非受限的,将会把子类作为返回类型,比如:
        S = TypeVar('S')
    
        def longest(first: S, second: S) -> S:
            return first if len(first) >= len(second) else second
    
        class MyStr(str): ...
    
        result = longest(MyStr('a'), MyStr('abc'))
    

    result 的类型推断结果为 MyStr(这里 AnyStrstr)。

    • 以下仍然作为对照,如果用了 Union,则返回类型必须为 Union
        U = Union[str, bytes]
    
        def longest(first: U, second: U) -> U:
            return first if len(first) >= len(second) else second
    
        result = longest('a', 'abc')
    

    即使两个参数均为 str,上述 result 的类型推断结果仍为 Union[str, bytes]

    Note that the type checker will reject this function::

    def concat(first: U, second: U) -> U:
        return x + y  # Error: can't concatenate str and bytes
    

    对于这种参数类型只能一起更改的情况,应该使用受限类型变量。

    泛型的定义和用法(Defining and using generic types)


    利用特殊的构件基块 Generic,用户可以将类声明为泛型。class MyGeneric(Generic[X, Y, ...]): ... 通过 X 等类型变量定义了泛型 MyGeneric。于是 MyGeneric 自身就具备了参数化的能力,比如通过代入 X -> intMyGeneric[int, str, ...] 就成为了一种具体的类型。例如:

      class CustomQueue(Generic[T]):
    
          def put(self, task: T) -> None:
              ...
          def get(self) -> T:
              ...
    
      def communicate(queue: CustomQueue[str]) -> Optional[str]:
          ...
    

    由泛型派生的类就成了泛型(类型)。类可以是多个泛型的子类。但是泛型返回的某具体类型的派生类则不是泛型。例如:

      class TodoList(Iterable[T], Container[T]):
          def check(self, item: T) -> None:
              ...
    
      def check_all(todo: TodoList[T]) -> None:  # TodoList is generic
          ...
    
      class URLList(Iterable[bytes]):
          def scrape_all(self) -> None:
              ...
    
      def search(urls: URLList) -> Optional[bytes]  # URLList is not generic
          ...
    

    泛型的子类化将会强制为对应的类型加上子型关系,因此在上述例子中 TodoList[t1]Iterable[t1] 的子类。

    泛型的具象化(specialized)/索引化(indexed)过程可以分为几个步骤。类型变量可代入具体类型
    或其他泛型。如果 Generic 出现在基类列表中,则其应包含所有类型变量,且类型参数的顺序由 Generic 中的出现顺序确定。例如:

      Table = Dict[int, T]     # Table is generic
      Messages = Table[bytes]  # Same as Dict[int, bytes]
    
      class BaseGeneric(Generic[T, S]):
          ...
    
      class DerivedGeneric(BaseGeneric[int, T]): # DerivedGeneric has one parameter
          ...
    
      SpecificType = DerivedGeneric[int]         # OK
    
      class MyDictView(Generic[S, T, U], Iterable[Tuple[U, T]]):
          ...
    
      Example = MyDictView[list, int, str]       # S -> list, T -> int, U -> str
    

    如果类型注解中的泛型省略了类型变量,则会被假定为Any。这种形式可用作动态定型的回调值,可用于 issubclassisinstance。在运行时,实例中的类型信息均会被清除。例如:

      def count(seq: Sequence) -> int:      # Same as Sequence[Any]
          ...
    
      class FrameworkBase(Generic[S, T]):
          ...
    
      class UserClass:
          ...
    
      issubclass(UserClass, FrameworkBase)  # This is OK
    
      class Node(Generic[T]):
         ...
    
      IntNode = Node[int]
      my_node = IntNode()  # at runtime my_node.__class__ is Node
                           # inferred static type of my_node is Node[int]
    

    协变和逆变(Covariance and Contravariance)


    假设 t2t1 的子类,则会调用泛型构造函数 GenType

    • 如果对所有 t1t2GenType[t2] 都是 GenType[t1] 的子类型,则为协变(Covariant)。
    • 如果对所有 t1t2GenType[t1] 都是 GenType[t2] 的子类型,则为逆变(Contravariant)。
    • 如果上述两条均不成立,则为不变(invariant)。

    为了更好地理解以上定义,不妨用普通函数作个比方。假定有以下函数:

      def cov(x: float) -> float:
          return 2*x
    
      def contra(x: float) -> float:
          return -x
    
      def inv(x: float) -> float:
          return x*x
    

    如果 x1 < x2,则一定cov(x1) < cov(x2)contra(x2) < contra(x1),但 inv 却不见得。将 < 替换为“是子类型”(is-subtype-of),将函数替换为泛型构造函数,就得到了协变、逆变、不变性的例子。下面看几个实例:

    • Union 对所有参数表现为协变性。正如上所述,如果 t1 等是 u1 等类型的子类型,则 Union[t1, t2, ...] 就是 Union[u1, u2, ...] 的子类型。

    • FrozenSet[T] 也是协变的。不妨将 T 替换为 intfloat。首先 intfloat 的子类型。其次 FrozenSet[int] 的值集明显就是 FrozenSet[float] 值集的子集,而 FrozenSet[float] 的函数(方法)集则为 FrozenSet[int] 方法集的子集。因此,FrozenSet[int] 的定义就是 FrozenSet[float] 的子类型。

    • List[T] 是不变的。其实正如背景知识一节所述,虽然 List[int] 的值集是 List[float] 值集的子集,但只有 int 能够加入到 List[int] 中去。因此, List[int] 不是 List[float] 的子类型。此为可变(mutable)类型的典型情况,通常他们是具备不变性(invariant)的。

    Callable 类型是演示逆变性(有点违反直觉)的最佳示例之一。它的返回类型是协变的,但参数类型则是逆变的。对于只是返回类型不同的两种 Callable 类型,其子型关系依据返回类型而定。例如:

    • Callable[[], int]Callable[[], float] 的子类型。
    • Callable[[], Manager]Callable[[], Employee] 的子类型。

    而对于两种只是某个参数不同的 Callable 类型,则其子型关系与参数类型的子型关系方向相反。例如:

    • Callable[[float], None]Callable[[int], None] 的子类型。
    • Callable[[Employee], None]Callable[[Manager], None] 的子类型。

    是的,确实如此。实际上,如果计算经理工资的函数如下定义:

      def calculate_all(lst: List[Manager], salary: Callable[[Manager], Decimal]):
          ...
    

    那么 Callable[[Employee], Decimal] 作为计算任何雇员工资的函数也是可以接受的。

    上述 Callable 的例子说明了该如何让函数的类型注解更加精确:为参数选择最泛化的类型,而为返回值选择最具象化的类型。

    在定义用于参数的类型变量时,可以用特殊的关键字 covariantcontravariant 为用户自定义泛型声明类型的协变逆变特性。默认情况下类型是具有不变性的。例如:

      T = TypeVar('T')
      T_co = TypeVar('T_co', covariant=True)
      T_contra = TypeVar('T_contra', contravariant=True)
    
      class LinkedList(Generic[T]):  # invariant by default
          ...
          def append(self, element: T) -> None:
              ...
    
      class Box(Generic[T_co]):      #  this type is declared covariant
          def __init__(self, content: T_co) -> None:
              self._content = content
          def get_content(self) -> T_co:
              return self._content
    
      class Sink(Generic[T_contra]): # this type is declared contravariant
          def send_to_nowhere(self, data: T_contra) -> None:
              with open(os.devnull, 'w') as devnull:
                  print(data, file=devnull)
    

    注意,虽然协变逆变性是通过类型变量定义的,但它不是类型变量的属性,而是泛型的属性。在较为复杂的派生泛型定义中,协变逆变性决定于所用的类型变量。以下是个比较复杂的示例:

      T_co = TypeVar('T_co', Employee, Manager, covariant=True)
      T_contra = TypeVar('T_contra', Employee, Manager, contravariant=True)
    
      class Base(Generic[T_contra]):
          ...
    
      class Derived(Base[T_co]):
          ...
    

    类型检查程序由第二句定义得知,Derived[Manager]Derived[Employee] 的子类型,Derived[t1]Base[t1] 的子类型。如果用 < 表示“属于子类型”的关系,那么上述示例的完整子型关系将如下所示:

      Base[Manager]    >  Base[Employee]
          v                   v
      Derived[Manager] <  Derived[Employee]
    

    因此类型检查程序也将发现这些关系,比如 Derived[Manager] 就是 Base[Employee] 的子类型。

    关于类型变量、泛型、协变逆变性的更多信息,请参阅 PEP 484mypy 有关泛型的文档Wikipedia

    语用学定义(Pragmatics)


    有些东西与原理无关,但能让实际运用起来更为便捷。(这里不算是完整列表,可能会有遗漏,有些尚存争议或并未完全形成规范。)

    • 在需要填入类型的地方,None 可以代替 type(None)。例如: Union[t1, None] == Union[t1, type(None)]

    • 类型别名,例如:

        Point = Tuple[float, float]
        def distance(point: Point) -> float: ...
    
    • 向前引用由字符串实现,例如:
        class MyComparable:
            def compare(self, other: 'MyComparable') -> int: ...
    
    • 如果指定了默认值为 None,则类型隐式为 Optional,例如:
        def get(key: KT, default: VT = None) -> VT: ...
    
    • 类型变量可以以非限定、限定或有界(bound)的形式进行声明。泛型的协变逆变性也可以用带有特殊关键字的类型变量进行标识,从而避免采用什么特殊的语法,例如:
        T = TypeVar('T', bound=complex)
    
        def add(x: T, y: T) -> T:
            return x + y
    
        T_co = TypeVar('T_co', covariant=True)
    
        class ImmutableList(Generic[T_co]): ...
    
    • 注释中可包含类型声明,例如:
        lst = []  # type: Sequence[int]
    
    • 可用 cast(T, obj) 指定类型,例如:
        zork = cast(Any, frobozz())
    
    • 其他详见 PEP 484,例如重载和 stub 模块。

    typing.py 中预定义的泛型和协议(Predefined generic types and Protocols in typing.py)


    (请参阅 typing.py 模块

    • 来自 collections.abc 的所有内容(但 Set 改名为 AbstractSet)。
    • DictListSetFrozenSet 等。
    • re.Pattern[AnyStr]re.Match[AnyStr]
    • io.IO[AnyStr]io.TextIO ~ io.IO[str]io.BinaryIO ~ io.IO[bytes]
  • 相关阅读:
    Shell判断文件或目录是否存在
    linux使用wpa_supplicant手动配置wifi
    ubuntu更换apt源后依然搜索旧软件源下载失败问题
    在Ubuntu下解决E: 无法对目录 /var/lib/apt/lists/ 加锁的问题
    Bash中判断一个命令的输出结果是否为空
    [解决]/bin/bash^M: bad interpreter: No such file or directory
    【转】 使用 NetworkManager 命令行工具 nmcli
    【转】wpa_supplicant及wpa_cli使用方法
    [RK3288] Vendor Storage区域知识及探讨
    安装oracle 时“[INS-30014]无法检查指定的位置是否位于 CFS上”问题
  • 原文地址:https://www.cnblogs.com/popapa/p/PEP483.html
Copyright © 2020-2023  润新知