给忙碌的 Python 程序员的类型提示

“好吧,我知道类型注释或类型提示或其他什么——”

是的,“类型注解”和“类型提示”是同一个意思。

“是的。我知道它们是什么:它们允许您为变量和函数参数和返回值指定数据类型,这可以让您及早发现某些类型的错误并避免大量调试和头痛。我在。如何我用它们吗?”

有两部分。 第一部分是向源代码添加类型提示。 这涉及 typing 从 verison 3.5 开始,它是 Python 标准库的一部分。 第二部分是安装 mypy 模块进行实际检查。

“等等,为什么有两个模块?听起来很复杂。”

第一个模块, typing, 让您指定不同的数据类型。 但是 Python 解释器并不关心它们是否正确。 变量的实际值不必与变量的类型匹配; 代码仍然可以正常运行。

“这不是违背了初衷吗?”

不,请记住在 Python 中类型提示是可选的。 如果您正在编写一个快速脚本并且不在乎,则不必使用类型提示。 Python 解释器本身完全忽略类型提示。 运行时不进行类型检查。 因此,从技术上讲,类型提示不是“可选静态类型”,因为“静态类型”意味着变量总是存储某种类型的值。 即使有类型提示,Python 仍然是一种动态类型的语言。 类型提示只是第三方工具(如 Mypy)可以用来指出潜在错误的提示。

这是第二个模块, mypy,这是实际检查您的源代码是否有效以及所有类型是否匹配的工具。 Mypy 是由第三方制作的,这就是我们使用两个独立模块的原因。 您不必使用 Mypy。 您可以改为使用任何第三方模块来执行 Mypy 所做的检查。

“那我可以用什么来代替 Mypy?”

Pyright,来自微软。 柴堆,来自 Facebook。 Pytype,来自谷歌。 它们都是免费的,而且工作原理应该相同,但大多数人似乎都在使用 Mypy。

“所以 typing Python 3.5 及更高版本附带。 我该如何安装 Mypy?”

跑步 python -m pip install mypy. (使用 python3 在 macOS 和 Linux 上。)添加 --user 如果您收到有关权限的错误消息: python -m pip install --user mypy.

如果 Python 解释器不关心类型提示,那么我如何检查我的源代码是否与声明的类型匹配?

当您安装 Mypy 时,Mypy 会在 Python 的 Scripts 文件夹中安装一个命令行工具。 所以你跑 mypy yourProgram.py 从命令行(而不是从 Python 交互式 shell)并且如果您的代码有效,它不会打印任何内容。 如果不是,它将显示错误。 我一般使用 python -m mypy yourProgram.py 运行它的风格。

但更容易的是为您使用的任何编辑器或 IDE 获取插件。 这样,您的编辑器会在您键入代码时在后台运行 Mypy,并且几乎可以立即向您显示您犯的任何错误。 例如,对于 Sublime Text,您只需使用包控制安装 SublimeLinter 和 SublimeLinter-contrib-mypy。 然后,当您在 Sublime Text 中键入 Python 代码时,它会显示 Mypy 自动发现的任何错误。

“当你说 Python 中的类型提示是可选的时,你的意思是我的一半变量可以使用类型提示而另一半不必?”

是的。 Python 称之为渐进式输入。 如果您想将类型提示添加到您的 Python 代码中,您不必一次完成所有操作。 这就像单元测试覆盖率:您可以只测试您的一些功能。

“好的,我将安装 Mypy 并设置我的编辑器以使用它。完成。那么在我的代码中编写类型提示时我使用的所有类型是什么?”

Mypy 站点有一个很棒的备忘单。 基本上,您使用与那些类型转换函数相同的名称,例如 int(), str(), 等等。 那是因为这些在技术上是整数和字符串的类名。 就像你打电话 datetime.date(2025, 10, 31) 创建一个对象 date 班级, int("42") 创建一个整数对象 int 班级。 在 Python 中,“类”、“类型”和“数据类型”是完全相同的东西。

“好的。那么我该如何在我的代码中编写类型提示呢?”

在变量名称之后,添加一个冒号和类型名称。 这是一个示例,注释解释了每一个:

numberOfTacos: int = 42  # This variable's type int declares it to be an int variable.
tacosEaten: int  # This declares tacosEaten to be an int variable, but doesn't set an initial value. It's not set to None: using it will result in NameError.

# Type hints without an initial value are handy when there's no one place where an initial value is set, like here:
if numberOfTacos 

"Cool. So what happens when I write code that sets a variable to the wrong type of value?"

In Python, nothing. When you run the Python interpreter to run your program, it doesn't care about type hints. It just sets the variable to the value. But, before that point, when you're writing the source code in an editor that runs Mypy in the background, Mypy will report to the editor that there's a problem and the editor will show it to you as you're typing.

numberOfTacos: int = 42  # The numberOfTacos variable should only have ints.
numberOfTacos="bleventeen"  # Your editor will display a warning on this line. But Pyhton will still run this code just fine.

“列表、元组、字典和其他容器类型中的值怎么样?我可以为它们指定类型提示吗?”

是的,您这样做是在其中添加带有项目类型的方括号。 但 cats: list[str] = ['Zophie', 'Pooka'] 不是有效的 Python 代码。 (这种风格行不通的原因太长了,无法在这里展开。)Python 核心开发人员提出的解决方案是在 typing 模块。 这是一个例子:

from typing import List, Tuple, Dict, Set, Frozenset

