python静态类型检查器-mypy简易教程
python 静态类型检查器-mypy 简易教程
一、简介
对于一个深度使用 TS 的程序员来说,一开始写 python 发现竟然没有静态类型检查,内心是拒绝的,直到我发现了 mypy。
Mypy 是 Python 中的静态类型检查器。Mypy 具有强大且易于使用的类型系统,具有很多优秀的特性,例如类型推断、泛型、可调用类型、元组类型、联合类型和结构子类型。
二、安装
Mypy 需要 Python 3.5 或更高版本才能运行。
python3 -m pip install mypy
然后把我们之前写的 python 代码,例如:
def greeting(name):
return 'Hello ' + name
只需要稍加改造,添加类型注释即可:
def greeting(name: str) -> str:
return 'Hello ' + name
然后运行:
$ mypy greeting.py
尽管您必须安装 Python 3 才能运行 mypy,但 mypy 也能对 Python 2 代码进行类型检查:只需传入 --py2
标志即可。
$ mypy --py2 greeting.py
我们也可以给参数设置默认值:
def greeting(name: str, excited: bool = False) -> str:
message = 'Hello, {}'.format(name)
if excited:
message += '!!!'
return message
如果我们不想检查这一行,类似 TS 中的 @ts-ignore,可以使用 #type:ignore 忽略:
import frobnicate # type: ignore
三、类型
内置类型:
类型 | 描述 |
---|---|
int | 整数 |
float | 浮点数 |
bool | 布尔值 |
str | 字符串 |
bytes | 8-bit 字符串 |
object | 对象 |
Any | 任意类型 |
list[str] | 字符串数组 |
tuple[int, int] | 2 个整数元素的元祖 |
tuple[int, ...] | 任意数量整数元素的元祖 |
dict[str, int] | key 为字符串,value 是整数的字典 |
Iterable[int] | 可迭代类型,元素为整数 |
Sequence[bool] | 布尔值序列 |
Mapping[str, int] | key 是字符串,value 是整数的映射 |
更多类型:
Class 类型,使用自定义类作为类型注释:
class A:
def f(self) -> int: # Type of self inferred (A)
return 2
class B(A):
def f(self) -> int:
return 3
def g(self) -> int:
return 4
def foo(a: A) -> None:
print(a.f()) # 3
a.g() # Error: "A" has no attribute "g"
foo(B()) # OK (B is a subclass of A)
Callable 类型,是否可调用:
from typing import Callable
def arbitrary_call(f: Callable[..., int]) -> int:
return f('x') + f(y=2) # OK
arbitrary_call(ord) # No static error, but fails at runtime
arbitrary_call(open) # Error: does not return an int
arbitrary_call(1) # Error: 'int' is not callable
Union 类型,联合类型,也可以写为 type1 | type2:
from typing import Union
def f(x: Union[int, str]) -> None:
x + 1 # Error: str + int is not valid
if isinstance(x, int):
# Here type of x is int.
x + 1 # OK
else:
# Here type of x is str.
x + 'a' # OK
f(1) # OK
f('x') # OK
f(1.1) # Error
Note
Optional 类型,可选类型, Optional[X] 相当于 Union[X,None]:
from typing import Optional
def strlen(s: str) -> Optional[int]:
if not s:
return None # OK
return len(s)
def strlen_invalid(s: str) -> int:
if not s:
return None # Error: None not compatible with int
return len(s)
NamedTuple 类型:
from typing import NamedTuple
Point = NamedTuple('Point', [('x', int),
('y', int)])
p = Point(x=1, y='x') # Argument has incompatible type "str"; expected "int"
TypeVar 任意类型:
from typing import TypeVar
T = TypeVar('T') # 任意类型
A = TypeVar('A', int, str) # A类型只能为int或str
def test(t: A) -> None:
print(t)
test(1)
Generator 生成器类型:
def echo_round() -> Generator[int, float, str]:
sent = yield 0
while sent >= 0:
sent = yield round(sent)
return 'Done'
NoReturn 类型,函数永不返回:
from typing import NoReturn
def stop() -> NoReturn:
raise Exception('no way')
NewType 类型,声明一个不同的类型而又不实际执行创建新类型,在运行时,将返回一个仅返回其参数的虚拟函数:
from typing import NewType
UserId = NewType('UserId', int)
def name_by_id(user_id: UserId) -> str:
...
UserId('user') # Fails type check
name_by_id(42) # Fails type check
name_by_id(UserId(42)) # OK
num = UserId(5) + 1 # type: int
**overload 类型,**给同一个函数多个类型注释来更准确地描述函数的行为:
from typing import Union, overload
# Overload *variants* for 'mouse_event'.
# These variants give extra information to the type checker.
# They are ignored at runtime.
@overload
def mouse_event(x1: int, y1: int) -> ClickEvent: ...
@overload
def mouse_event(x1: int, y1: int, x2: int, y2: int) -> DragEvent: ...
# The actual *implementation* of 'mouse_event'.
# The implementation contains the actual runtime logic.
#
# It may or may not have type hints. If it does, mypy
# will check the body of the implementation against the
# type hints.
#
# Mypy will also check and make sure the signature is
# consistent with the provided variants.
def mouse_event(x1: int,
y1: int,
x2: Optional[int] = None,
y2: Optional[int] = None) -> Union[ClickEvent, DragEvent]:
if x2 is None and y2 is None:
return ClickEvent(x1, y1)
elif x2 is not None and y2 is not None:
return DragEvent(x1, y1, x2, y2)
else:
raise TypeError("Bad arguments")
Literal 类型,表明一个表达式等于某个特定的原始值。例如,如果我们用 type 注释一个变量 Literal["foo"]
,mypy 将理解该变量不仅是 type str
,而且还特别等于 string "foo"
。
from typing import overload, Union, Literal
# The first two overloads use Literal[...] so we can
# have precise return types:
@overload
def fetch_data(raw: Literal[True]) -> bytes: ...
@overload
def fetch_data(raw: Literal[False]) -> str: ...
# The last overload is a fallback in case the caller
# provides a regular bool:
@overload
def fetch_data(raw: bool) -> Union[bytes, str]: ...
def fetch_data(raw: bool) -> Union[bytes, str]:
# Implementation is omitted
...
reveal_type(fetch_data(True)) # Revealed type is "bytes"
reveal_type(fetch_data(False)) # Revealed type is "str"
# Variables declared without annotations will continue to have an
# inferred type of 'bool'.
variable = True
reveal_type(fetch_data(variable)) # Revealed type is "Union[bytes, str]"
Final 类型,限定符来指示不应重新分配、重新定义或覆盖名称或属性:
from typing import Final
RATE: Final = 3000
class Base:
DEFAULT_ID: Final = 0
RATE = 300 # Error: can't assign to final attribute
Base.DEFAULT_ID = 1 # Error: can't override a final attribute
四、配置文件
mypy 将按照当前目录下的 mypy.ini > .mypy.ini > pyproject.toml > setup.cfg 顺序查找配置文件。或者使用 --config-file
指定配置文件。
配置文件格式 :
- [mypy] 全局设置
- [mypy-PATTERN1,PATTERN2,...] 特定模块设置
如果是使用 pyproject.toml 文件,则有一些不同的地方:
- [mypy] 改写为 [tool.mypy]
- 模块特定部分应移入
[[tool.mypy.overrides]]
部分,例如,[mypy-packagename]
会变成:
[[tool.mypy.overrides]]
module = 'packagename'
...
- 多模块特定部分可以移动到单个
[[tool.mypy.overrides]]
部分中,并将模块属性设置为模块数组,例如,[mypy-packagename,packagename2]
会变成:
[[tool.mypy.overrides]]
module = [
'packagename',
'packagename2'
]
...
- 字符串必须用双引号括起来,如果字符串包含特殊字符,则必须用单引号括起来,布尔值应全部小写
常见配置项:
- files 逗号分隔的路径列表,如果命令行上没有给出,则应由 mypy 检查,支持递归。
- exclude 应忽略检查的文件名、目录名和路径
- ignore_missing_imports 禁止有关无法解析的导入的错误消息。
- disallow_untyped_defs 不允许定义没有类型注释或类型注释不完整的函数。
- plugins 逗号分隔的 mypy 插件列表