typing 模块不断发展,每个Python版本都有新的特性。如果你试图输入支持多个Python版本的代码,这可能会让你感到棘手。为了帮助编写这样的代码,Mypy使用sys.version_info 来识别版本检查,并读取相应的分支。
回落到一个更简单的类型
想象一下,你想在一个 typing.Literal在一个Pizza 类中。
from typing import Literal
class Pizza:
def __init__(self, base: Literal["deep-pan", "thin"]) -> None:
self.base = base
Literal 在Python 3.8中才被引入。为了让这段代码在Python 3.7或更早的版本上工作,你可以有条件地避免使用 ,而退回到一个更简单的类型。由于字面价值都是字符串,你可以在老的Pythons上使用 。Literal str
import sys
if sys.version_info >= (3, 8):
from typing import Literal
PizzaBaseType = Literal["deep-pan", "thin"]
else:
PizzaBaseType = str
class Pizza:
def __init__(self, base: PizzaBaseType) -> None:
self.base = base
这在新旧Python版本上都能通过类型检查。Mypy解析了if sys.version_info 分支,只解释与它所针对的Python版本相匹配的代码。 你可以用 --python-version标志来告诉 Mypy 瞄准哪个 Python 版本。
$ mypy --python-version 3.7 example.py
Success: no issues found in 1 source file
$ mypy --python-version 3.8 example.py
Success: no issues found in 1 source file
这纯粹是内部的,所以它不要求你安装那个版本的 Python。 (如果没有 --python-version的情况下,Mypy 假设你的目标是它所安装的版本)。
Literal 在旧版本上有一个自然的后退类型:允许的字面值的类型。 这仍然提供了某种程度的类型检查安全。 但是对于更复杂的类型,你可能被迫使用 作为后退类型,这将禁用类型检查--不是很好。幸运的是,我们有一个替代方案...Any
使用来自tying-extensions的回传类型
typing-extensions 包包含了回传的和实验性的类型特征。 Mypy 将来自typing_extensions 模块的导入解释为等价的typing 类型,允许你在较早的 Python 版本上使用它们。
你可以完全依赖typing_extensions ,它将在所有的 Python 版本上工作。
from typing_extensions import Literal
class Pizza:
def __init__(self, base: Literal["deep-pan", "thin"]) -> None:
self.base = base
或者,为了避免在较新的 Python 版本中对类型扩展的不必要的依赖。
import sys
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal
class Pizza:
def __init__(self, base: Literal["deep-pan", "thin"]) -> None:
self.base = base
这需要声明 typing-extensions作为对较早的 Python 版本的依赖。对于一个setuptools 包,你可以在setup.cfg 中像这样声明。
[options]
...
install_requires =
...
typing-extensions ; python_version < "3.8"
你可以尝试避免这种依赖,可以通过在TYPE_CHECKING 上检查导入的门槛来做到这一点。
import sys
from typing import TYPE_CHECKING
if TYPE_CHECKING:
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal
PizzaBaseType = Literal["deep-pan", "thin"]
else:
PizzaBaseType = str
class Pizza:
def __init__(self, base: PizzaBaseType) -> None:
self.base = base
但这是非常复杂的。
我认为最好的办法是声明 typing-extensions依赖关系,或者使用回退模式。typing-extensions并不增加太多的开销。