Generic

张开发
2026/4/3 11:18:49 15 分钟阅读
Generic
关于“Generic”这个词在编程里尤其是Python中其实挺有意思的。它不像“类”或者“函数”那样有明确的定义更像是一种思维方式或者说是一种设计上的倾向。很多人第一次接触这个词可能是在“泛型编程”这个概念里觉得特别学术离日常开发很远。但实际写代码时间长了会发现它其实渗透在很多不起眼的地方。举个例子你写一个函数用来处理列表最开始可能只打算处理整数。写着写着发现隔壁组传来的数据是浮点数也能用但偶尔会报错。然后产品说能不能支持一下字符串列表的排序这时候如果一开始就把函数写得很“具体”只认整数那后面就得不断打补丁加if isinstance(x, int)之类的判断代码会变得很臃肿。但如果你换个思路写函数的时候不去假设它具体处理什么类型只关心它需要满足什么行为——比如只要能比较大小就行那这个函数突然就通用了。它既能处理整数、浮点数也能处理字符串甚至自定义的对象只要那个对象实现了比较的方法。这种“不关心具体类型只关心行为”的想法其实就是泛型思维的一种体现。Python本身是动态类型语言所以它的“泛型”和Java、C那些静态语言不太一样。那些语言里的泛型往往和类型系统、编译检查绑在一起写的时候要声明ListT用的时候编译器会帮你确保类型安全。Python没这套严格的检查看起来更自由但也更容易写出运行时才报错的代码。所以Python社区的泛型更多是约定和惯例而不是语法强制。比如你用typing模块里的Generic和TypeVar它们不会改变程序的运行行为只是给类型检查器像mypy看的。告诉它“这个容器里放的可能是任何类型但一旦确定了就得保持一致。”这就像你在仓库的箱子上贴个标签写上“工具”但不指定是扳手还是螺丝刀。贴标签这个动作本身不影响箱子里实际放什么但能让后面来取东西的人更清楚该找什么。这种设计带来的好处是代码更容易复用。你写一个缓存类用泛型定义那么无论是缓存用户数据、商品信息还是会话状态都可以用同一个类不用重复写。而且类型提示能让IDE更好地自动补全减少低级错误。不过代价是代码看起来会稍微复杂一些特别是涉及到多重继承或者复杂约束的时候。有时候看一些开源项目的源码会发现他们即使不用typing模块也会在命名和文档里体现泛型的思想。比如函数名用process_items而不是process_integers文档里写“接受一个可迭代对象”而不是“接受一个列表”。这种命名的选择其实就是在暗示这个函数更通用更灵活。泛型也不是越通用越好。过度追求通用性可能会让代码变得难以理解或者引入不必要的抽象。比如你明明只需要处理本地文件的读写却硬要抽象成一个“数据源接口”支持网络、数据库、缓存等等那就有点过度设计了。判断什么时候该用泛型什么时候该写具体一点很多时候靠的是经验或者说对需求变化的预感。在Python里玩泛型还有一个有趣的地方是鸭子类型duck typing。它甚至比传统的泛型更“泛”——不关心对象的类型是什么只关心它有没有需要的方法。比如一个对象只要有__len__方法就可以被len()函数处理只要有__iter__方法就可以被循环遍历。这种设计让Python非常灵活但也要求开发者自己多注意因为错误可能要到运行时才暴露。所以回到最初的问题Generic在Python里与其说是一个具体的工具不如说是一种鼓励写灵活、可复用代码的倾向。它藏在类型提示里藏在函数签名里也藏在那些优秀的代码约定里。刚开始可能觉得它有点绕用多了会发现它其实是在帮你减少重复劳动让代码更容易适应变化。当然前提是得用对地方不然就像拿着万能扳手去拧所有螺丝有时候还不如一把合适的螺丝刀来得顺手。有时候看一些开源项目的源码会发现他们用TypeVar的姿势很巧妙比如结合overload装饰器来描述更复杂的函数行为或者用ParamSpec来处理回调函数的参数类型。这些都是类型系统里比较进阶的用法有机会再展开聊。总之TypeVar算是Python静态类型检查工具箱里的一件实用工具。刚开始可能觉得用不用无所谓但习惯之后会发现它在描述代码意图方面确实能起到不错的效果。

更多文章