如何分离屎山中 Union 类型的变量

2025 年 7 月 31 日
 xuegy

正在处理一座屎山,大概情况是这样的。有一个自定义的class X,需要改掉的东西注释写的是y=Union[X, str],实际情况也是两种类型到处都混在一起根本分不清。后面所有跟X类有关的方法一点注释没写,甚至都不做类型判断,而是大量的使用getattr(y, 'name', y)把水搅得更浑(如果y是字符串,没有name,返回字符串本身。否则返回y.name也是一个字符串)。最离谱的是整座屎山还配了一套 30 分钟才能跑完的 pytest 。

现在需要把混乱的y分成确定类型的yy_str,使用 pydantic 强制定义数据类型来一点点排错。然而改了几百行以后,所有跟X有关的方法都被迫改出两种版本的。pytest 不再报类型错误了,却开始出现各种离奇的 bug 挂掉。

有什么更强大的工具可以搞定这件事吗?比如同步检测两个版本的代码,看从哪一步开始两边的数据变得不一致了?

2353 次点击
所在节点    Python
12 条回复
chaoshui
2025 年 7 月 31 日
交给 AI
xuegy
2025 年 7 月 31 日
@chaoshui 用 GPT o4-mini-high 也无力回天
huangyezhufeng
2025 年 7 月 31 日
其实从开始用`Union`开始,就已经把潘多拉魔盒打开了一半。再用`getattr`,几乎是不可避免地成为屎山了。

为了避免误解,补充说明一下,用`Union`和`getattr`都没问题,前提是会用。从这个半个钟的单测来看,似乎并不是如此。
Syiize
2025 年 7 月 31 日
可以重写 class X 中的魔术方法 __getattribute__,如果要访问的属性是 "name",就 raise RuntimeError 。然后运行代码,再根据 Python 的 traceback 定位到对应的位置修改。
xuegy
2025 年 7 月 31 日
@huangyezhufeng 字里行间甚至能看出来原作者对于 getattr 这个小聪明还挺自豪的
xuegy
2025 年 7 月 31 日
@Syiize 这个不行吧,是真的需要查询 y.name 。我隐约感觉整个代码的真实运行逻辑就是建立在 getattr 大乱炖的基础之上。
julyclyde
2025 年 8 月 1 日
@Syiize 冒烟测试,哈哈哈
julyclyde
2025 年 8 月 1 日
@xuegy getattr 、getattribute 、__call__之类的,都过于动态了,只有 runtime 才知道这玩意具体在干啥
woodfizky
2025 年 8 月 1 日
代码量有多少?建议不要直接在屎山上改,而是认真品鉴屎山的代码逻辑之后,用更好的方式重新写一遍。
有原始需求或者设计文档最好了。

血泪教训,特别是需要长期维护迭代的项目,早期图省事屎上雕花,后面会长痛。
除非真的赶时间,不如短痛一下重写。
Syiize
2025 年 8 月 1 日
@xuegy #6 应该是可以的。我测试了一下,重写过__getattribute__后,不论是 y.name 还是 getattr(y, "name", y) 都会打印出信息的。
xuegy
2025 年 8 月 2 日
@woodfizky 总量差不多十万行,涉及操作这个类的有五千行左右。其实我就是在用 C++和 pybind11 移植底层的数据结构,但是老板觉得 std::variant 是屎上雕花不解决问题。所以我只能是先在 Python 里面试着分离一下,能跑过 pytest 了再移植到 C++。
zizon
2025 年 8 月 4 日
>pytest 不再报类型错误了,却开始出现各种离奇的 bug 挂掉
说明有些地方已经变成 feature 了.

比如本来就是不是 str 的,但是当作 str 处理了产生后续逻辑.

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1148903

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX