同事又写出生产bug了
上周review代码,同事把字符串塞给了本该是整数的参数。凌晨两点生产环境直接报警,全组被叫醒。他摊摊手:"Python不是动态类型吗?"我当场就懵了。😂
动态类型从来不是免死金牌
很多人误解了这一点。动态类型只是推迟检查,不是不检查。bug迟到不代表不会来,该炸的雷一个不少。类型提示就是给代码加层保险,防患于未然。
我三年前也嫌typing麻烦
满屏的-> int、List[str],看着纯属画蛇添足。多敲这么多字,运行结果又没变。有这功夫不如多喝杯咖啡刷刷手机,图啥?☕
# 曾经的写法,简洁但危险
defadd(a, b):
return a + b
# 天知道调用者塞进来什么
add("1", "2") # "12",沉默的灾难
直到那次接口变更炸了我一脸
下游返回的用户列表,我以为是list[dict]。结果某天接口改成了list[str],下游代码直接崩。调试追了两小时,真是汗流浃背,记忆深刻。
type hints本质就是结构化注释
IDE能看懂,mypy能看懂,人也能看懂。但光写注解不检查,等于写了借条不追债。废纸一张,自欺欺人而已,没人买账。
from typing import Optional, Union
# 老代码的困惑:这函数到底接受什么?
deffind_user(user_id: Union[int, str]) -> Optional[dict]:
...
# 调用者懵了:传字符串还是整数?返回值会不会是None?
mypy上场那一刻才真香
mypy是类型检查的核武器。静态跑一遍,类型错误无处遁形。没它,注解就是摆设,自欺欺人,写了白写。
# 终端跑 mypy app.py
defgreet(name: str) -> str:
return"Hi " + name
greet(42) # mypy报错: Argument 1 has incompatible type "int"
TypeVar和Optional才是高阶玩法
Optional[X]就是Union[X, None]的语法糖,别滥用Any,那是投降。TypeVar让函数既通用又不丢类型,一个泛型搞定一堆重复代码,爽多了。
from typing import TypeVar, List
T = TypeVar('T')
deffirst(items: List[T]) -> T:
return items[0]
# first(["a", "b"]) 返回 str
# first([1, 2]) 返回 int
# 类型信息全保留,不丢
类型推断让代码自文档化
好的类型签名就是活的文档。新人接手项目,不用翻wiki,看函数签名就知道怎么调用。省下的沟通时间够吃十顿火锅,值不值?
但类型提示也有它的坑
第三方库没注解怎么办?# type: ignore是止痛药,不能当饭吃。还有些场景,鸭子类型比硬类型更Pythonic,别硬套,会疼,真的。
说点得罪人的实话
嘲笑typing是"Java程序员入侵Python"的人,多半没维护过十万行以上的项目。小脚本确实没必要,大项目不写类型?那是给自己埋雷,迟早炸,不信走着瞧。
# 一个类型安全的数据管道示例
from dataclasses import dataclass
from typing import List
@dataclass
classOrder:
id: int
amount: float
deftotal(orders: List[Order]) -> float:
return sum(o.amount for o in orders)
运行时性能?完全是零影响
类型注解在运行时被完全忽略,对速度没有任何影响。担心性能损失的人,纯属杞人忧天,想太多了,大可不必。
我现在的团队项目标配
新项目直接上mypy strict模式,CI里跑,不通过不让合并。一开始团队叫苦连天,三个月后线上bug率降了四成。数据不会说谎,不服来辩。
# pyproject.toml 配置片段
# [tool.mypy]
# python_version = "3.11"
# strict = true
# warn_return_any = true
# disallow_untyped_defs = true
类型提示终究不是万能药
它抓不到逻辑bug,也拦不住产品经理改需求。但该拦的类型错误它都拦了。多一层保险,少一次凌晨两点的报警电话,睡好觉。
从抗拒到真香的心路历程
团队里最早推typing的是我,被嘲笑过"写Python像写Java"。现在那些曾经嘲笑的人,代码里注解比我还多。脸疼吗?不疼,好用就行,真香定律。