在Python中,猴子补丁标准库方法是好实践吗?

2 投票
2 回答
844 浏览
提问于 2025-04-16 02:35

随着时间的推移,我发现需要重写一些Python的stdlib方法,以解决一些限制或添加缺失的功能。

在所有情况下,我都添加了一个包装函数,并用我的包装函数替换了模块中的原始方法(这个包装函数会调用原始方法)。

我为什么要这样做呢?就是为了确保所有对这个方法的调用都使用我的新版本,即使这些调用来自其他第三方模块。

我知道猴子补丁(monkeypatching)可能不是个好事,但我的问题是,如果小心使用,这是否有用?也就是说:

  • 你仍然调用原始方法,确保在原始模块更新时不会遗漏任何东西
  • 你没有改变原始方法的“意义”

举几个例子:

  • 给Python的日志模块添加颜色支持
  • open()能够识别Unicode BOM掩码,在使用文本模式时。
  • os.system()subprocess.Popen()添加日志支持 - 让你可以输出到控制台或重定向到另一个文件。
  • 实现你平台上缺失的方法,比如Windows上缺失的os.chown()os.lchown()

像这样的做法在我看来是合理的重写,但我想看看其他人是怎么看的,特别是哪些被认为是可以接受的猴子补丁,哪些又不可以。

2 个回答

7

这些事情似乎都不需要用到“猴子补丁”。看起来都有更好、更可靠的解决方案。

添加一个日志处理器很简单,不需要猴子补丁。

修复打开文件的方式是这样的。

from io import open

这很简单,不需要补丁。

如果要记录到 os.system(),我觉得用一个简单的“包装”函数会比复杂的补丁好得多。此外,我会使用 subprocess.Popen,因为这是推荐的替代方案。

为了处理操作系统之间的差异(比如 os.chown()),添加缺失的方法似乎更适合用 try/except 来处理。但这只是我的看法。我更喜欢明确的方式,而不是隐含的。

总的来说,我还是看不出使用猴子补丁的好理由。

我不想因为过于依赖猴子补丁而被锁定在旧代码(比如 os.system)里。


“子类”的概念不仅适用于类,也适用于模块。你可以很容易地编写自己的模块,这些模块可以 (a) 导入和 (b) 扩展现有模块。然后你使用你的新模块,因为它们提供了额外的功能。你不需要猴子补丁。

即使这些模块是从其他第三方模块调用的

这真是个糟糕的主意。通过改变内置功能,你很容易就会破坏另一个模块。如果你已经阅读了其他模块,并且非常确定你的猴子补丁不会出问题,那么你发现的就是这一点。

  1. 那个“其他”模块应该有定制的空间。它应该有一个“依赖注入”或“策略”设计模式的地方。想得不错。

  2. 一旦你发现了这一点,可以修复“其他”模块以允许这种定制。可能只是简单地更改文档,说明如何修改一个对象。也可能是构造时增加一个参数来插入你的定制。

  3. 然后你可以将修改后的模块提供给作者,看看他们是否会支持你对他们模块的小更新。许多类可以通过支持“依赖注入”或“策略”设计来获得额外的帮助。

如果你没有阅读其他模块,并且不确定你的猴子补丁是否有效……好吧……我们仍然希望猴子补丁不会破坏任何东西。

3

猴子补丁(Monkeypatching)有时候可以算是“坏中之坏”,特别是在你需要测试一些设计得不太好、难以测试的代码时(比如不支持依赖注入等)。在这种情况下,你会在测试环境中临时使用猴子补丁,通常是为了用模拟或假对象来隔离测试(也就是说,把它们变成单元测试,而不是集成测试)。

不过,这种“坏但可能更糟”的情况似乎不适用于你的例子——你的代码可以通过修改应用层代码来更好地架构,比如调用你的包装函数(比如用myos.chown代替直接用os.chown),并把你的包装函数放在你自己的中间模块(比如myown)中,这样就能在应用层代码和标准库之间架起一座桥(或者是你要包装的第三方扩展——在这方面,标准库并没有什么特别之处)。

有一种麻烦的情况是,当“应用层代码”并不在你的控制之下——它是一个你不想修改的第三方子系统。尽管如此,我发现,在这种情况下,修改第三方子系统以调用包装函数(而不是直接调用标准库函数)从长远来看要更有效率——然后你当然可以把这个改动提交给第三方子系统的维护者,他们会把你的改动放进下一个版本中,这样对每个人来说生活都会变得更好(包括你,因为一旦你的改动被接受,其他人会定期维护和测试这些改动!)。

顺便提一下,这种包装函数也可以作为差异(diff)提交给标准库,但那是另一回事,因为标准库的发展非常缓慢而谨慎,特别是在Python 2的版本中,2.7是最后一个版本,之后不会再有新特性了)。

当然,以上所有内容都假设你处在一个开源文化中。如果因为某些神秘的原因你在使用一个闭源的第三方子系统,也就是说你无法维护它,那么你就处于另一种情况,在这种情况下,猴子补丁可能是较小的恶(但这只是因为失去对你开发的战略控制,去信任你无法维护的代码本身就是一个更大的恶)。我从来没有遇到过同时是闭源和用Python编写的第三方包的情况(如果后者不成立,你的猴子补丁就没什么用处了)。

需要注意的是,这里对“闭源”的定义非常严格:例如,早在12年前,微软就把像MFC这样的库的源代码与Visual C++一起发布(当时他们的产品就是这样称呼的)——虽然是闭源,因为你不能重新分发他们的源代码,但你确实有源代码在手,所以当你遇到一些可怕的限制或bug时,你可以修复它(并把改动提交给他们以便未来的版本,同时只要你的改动中绝对不包含他们的版权代码,就可以发布你的改动作为差异——这并不简单,但可行)。

在动态语言的用户中,超出这种方法“坏中之坏”的严格界限是一个常见的错误——要小心不要掉进这个陷阱!

撰写回答