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[()]
- 可變長同類型元組可寫為
。(這裡是三個句點即一個省略号,在 Python 文法中是合法的标記。)Tuple[t1, ...]
- 如果長度相同
- 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]
的子類型,則為協變(Covariant)。GenType[t1]
- 如果對所有
和t1
,t2
都是GenType[t1]
的子類型,則為逆變(Contravariant)。GenType[t2]
- 如果上述兩條均不成立,則為不變(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]
的子類型。此為可變(mutable)類型的典型情況,通常他們是具備不變性(invariant)的。List[float]
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/