天天看點

PEP 483 類型提示的理論 -- Python官方文檔譯文 [原創]

Python 官方文檔 PEP 483(類型提示理論)的譯文,本人原創。

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 [email protected]、Ivan Levkivskyi [email protected]

Discussions-To: Python-Ideas [email protected]

Status: Final

Type: Informational

Created: 19-Dec-2014

Post-History:

Resolution:

目錄

  • 摘要(Abstract)
  • 簡介(Introduction)
    • 符号約定(Notational conventions)
  • 背景知識(Background)
    • 子型關系(Subtype relationships)
  • 漸進定型概述(Summary of gradual typing)
    • 類型與類的比較 (Types vs. Classes)
    • 基本的構件基塊(Fundamental building blocks))
  • 泛型(Generic types)
    • 類型變量(Type variables)
    • 泛型的定義和用法(Defining and using generic types)
    • 協變和逆變(Covariance and Contravariance)
  • 語用學定義(Pragmatics)
    • typing.py 中預定義的泛型和協定(Predefined generic types and Protocols in typing.py)
  • 版權(Copyright)
  • 參考文獻和腳注(References and Footnotes)

## 摘要(Abstract)

本 PEP 列出了 PEP 484 的理論知識。

## 簡介(Introduction)

本文檔列出了 Python 3.5 新引入的類型提示提案的理論知識。因為尚有很多細節需要制定,是以這還算不上是一個完整的提案或規範,但沒有這裡羅列的理論,就很難對更詳細的規範進行讨論。本文首先将回顧類型理論的基本概念,再解釋漸進定型(gradual typing),然後再聲明一些通用規則并定義一些可用于注釋的的新增特殊類型(如

Union

),最後定義了泛型的實作方案并給出類型提示的語用學定義。

### 符号約定(Notational conventions)

  • t1

    t2

    u1

    u2

    等等均表示某種類型。有時會用

    ti

    tj

    表示“

    t1

    t2

    等中的任一類型”。
  • T

    ,

    U

    之類表示類型變量(由

    TypeVar()

    定義,參見下文)。
  • 對象、類由 class 語句定義,執行個體用标準的 PEP 8 約定進行表示。
  • 本文中類型上的

    ==

    符号意味着兩個表達式代表着同一個類型。
  • 注意 PEP 484 對類型和類做了區分(類型是對類型檢查程式而言的概念,而類是運作時概念)。本文明晰了這種差別,但避免做不必要的嚴格區分,以便能更加靈活地實作類型檢查程式。

## 背景知識(Background)

類型的概念在文字上有很多定義。這裡假定“類型”是一組值和一組可應用于這些值的函數。

定義類型的方式有很多:

  • 顯式列出所有值。比如

    True

    False

    形成了

    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           
  • 還有一些比較複雜的類型。比如可以将僅包含

    int

    str

    及其子類資料的所有清單定義為

    FancyList

    類型,

    [1, 'abc', UserID(42)]

    即為這種類型。

對于使用者而言,重要的是能以類型檢查程式可了解的形式定義類型。本文目标是要為變量和函數的類型注解(type annotation)提出一種采用 PEP 3107 文法進行類型定義的系統性方案。作為文檔,這些注解可用于避免多種 bug,甚至可能會提高程式的執行速度。本文隻重點關注用靜态類型檢查程式來避免出現 bug。

### 子型關系(Subtype relationships)

靜态類型檢查程式的關鍵概念是子型關系。它由以下問題引發:如果

first_var

first_type

類型,

second_var

second_type

類型,那麼

first_var = second_var

是否安全呢?

何時應該安全的有力準則是:

  • second_type

    類型的每個值同時也位于

    first_type

    類型的值集中;并且
  • first_type

    類型的每個函數也都位于

    second_type

    類型的函數集中。

上述關系被稱為子型關系。

根據以上定義:

  • 每個類型均為自身的子類型。
  • 在子類化過程中,值的集合會越變越小,而函數的集合會越變越大。

舉個直覺的例子:每個

Dog

都是

Animal

類型,

Dog

的函數也比

Animal

多,比如會叫,是以

Dog

Animal

的子類型。相反

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)時,子型關系是由已聲明的方法推導出來的,即

UserID

int

将被視作相同類型。盡管有時候這可能會引起混亂,但結構子類型被認為更靈活。這裡盡量對這兩種方案都提供支援,這樣在名義子類型之外還可讓結構資訊也派上用場。

## 漸進定型概述(Summary of gradual typing)

漸進定型允許隻給一部分程式加上注解,是以可以充分利用動态和靜态類型确定的各自優勢。

