Python `mypy` / `pyright`:静态类型检查与代码质量

好的,各位观众老爷们,欢迎来到今天的“Python类型检查与代码质量”脱口秀现场!我是今天的段子手——哦不,是主讲人,咱们今天就来好好聊聊Python里那些“看似可有可无,实则至关重要”的类型检查工具:mypypyright

开场白:Python,你的类型在哪里?

话说Python这门语言啊,以其简洁易懂著称,深受广大码农喜爱。但是,它也有个“小秘密”,那就是它是个动态类型语言。啥意思呢?就是说,你定义一个变量的时候,不用像Java或者C++那样,明确告诉它是什么类型,Python自己会“猜”。

x = 10  # Python:“嗯,看起来像个整数。”
y = "Hello"  # Python:“哦,这是个字符串。”

这种“猜猜猜”的机制在开发初期确实很方便,写代码嗖嗖的,感觉自己就是风一样的男子。但是,随着项目越来越大,代码越来越多,这种动态类型的弊端就暴露出来了:

  • 运行时错误: 很多类型错误只有在程序真正运行的时候才会发现,比如你把一个字符串和一个整数相加,程序就崩给你看。
  • 代码可读性差: 别人(或者未来的自己)看你的代码,很难一下子明白某个变量应该是什么类型,需要仔细阅读代码才能推断出来。
  • 重构困难: 如果你需要修改某个变量的类型,可能会影响到很多地方的代码,而且很难保证修改的正确性。

这就像开一辆没有刹车的跑车,一开始感觉很爽,但是一旦遇到紧急情况,就只能眼睁睁地看着它撞墙了。

救星驾到:静态类型检查

为了解决这些问题,就出现了静态类型检查。静态类型检查就是在程序运行之前,通过分析代码来检查类型错误。这样就可以在开发阶段尽早发现问题,避免运行时错误。

Python里最流行的静态类型检查工具就是mypypyright。它们可以帮助你给Python代码加上类型注解,然后检查代码中是否存在类型错误。

mypy:老牌劲旅

mypy是Python官方推荐的静态类型检查器,它历史悠久,功能强大,社区支持也很活跃。

  • 安装mypy

    pip install mypy
  • 基本使用:

    1. 给你的代码加上类型注解。
    2. 运行mypy your_file.py来检查类型错误。

pyright:后起之秀

pyright是微软开发的静态类型检查器,它使用TypeScript的类型检查引擎,性能非常出色,而且对VS Code的支持也非常好。

  • 安装pyright

    npm install -g pyright

    或者通过VS Code的插件安装。

  • 基本使用:

    pyright通常会自动在VS Code中运行,你也可以在命令行中使用pyright your_file.py来检查类型错误。

类型注解:给你的代码加个“身份证”

类型注解就是给你的代码中的变量、函数参数和返回值加上类型信息。这样mypypyright就知道你期望的类型是什么,然后就可以检查你的代码是否符合要求。

  • 变量注解:

    x: int = 10  # x是一个整数
    name: str = "Alice"  # name是一个字符串
    pi: float = 3.14159  # pi是一个浮点数
  • 函数注解:

    def add(x: int, y: int) -> int:
        return x + y
    
    def greet(name: str) -> str:
        return f"Hello, {name}!"

    ->符号表示函数的返回值类型。

  • 复杂类型注解:

    from typing import List, Dict, Tuple
    
    numbers: List[int] = [1, 2, 3, 4, 5]  # numbers是一个整数列表
    person: Dict[str, str] = {"name": "Bob", "age": "30"}  # person是一个字符串键值对的字典
    coordinates: Tuple[float, float] = (10.0, 20.0)  # coordinates是一个浮点数元组

类型注解的常见用法

类型 描述 示例
int 整数 x: int = 10
float 浮点数 pi: float = 3.14159
str 字符串 name: str = "Alice"
bool 布尔值(True或False) is_valid: bool = True
List[T] 元素类型为T的列表 numbers: List[int] = [1, 2, 3]
Dict[K, V] 键类型为K,值类型为V的字典 person: Dict[str, int] = {"age": 30}
Tuple[T1, T2, ...] 元素类型分别为T1, T2, …的元组 point: Tuple[int, int] = (10, 20)
Set[T] 元素类型为T的集合 unique_numbers: Set[int] = {1, 2, 3}
Optional[T] T类型或None name: Optional[str] = None
Any 任意类型 data: Any = 123
Union[T1, T2, ...] T1, T2, …类型之一 value: Union[int, str] = 10

