2020-11-25
< view all postsPython 作为一个动态类型语言,虽然给我们编写代码带来了很多方便,但是动态类型也会容易导致一些隐蔽的bug,一旦出现问题,通常需要较多的精力进行排查。 Python type hint 将类似于静态语言的类型提示引入代码中,可以帮助我们在编码的早期发现潜在的问题。
这篇文章的内容主要参考了mypy的Type hints cheat sheet,以及Python Typing的官方文档。对其中的内容做了翻译和整理,以及一些自己的补充。
首先需要明确的一点是,typing是提供给IDE或者各种插件检查的,实际type与声明不一致并不会导致python的解释器报错。
下面例子中使用的语法需要python 3.6以上的版本。
简单的内置类型,直接用冒号加类型名称作注解。
x: int = 1 x: float = 1.0 x: bool = True x: str = "test" x: bytes = b"test"
可以只声明类型而不给变量赋值。
a: int
集合、字典、列表等类型,将集合内变量的类型放在中括号中。
from typing import List, Set, Dict, Tuple, Optional, Union, Any x: List[int] = [] # OK x: List[int] = [1] x: Set[int] = {6, 7} x: Dict[str, float] = {'field': 2.0} # 固定长度的Tuple,指定所有类型 x: Tuple[int, str, float] = (3, "yes", 7.5) # 变量长度的Tuple,使用省略号 x: Tuple[int, ...] = (1, 2, 3)
可以为None的值,使用Optional注解:
x: Optional[str] = some_function()
同时可以存在多种类型的时候,使用Union进行注解:
x: List[Union[int, str]] = [3, 5, "test", "fun"]
使用Any表示可以为任意类型
x: Any = mystery_function()
对于没有返回值的函数,可以指定返回类型为None
def play(player_name: str) -> None: print(f"{player_name} plays")
对于不存在return的函数,也可以用 NoReturn
from typing import NoReturn def stop() -> NoReturn: raise RuntimeError('no way')
定义函数时注解变量和返回值类型:
from typing import Callable, Iterator, Union, Optional, List # 定义函数 def stringify(num: int) -> str: return str(num) # 多个输入变量 def plus(num1: int, num2: int) -> int: return num1 + num2 # 指定默认值 def f(num1: int, my_float: float = 3.5) -> float: return num1 + my_float
将函数赋值给变量,例如 Callable[[int], str] 表示函数(int) -> str.
x: Callable[[int, float], float] = f
对 Generator 函数可使用 Iterator 作注解
def g(n: int) -> Iterator[int]: i = 0 while i < n: yield i i += 1
对涉及系统输入输出的可以使用 IO 注解
# Use IO[] for functions that should accept or return any # object that comes from an open() call (IO[] does not # distinguish between reading, writing or other modes) def get_sys_IO(mode: str = 'w') -> IO[str]: if mode == 'w': return sys.stdout elif mode == 'r': return sys.stdin else: return sys.stdout
对于 Iterators (实现了__iter__()和__next__()方法的对象),可以使用 Iterable 注解。
from typing import Mapping, MutableMapping, Sequence, Iterable, List, Set def f(ints: Iterable[int]) -> List[str]: return [str(x) for x in ints] f(range(1, 3))
对于序列对象(实现了__len__()和__getitem()__方法的对象),可以使用 Sequence 注解。
def str_len(seq: Sequence) -> str: return str(len(seq))
可以使用 Mapping 注解类似于字典(键值对)形式,且不需要改变其内容的类型。使用 MutableMapping 注解可以改变内容的类型。
def f(my_mapping: Mapping[int, str]) -> List[int]: my_mapping[5] = 'maybe' # 这是与Mapping注解相悖的 return list(my_mapping.keys()) f({3: 'yes', 4: 'no'}) def f(my_mapping: MutableMapping[int, str]) -> Set[str]: my_mapping[5] = 'maybe' # 这是符合MutableMapping注解的 return set(my_mapping.values()) f({3: 'yes', 4: 'no'})
用户自定义的类可以作为类型被注解。
x: MyClass = MyClass()
需要注意的是,如果我们在一个类被定义前使用类名作为注解,是不通过的。如果要让注解和类的位置无关,可以使用类名的字符串。
def f(foo: A) -> int: # This will fail ... class A: ... # If you use the string literal 'A', it will pass as long as there is a # class of that name later on in the file def f(foo: 'A') -> int: # Ok ...
可以用 ClassVar 注解禁止在实例里设置的类的属性。
from typing import ClassVar, Final class Starship: stats: ClassVar[dict[str, int]] = {} # class variable damage: int = 10 # instance variable enterprise_d = Starship(3000) enterprise_d.stats = {} # 与注解相悖 Starship.stats = {} # 这样是可以的
可以用 Final 来注解不能在子类中修改的属性。也可以直接将变量注解为 Final。(New in v3.8)
MAX_SIZE: Final = 9000 class Connection: TIMEOUT: Final[int] = 10
可以用 Literal 来枚举一个变量所有有效的值。
from typing import Literal def validate_simple(data: Any) -> Literal[True]: # always returns True ... MODE = Literal['r', 'rb', 'w', 'wb'] def open_helper(file: str, mode: MODE) -> str: ...
定义类型别名可以有两种方式,直接赋值:
from typing import List, Tuple Card = Tuple[str, str] Deck = List[Card]
或者用NewType:
from typing import NewType UserId = NewType('UserId', int) ProUserId = NewType('ProUserId', UserId)
这两者的区别在于,直接赋值,相当于声明这两个类型之间完全等价。因此类型别名和类型本身是可以互换的。而使用NewType,是从原类型中衍生出了一个子类型,因此在注解了需要子类型变量的地方,使用原类型的变量,是无法通过的。如下面这个例子:
t1 = int def f1(x: t1): pass f1(99) # OK t2 = NewType('t2', int) def f2(x: t2): pass f2(99) # Can not pass a = t2(99) f2(a) # OK
在 for 和 with 语句中不能直接使用类型注解,有两种替代的方案。可以选择在语句前进行注解:
i: int for i in range(5): pass
或者使用旧的注释方式标注类型
for i in range(5): # type: int pass