這裡定義了一個新的關系:一緻(is-consistent-with),它和子類(is-subtype-of)類似,但在遇到新類型

Any

時是不可傳遞的。(這兩種關系都是不對稱的。)如果

a_value

的類型與

a_variable

的類型一緻,則将

a_value

指派給

a_variable

沒有問題。(請與OO程式設計基礎之一做個比較,即:

a_value

的類型是

a_variable

的子類型。)一緻關系由以下三條規則定義:

  • 如果

    t1

    t2

    的子類型,則

    t1

    類型與

    t2

    一緻。(但反向則不見得。)
  • Any

    與任意類型一緻。(但

    Any

    不是任意類型的子類型。)
  • 任意類型均與

    Any

    一緻。(但任意類型均非

    Any

    的子類型)

就這些了!更多解釋和成因,請參閱 Jeremy Siek 的部落格文章

What is Gradual Typing

Any

可被視為包含所有值和所有方法的類型。結合上述的子類型定義,一定程度上

Any

将被置于類型層次結構的最高處(包含所有值)和最底部(包含所有方法)。相較而言,

object

與大多數類型都不一緻(比如不能在要用

int

的地方把

object()

執行個體用上)。換句話說,在用作參數注解時

Any

object

均表示“允許任何類型”,但無論應該是什麼類型都隻能傳

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__

這種代碼進行計算。但為了強調類和類型之間的差別,請遵循以下通用規則:

  • 下面定義的

    Any

    Union

    等類型均不能被執行個體化,否則會觸發

    TypeError

    。但非抽象的

    Generic

    子類可被執行個體化。
  • 下面定義的類型均不能被子類化(subclassed),

    Generic

    及其派生類除外。
  • 如果對他們作

    isinstance

    issubclass

    調用,則會觸發

    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]

    • 如果

      ti

      tj

      具有子型關系,則保留更泛化的類型。例如:

      Union[Employee, Manager] == Union[Employee]

    • Union[t1]

      隻會傳回

      t1

      Union[]

      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

    • Intersection

      Union

      的關系是比較複雜,但如果了解了普通集合的交集和并集,就沒什麼可驚訝的了(注意類型的集合大小可以是無限的,因為建立子類的數量沒有限制。)

## 泛型(Generic types)

以上定義的構件基塊可以用通用類型的方式構件出新的類型。比如

Tuple

的參數實際(concrete)可為

float

類型,生成實際的類型

Vector = Tuple[float, ...]

,或者可用

UserID

類型作參數,生成實際的類型

Registry = Tuple[UserID, ...]

。這種語義被稱作通用類型構造函數,它類似于函數的語義,但是函數的參數為值且傳回值,而通用類型構造函數的參數為類型并“傳回”類型。

若是某個類或函數以這種通用類型的方式運作,就是很常見的情況。不妨考慮兩個例子:

  • 容器類,比如

    list

    dict

    ,典型情況隻包含一種類型的資料。此時使用者可能想如下進行聲明:
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()

兩個參數的類型必須相同(

str

str

),而且即便參數是

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

(這裡

AnyStr

str

)。

  • 以下仍然作為對照,如果用了

    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 -> int

MyGeneric[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

。這種形式可用作動态定型的回調值,可用于

issubclass

isinstance

。在運作時,執行個體中的類型資訊均會被清除。例如:

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)

假設

t2

t1

的子類,則會調用泛型構造函數

GenType

  • 如果對所有

    t1

    t2

    GenType[t2]

    都是

    GenType[t1]

    的子類型,則為協變(Covariant)。
  • 如果對所有

    t1

    t2

    GenType[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

    替換為

    int

    float

    。首先

    int

    float

    的子類型。其次

    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

的例子說明了該如何讓函數的類型注解更加精确:為參數選擇最泛化的類型,而為傳回值選擇最具象化的類型。

在定義用于參數的類型變量時,可以用特殊的關鍵字

covariant

contravariant

為使用者自定義泛型聲明類型的協變逆變特性。預設情況下類型是具有不變性的。例如:

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 484、mypy 有關泛型的文檔和 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

    )。
  • Dict

    List

    Set

    FrozenSet

    等。
  • re.Pattern[AnyStr]

    re.Match[AnyStr]

  • io.IO[AnyStr]

    io.TextIO ~ io.IO[str]

    io.BinaryIO ~ io.IO[bytes]

## 版權(Copyright)

本文遵守 Open Publication License。

## 參考文獻和腳注(References and Footnotes)

Open Publication License: http://www.opencontent.org/openpub/