Python typing模块是3.5引入的,Python解释器没有强制要求添加类型,类型注解只是为了给type checkers, IDE,linters等用的。
PyCharm和VS Code都支持类型检查,Eclipse需要配置mypy,运行mypy来检查。
类型别名
对复杂类型,可以起个别名,方便重复使用。
from typing import List, Callable Vector = List[int] CallBack = Callable[[int], str] def mymap(nums: Vector, action: CallBack): action(len(nums)) return [i*2 for i in nums]
NewType
Derived = NewType('Derived', Original)
静态检查器把Derived当成Original的子类,意味着Original类型的值不能用于Derived类型的值需要的地方。
运行时NewType(‘Derived’, Original)创建一个Derived函数,该函数立即返回传给它的任何参数;意味着不会创建一个新的类,没有引入很多开销。
下面的例子中 UserId 是 int 类型,print_id()函数传入int值,运行没有问题,不过编译器会报错。类型别名是可以互相替换的,感觉NewType是更严格的类型别名。
UserId = NewType('UserId', int) def print_id(id: UserId): print('The ID is', id) # Error: Expected type 'UserId', got 'int' instead print_id(1234) print_id(UserId(1234))
Callable
用于定义函数的类型 Callable[[Arg1Type, Arg2Type], ReturnType]
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
泛型(Generic)
使用TypeVar()生成一个泛型类型。
泛型函数
from typing import Sequence, TypeVar T = TypeVar('T') # Declare type variable def first(l: Sequence[T]) -> T: # Generic function return l[0] # Expected type 'list', got 'int' instead value: List = first([1, 2, 3])
泛型类
from typing import TypeVar, Generic T = TypeVar('T') class LoggedVar(Generic[T]): def __init__(self, value: T, name: str) -> None: self.name = name 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: print('%s: %s', self.name, message) var = LoggedVar(888, 'Record') # Expected type 'int' (matched generic type 'T'), got 'str' instead var.set('1234') var.get()
限制类型只能是int或str
from typing import TypeVar, Generic T = TypeVar('T', int, str) # Expected type 'T', got 'tuple[int, int]' instead val: T = (1, 2)
继承了Generic的类,还能同时继承其它类,支持多重继承。
from typing import TypeVar, Generic, Sized T = TypeVar('T') class LinkedList(Sized, Generic[T]): ...
继承多个参数的泛型类时,可以固定某些类型,例如固定Mapping的key是str。
from typing import TypeVar, Mapping T = TypeVar('T') class MyDict(Mapping[str, T]): ...
没有指定类型的泛型类会被假设为Any,下例的Iterable等同于Iterable[Any]。
from typing import Iterable class MyIterable(Iterable): # Same as Iterable[Any] ...
类型别名中如果有泛型类型,该类型别名也是泛型的,使用时要加上类型。
from typing import TypeVar, Iterable, Tuple, Union S = TypeVar('S') Response = Union[Iterable[S], int] # Return type here is same as Union[Iterable[str], int] def response(query: str) -> Response[str]: ... T = TypeVar('T', int, float, complex) Vec = Iterable[Tuple[T, T]] # Same as Iterable[Tuple[T, T]] def inproduct(v: Vec[T]) -> T: return sum(x*y for x, y in v)
Any 类型
Any表示任意类型,可以执行任何操作。只有不知道类型时才定义成Any,否则就失去类型检查的意义了。
from typing import Any a: Any = 1 # No type issue but fail at runtie a.adsfs(1)
静态类型和鸭子类型
静态类型
Python引入的typing模块属于静态类型,也叫名义类型(nominal type),如果B是A的子类,则B可以用在需要A的地方。下面的Bucket类是个迭代器:
from typing import Sized, Iterable, Iterator class Bucket(Sized, Iterable[int]): ... def __len__(self) -> int: ... def __iter__(self) -> Iterator[int]: ...
鸭子类型
鸭子类型,也叫结构化类型(structural type),只要类定义了某类型的方法,它就可以当该类型使用。例如类定义了__len__方法和__iter__方法,就是迭代器。下面的例子,如果注释掉__iter__方法,最后一行调用处,PyCharm会报错。
from typing import Iterator, Iterable class Bucket: # Note: no base classes ... def __len__(self) -> int: ... def __iter__(self) -> Iterator[int]: ... def collect(items: Iterable[int]) -> int: ... result = collect(Bucket()) # Passes type check
Pycharm类型检查很智能,即支持静态类型又支持鸭子类型。
自定义鸭子类型 (Protocol)
用于定义自己的鸭子类型,工具如PyCharm会自动检查。
A继承了Protocol类,某类只要具有和A相同的方法,就可以当成A类型。下面的例子C类的方法和Proto类一样,就可以当成Proto类型。注意:只检查任何def声明的方法(包括__init__方法);手动给self添加的属性,不会被检查。
from typing import Protocol class Proto(Protocol): def meth(self) -> int: ... class C: def meth(self) -> int: return 0 def func(x: Proto) -> int: return x.meth() func(C()) # Passes static type check
Protocol类也可以是泛型的。
from typing import TypeVar, Protocol T = TypeVar('T') class GenProto(Protocol[T]): def meth(self) -> T: ...
常用类型声明
list[int] / typing.List[T]
val: list[int] = [1, 2, 3, 3]
typing.Tuple[int, str]
from typing import Tuple val: Tuple[int, str] = (1, 'abc')
typing.Sequence[int]
from typing import Sequence val: Sequence[int] = [1, 2, 3]
typing.Iterable[int]
from typing import Iterable val: Iterable[int] = [1, 2, 3] for i in val: print(i)
set[int] / typing.Set[int]
val: set[int] = set() val.add(1) val.add(1)
frozenset[int] / typing.FrozenSet[int]
# There's no add method val: frozenset[int] = frozenset([1, 2, 3])
bytes / bytesarray
val: bytes = b'hello' val2: bytearray = bytearray(b'hello') val2.append(12) val2.append(80)
dict[str, int] / typing.Dict[str, int]
第一个为key类型,第二个为value类型。
# Expected type 'dict[str, int]', got 'dict[str, str | tuple[int, int]]' instead val: dict[str, int] = { 'name': 'alex', 'other': (1, 2) }
字典就如Java里的Map,通过方括号直接添加元素。
num_map: dict[str, int] = {} num_map['num1'] = 1 num_map['num2'] = 2 num_map.keys() num_map.values() num_map.items()
typing里的其它类
typing.Any
任何类型,表示没有类型限制。
typing.NoReturn
标记函数没有返回值。
from typing import NoReturn def stop() -> NoReturn: raise RuntimeError('no way')
typing.Union
联合类型;Union[X, Y],以为着是X类型,或者Y类型。Optional[X] 等于 Union[X, None]。
typing.Optional
Optional[X]表示为X类型或者None。
def foo(arg: Optional[int] = None) -> None: ...
typing.Final
表明变量不能再次赋值,或者不能在子类中被重载。类似Java中的final。
from typing import Final MAX_SIZE: Final[int] = 9000 MAX_SIZE += 1 # Error reported by type checker class Connection: TIMEOUT: Final[int] = 10 class FastConnector(Connection): TIMEOUT = 1 # Error reported by type checker
typing.Literal
用于声明字面量类型。
- Literal[True] 表示只能是 True
- Literal[‘r’, ‘rb’, ‘w’, ‘wb’] 表示只能输入’r’, ‘rb’, ‘w’或’wb’
from typing import Literal, Any def validate_simple(data: Any) -> Literal[True]: # always returns True ... MODE = Literal['r', 'rb', 'w', 'wb'] def open_helper(file: str, mode: MODE) -> str: ... open_helper('/some/path', 'r') # Passes type check open_helper('/other/path', 'typo') # Error in type checker