[译]PEP 484 -- 类型提示
PEP 原文:https://www.python.org/dev/peps/pep-0484/
创建日期:2014-09-29
合入版本:3.5
译注:PEP 484 快速翻译。省略了很多的内容,只是为了快速入门。
# Abstract
此方案还是 provisional 的,此方案并没有阻止其他的 annotation,或者强制或者禁止使用一种特定的 annotation 的过程。
def greeting(name: str) -> str:
return 'Hello ' + name
2
3
会在运行时添加一个__annotations__
的属性,但是不会再运行时进行类型检查。本方案假设有额外的单独的 off-line 的 type checker 工具进行类型检查,当然这个检查的过程也是用户自己选择的。
本提案更多的收到 mypy 这个工具的灵感。比如说"sequence of integers"会被写作Sequence[int]
。这个Swquence
是typing
模块原生提供。
type system 支持 unions,generic 类型, Any
类型兼容所有的类型。his latter feature is taken from the idea of gradual typing. Gradual typing and the full type system are explained in PEP 483 (opens new window).
# The meaning of annotations
所有没有被 annotation 的函数都应该被认为是拥有最 general 的类型,应该被 type checker 进行忽略。如果一个函数被@no_type_check
装饰器装饰,都应该被认为不带任何的 annotations
类的实例函数或者类函数的第一个参数是此类型,这很好理解。并且__init__
函数的函数值如果有 type hint 的话,应该是->None
这主要是为了明确这是有 type hint 的,否则的话def __int__(self)
到底是有类型 hint 还是没有?
# Type Definition Syntax
简单来说
def greeting(name: str) -> str:
return 'Hello ' + name
2
3
# Acceptable type hints
可接受的 type hint 包括内置的类(包括标准库中的类,或者第三方模块中的类),abstract base classes, 或者types
模块中的类型,或者用户自定义的类型(包括标准库中定义的类型,或者第三方模块中的类型)
annotation 仅仅是一些特殊的注释,或者一个 stub file。除此之外并无特殊之处
annotation 应该足够的简单,否则的话静态分析工具并不能很好的解析类型。比如说动态计算的类型是不会被允许的。
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 fromtyping
(e.g.Sequence
andDict
), type variables, and type aliases.
# Using None
在使用 type hint 时,None
相当于type(None)
# Type aliases
简单来说,就是这样:
Url = str
def retry(url: Url, retry_count: int) -> None: ...
2
3
4
Url
尽量首字母大写,这样体现了这是用户自定义的类型。
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]
2
3
4
5
6
7
8
9
10
11
在 type hint 中可以接受的东西,在 type alias 中都是可以接受的,比如说,上面按个等价于下面这个
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]]
2
3
4
5
6
7
8
9
10
# Callable
Callable[[Arg1Type, Arg2Type], ReturnType]
前面的是接受参数的类型,后面那个是返回的类型。
当然,也可以Callable[..., str]
的省略号,表示不在乎接受的参数的类型,这里的省略号没有方括号,这点需要额外注意。
typing.callable
将重复的进行 collections.abc.Callable 的工作,isinstance(x, typing.Callable)
会被解析成isinstance(x, collections.abc.Callable)
,但是isinstance(x, typing.Callable[...])
不会被支持。
# 泛型 Generics
对于容器类来说,容器类中的对象类型是不能静态引用。所以typing
中的 abstract base class 或许比较有用
from typing import Mapping, Set
def notify_by_email(employees: Set[Employee], overrides: Mapping[str, str]) -> None: ...
2
3
4
对于泛型类,可以使用typing
包中的TypeVar
来创建
from typing import Sequence, TypeVar
T = TypeVar('T') # Declare type variable
def first(l: Sequence[T]) -> T: # Generic function
return l[0]
2
3
4
5
6
7
这个函数必须被直接赋值给一个变量,而不能作为一个大的表达式的中间结果。此外,这个函数的参数必须是个字符串,且应该是被赋值给的那个变量的 name。并且,同一个 type variable 不能被重定义。
除此之外,也可以创建一个只包含指定类型的泛型,在下面这个例子中,AnyStr
只能是str
或者bytes
from typing import TypeVar
AnyStr = TypeVar('AnyStr', str, bytes)
def concat(x: AnyStr, y: AnyStr) -> AnyStr:
return x + y
2
3
4
5
6
7
对于下面这个例子中,两个参数不会被认为是MyStrl
类型,而是被认为是str
类型,并且返回值 x 的类型也是 str 而不是 MyStr
class MyStr(str): ...
x = concat(MyStr('apple'), MyStr('pie'))
2
3
4
Any
被认为是任意类型,比如下面例子中的泛型被认为是elements: List
def count_truthy(elements: List[Any]) -> int:
return sum(1 for elem in elements if elem)
2
3
# User-defined generic types
引入Generic
基类以创建泛型。
from typing import TypeVar, Generic
from logging import Logger
T = TypeVar('T')
class LoggedVar(Generic[T]):
def __init__(self, value: T, name: str, logger: Logger) -> None:
self.name = name
self.logger = logger
self.value = value
def set(self, new: T) -> None:
self.log('Set ' + repr(self.value))
self.value = new
def get(self) -> T:
self.log('Get ' + repr(self.value))
return self.value
def log(self, message: str) -> None:
self.logger.info('{}: {}'.format(self.name, message))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
有意思的是,这个[]
真的是会实现__getitem__
方法,以返回基于 T 这个类型的 LoggedVar 类,比如下面是合法的
from typing import Iterable
def zero_all_vars(vars: Iterable[LoggedVar[int]]) -> None:
for var in vars:
var.set(0)
2
3
4
5
6
自然,泛型类中的类型可以不止一个
from typing import TypeVar, Generic
...
T = TypeVar('T')
S = TypeVar('S')
class Pair(Generic[T, S]):
...
2
3
4
5
6
7
8
9
但是这个是错误的:
from typing import TypeVar, Generic
...
T = TypeVar('T')
class Pair(Generic[T, T]): # INVALID
2
3
4
5
6
7
对于声明成一个泛型类(?是这么表述嘛),不一定一定要写上Generic[T]
,可以省略,比如:
from typing import TypeVar, Iterator
T = TypeVar('T')
class MyIter(Iterator[T]): # 等价于 class MyIter(Iterator[T], Generic[T]):
...
2
3
4
5
6
7
# Scoping rules for type variables
十分符合直觉,不看也罢,大概就是T
是有范围的,对于一个类的实例,一点T
确定了下来,就不能变了。对于一个普通的函数,就没有这个要求。
可能需要注意的是,嵌套类的定义
T = TypeVar('T')
S = TypeVar('S')
class Outer(Generic[T]):
class Bad(Iterable[T]): # Error
...
class AlsoBad:
x = None # type: List[T] # Also an error
class Inner(Iterable[S]): # OK
...
attr = None # type: Inner[T] # Also OK
2
3
4
5
6
7
8
9
10
11
12
13
# instantiating generic classes and type erasure, 展示泛型类以及类型擦除
类型可以推断出来,但是推断不出来就出错(废话),但是在运行时,不同具体泛型类的对象是同一个类型的,也就是说类型擦除了,运行时不记录泛型类型。就像 typescript 或者 java 那样。
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:
对于虚 collections 比如Mapping
或者Sequence
以及内置类型的泛型类List, Dict, Set, FrozenSet
不能够被实例化,但是一个 concrete(实在的) 自定义子类或者 generic 版本的 concrete 容器类可以被实例化
data = DefaultDict[int, bytes]()
2
最好不要用Node[int]
这样的东西在表达式中,应该用 aliasIntNode=Node[Int]
,因为前者会带来运行时的消耗
# Arbitrary generic types as base classes
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
SymbolTable
is a subclass ofdict
and a subtype ofDict[str, List[Node]]
.
from typing import TypeVar, Iterable, Container
T = TypeVar('T')
class LinkedList(Iterable[T], Container[T]):
...
2
3
4
5
6
7
然后引用LinkedList[int]
合法
# Abstract generic types
# Type variables with an upper bound
指定这个类型是某个具体边缘类型的子类型
from typing import TypeVar
class Comparable(metaclass=ABCMeta):
@abstractmethod
def __lt__(self, other: Any) -> bool: ...
... # __gt__ etc. as well
CT = TypeVar('CT', bound=Comparable)
def min(x: CT, y: CT) -> CT:
if x < y:
return x
else:
return y
min(1, 2) # ok, return type int
min('x', 'y') # ok, return type str
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Covariance and contravariance
简单点来说就是如果Employee
是Manager
的父类,List[Employee]
是List[Manager]
的父类(convariance),或者说是子类(contravariance)吗?至少在目前的 PEP 中,默认都不是。当然可以设置
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
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
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# The numeric tower
标准库中有 numbers 这个模块有一系列的 ABCs
# Forward references
合法
class Tree:
def __init__(self, left: 'Tree', right: 'Tree'):
self.left = left
self.right = right
2
3
4
5
不合法
class Tree:
def __init__(self, left: Tree, right: Tree):
self.left = left
self.right = right
2
3
4
5
事实上,只要字符串是一个合法的 python 表达式就行。
# Union types
from typing import Union
def handle_employees(e: Union[Employee, Sequence[Employee]]) -> None:
if isinstance(e, Employee):
e = [e]
...
from typing import Optional
def handle_employee(e: Optional[Employee]) -> None: ...
# Optional[Employee]等价于Union[Employee, None]
2
3
4
5
6
7
8
9
10
11
事实上,对于以前的 PEP 来说,允许那些标注了类型,但是值是 None 的那些东西,认为是 Optional
def handle_employee(e: Employee = None): ...
# equal
def handle_employee(e: Optional[Employee] = None) -> None: ...
2
3
4
# Support for singleton types in unions
略
# The Any
type
它和object
完全不是一个东西。
# The NoReturn type
表识函数不返回任何值。连 None 都不能返回,表示,此函数必然触发异常或者 exit。如果有某个分支导致可能有返回值,就会出错,比如下面第二个例子
from typing import NoReturn
def stop() -> NoReturn:
raise RuntimeError('no way')
import sys
from typing import NoReturn
def f(x: int) -> NoReturn: # Error, f(0) implicitly returns None
if x != 0:
sys.exit(1)
2
3
4
5
6
7
8
9
10
11
对于下面这个,有可能不报错,因为最后那个分支一定不会被执行
# continue from first example
def g(x: int) -> int:
if x > 0:
return x
stop()
return 'whatever works' # Error might be not reported by some checkers
# that ignore errors in unreachable blocks
2
3
4
5
6
7
8
并且,这个只能用在函数标注上。
# The type of class objects
有些时候,一个函数可能需要传入一个类对象,而不是一个类的实例。这个时候应该用type[C]
这个表示,传入的参数可以是C
的子类, 而不是 C 的实例。
# Annotating instance and class methods
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() # type here should be D
2
3
4
5
6
7
8
9
# Version and platform checking
import sys
if sys.version_info[0] >= 3:
# Python 3 specific definitions
else:
# Python 2 specific definitions
if sys.platform == 'win32':
# Windows specific definitions
else:
# Posix specific definitions
2
3
4
5
6
7
8
9
10
11
12
对于简单的平台验证,静态工具还是可以以来的
# Runtime or type checking
略
# Arbitrary argument lists and default argument values
略
# Positional-only arguments
函数参数中,以__
开头的都是 positional-only 参数,除非它的结尾也有__
# Annotating generator functions and coroutines
对于生成器:Generator[yield_type, send_type, return_type]
def echo_round() -> Generator[int, float, str]:
res = yield
while res:
res = yield round(res)
return 'OK'
2
3
4
5
6
对于 await
async def spam(ignored: int) -> str:
return 'spam'
async def foo() -> None:
bar = await spam(42) # type: str
2
3
4
5
6
想了解更多,还是看原文
# Compatibility with other uses of function annotations
下面三种情况不会被类型检查
- a
# type: ignore
comment; - a
@no_type_check
decorator on a class or function; - a custom class or function decorator marked with
@no_type_check_decorator
.
# Type comments
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]
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
...
2
3
4
5
6
7
8
9
10
11
12
13
在标注文件中,可以这么做,尤其适合那些不想给初始值的地方。这些对所有的 python 版本都支持
from typing import IO
stream: IO[str]
2
3
4
在 python3.5,但是没有 stub 的地方可以这么做
from typing import IO
stream = None # type: IO[str]
2
3
4
# 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])
2
3
4
5
6
7
# NewType helper function
NewType("unique_name", base_class)
减少运行时的开销。
# Stub files
用在什么地方:
- Extension modules
- Third-party modules whose authors have not yet added type hints
- Standard library modules for which type hints have not yet been written
- Modules that must be compatible with Python 2 and 3
- Modules that use annotations for other purposes
# Function/method overloading
略
# 存储和分发 stub files
略
# The Typeshed Repo
略
# Exceptions
# The typing
Module
应该去原文读一读,工具类的东西
# Suggested syntax for python2.7 and straddling code
如果需要在 python2.7 上实现 type hint,可能需要去读一读
# Rejected Alternatives
略