mypypyright的进阶技巧

  • 配置mypy

    你可以在项目根目录下创建一个mypy.ini文件来配置mypy的行为,比如忽略某些错误、指定Python版本等。

    [mypy]
    python_version = 3.8
    ignore_missing_imports = True
  • 配置pyright

    pyright的配置文件是pyrightconfig.json

    {
      "pythonVersion": "3.8",
      "venvPath": ".",
      "venv": "venv",
      "reportMissingImports": false
    }
  • 使用--strict模式:

    mypy --strict your_file.pypyright --strict your_file.py会启用更严格的类型检查,可以帮助你发现更多潜在的问题。

  • 忽略类型错误:

    有时候你可能需要暂时忽略某些类型错误,可以使用# type: ignore注释。

    x = 10
    y = "Hello"
    z = x + y  # type: ignore  # 暂时忽略这个类型错误

    但是,请谨慎使用这个功能,最好在解决问题后再删除这个注释。

  • 使用TypeVar

    TypeVar可以用来定义泛型类型,让你的代码更加灵活。

    from typing import TypeVar, List
    
    T = TypeVar('T')
    
    def first(items: List[T]) -> T:
        return items[0]

一个完整的例子

from typing import List, Optional

class Student:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

    def __repr__(self) -> str:
        return f"Student(name='{self.name}', age={self.age})"

def get_student_names(students: List[Student]) -> List[str]:
    """
    Returns a list of student names.
    """
    names: List[str] = []
    for student in students:
        names.append(student.name)
    return names

def find_student_by_name(students: List[Student], name: str) -> Optional[Student]:
    """
    Finds a student by name. Returns None if no student is found.
    """
    for student in students:
        if student.name == name:
            return student
    return None

if __name__ == "__main__":
    students: List[Student] = [
        Student("Alice", 20),
        Student("Bob", 22),
        Student("Charlie", 21),
    ]

    names: List[str] = get_student_names(students)
    print(f"Student names: {names}")

    student: Optional[Student] = find_student_by_name(students, "Bob")
    if student:
        print(f"Found student: {student}")
    else:
        print("Student not found.")

    # 故意引入一个类型错误
    # ages: List[int] = get_student_names(students) # 这行代码会导致类型错误

运行mypypyright,你将会看到类似下面的输出:

your_file.py:52: error: Incompatible types in assignment (expression has type "List[str]", variable has type "List[int]")
Found 1 error in 1 file (checked 1 source file)

这说明mypypyright发现了类型错误,你需要修改代码来解决这个问题。

类型体操:让你的代码更健壮

类型检查不仅可以帮助你发现类型错误,还可以提高代码的可读性和可维护性。通过使用类型注解,你可以让你的代码更加清晰,更容易理解,也更容易进行重构。

  • 提高代码可读性: 类型注解可以让你一眼就明白某个变量应该是什么类型,而不用仔细阅读代码来推断。
  • 提高代码可维护性: 当你需要修改某个变量的类型时,类型检查器可以帮助你找到所有受到影响的代码,并确保修改的正确性。
  • 减少运行时错误: 类型检查可以在程序运行之前发现类型错误,避免运行时错误,提高程序的稳定性。

总结:告别Bug,拥抱高质量代码

mypypyright是Python开发中不可或缺的工具。它们可以帮助你发现类型错误,提高代码的可读性和可维护性,减少运行时错误,让你的代码更加健壮。

虽然刚开始使用类型检查可能会觉得有点麻烦,需要花费一些时间来给代码加上类型注解。但是,从长远来看,这绝对是值得的。因为它可以帮助你节省大量的时间和精力,避免不必要的麻烦。

记住,好的代码就像一栋坚固的房子,需要精心设计和建造。而类型检查就像是房子的地基,只有地基打牢了,房子才能屹立不倒。

所以,各位观众老爷们,赶紧拿起你的键盘,开始使用mypypyright吧!让我们的代码告别Bug,拥抱高质量!

彩蛋:一些幽默的建议

  • 如果你发现mypypyright报了很多错误,不要灰心,这说明你的代码还有很大的改进空间。
  • 如果你实在不想给代码加上类型注解,可以试试--allow-untyped-globals--allow-untyped-defs选项,但是我不建议你这样做。
  • 如果你发现某个类型错误很难解决,可以考虑重构你的代码,也许是你的设计有问题。
  • 记住,类型检查只是工具,最终还是要靠你自己的智慧和努力。

好啦,今天的脱口秀就到这里了,感谢大家的观看!希望大家能够喜欢!下次再见!

发表回复

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