groceryList: List[str] = ['bread', 'eggs', 'tofu']
cityHall: Tuple[float, float, str] = (37.779, -122.419, 'San Francisco')
fruitCount: Dict[str, int] = {'apples': 5, 'tomatoes': 7}
barbers: Set[str] = set(['Alice', 'Bob', 'Carol'])
shavers: Frozenset[str] = frozenset(['Alice', 'Bob', 'Carol'])

“如果我想要一个可以包含其项目的多种类型值的列表怎么办?”

使用 Uniontyping 模块。 例如:

from typing import Union
favoriteLettersAndNumbers: List[Union[int, str]] = [42, 'x', 86]

您也可以使用 Union 指定可以具有多种类型的变量:

from typing import Union
serialNumber: Union[int, str] = 42
serialNumber="42b"

“好吧。所以如果我有一个包含整数的变量,但也可以包含 None,我应该使用,呃, Union[int, NoneType]

您可以使用 Optional 相反,这意味着同一件事。 从技术上讲, NoneType 不是定义的类型 int 或者 str 是。 所以你不能说 type(None) == NoneType 你可以说的方式 type(42) == int. 相反,我们只是使用 None 就像在这个例子中:

from typing import Union, Optional

# These two have the same type hint, but `Optional[int]` is easier to write and read:
eggs: Union[int, None] = 42
bacon: Optional[int] = 42

但事情是这样的:你应该避免使用像这样的“空”值 None 根本。 计算机科学家和零值的发明者 Tony Hoare 将其称为他的十亿美元错误,因为它通常是错误的原因。 我依稀记得一些研究表明,NullReferenceException 本身就是 30% 的 Java 应用程序崩溃的原因。 Kotlin 是一种现代化的 Java 语言,默认情况下不允许您将变量设置为 null。 你仍然可以使用 None 如果需要,但不要未经深思熟虑就使用它。

“如果我不关心变量具有什么类型的值怎么办。我应该不使用类型提示吗?”

不,请记住“显式优于隐式”。 你应该明确地使用 Any 可以是任何类型的变量的类型:

from typing import Any
spam: Any = 42
spam = 'hello'  # This is fine. `spam` can be set to any type.

我可以做更具体的检查吗? 比如,指定一个变量应该是一个整数,但绝不能是负整数?

没有。 类型提示仅适用于类型,不适用于值。 我建议将类子类化,或者,因为你不能子类化 int,您创建自己的非负整数类。 无论如何,请记住类型提示仅在运行时之前有效。 他们不对值进行运行时检查。 你应该使用 assert 甚至只是 if 声明。

“我写的课程或其他类型的课程怎么样 datetime.date 或从返回的匹配对象 re.search('[aeiou]', 'hello')

您只需使用类名作为类型。 请记住,在 Python 中,术语类型、数据类型和类都表示同一事物。 还要注意的是 self 没有使用类型提示编写:

class Cat:
    def __init__(self, name: str, color: str, weightkg: float) -> None:
        self.name = name
        self.color = color
        self.weightkg = weightkg

def getZophieClone() -> Cat:
    return Cat('Zophie', 'gray', 4.9)

“那些奇怪的类型呢,比如函数、序列、映射或可迭代对象?”

typing 模块有 Callable, Sequence, Mapping, Iterable, 和别的。 您可以在文档中找到它们。

“好吧,很酷。那么函数呢?如何为参数和返回值编写类型提示?”

这些都是在 def 陈述。 返回值的类型提示在 -> 箭。 例如:

from typing import Optional, List

def getVowelsFromWord(word: str, maxVowels: Optional[int] = None) -> List[str]:
    vowels: List[str] = []
    for letter in word:
        if maxVowels is not None and len(vowels) >= maxVowels:
            break  # Break early since we've reached the maxmimum.

        if letter.upper() in 'AEIOU':
            vowels.append(letter) # Record this vowel letter.
    return vowels  # Return all the vowels fown in word.

在我们的 getVowels() 函数,它有两个参数, word (可以是字符串)和 maxVowels (可以是整数或 None). 该函数本身返回一个字符串列表。 像 Mypy 这样的工具可以验证 return 语句返回 vowels 变量,具有类型提示 List[str],这又与函数的返回类型提示相匹配。

“Python 2 中的类型提示怎么样?那些冒号不会导致语法错误吗?”

是的,但是如果您正在编写 Python 2(或 Python 3.5 之前)兼容的代码,则可以将类型提示放在注释中:

from typing import List # Required even for comment-style type hints.

spam = 42 # type: int
def sayHello():
    # type: () -> None
    """The docstring comes after the type hint comment."""
    print('Hello!')

def addTwoNumbers(listOfNumbers):
    # type: (List[float]) -> None
    listOfNumbers[0] += listOfNumbers.pop()

请注意,您仍然需要从 typing 即使使用类型提示的注释样式。

“我已经开始使用类型提示,但我遇到了一个并不真正适用于我的代码的警告。我如何让 Mypy 忽略这一行?”

您可以添加评论 # type: ignore 到该行的末尾:

from typing import Any

def raiseMyCustomException(val: Any):
    # Some code here...
    raise Exception('message here: ' + str(val))

def getLengthIfString(val: Any) -> int:
    # Some code here...

    # This function call always raises an exception, but mypy thinks
    # gives the error "missing return statement", so we tell mypy to ignore it:
    raiseMyCustomException(val)  # type: ignore

“太棒了!现在我知道的足够多了,可以开始了。我在哪里可以了解更多关于类型提示的信息?”

查看官方 Python 文档或 Mypy 文档,从备忘单开始。 Carl Meyer 在 PyCon 2019 上的类型提示演讲也很受欢迎。

阅读更多

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注