假设我写了一个元类M
,用户写了我的元类的实例A
,它覆盖了方法g
:
>>> class M(type):
... def f(self): return self.g()
... def g(self): return 'foo'
...
>>> class A(metaclass=M):
... def g(self): return 'bar'
...
>>> A.f() # oops!
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in f
TypeError: g() missing 1 required positional argument: 'self'
>>> del A.g
>>> A.f()
'foo'
或者假设我编写了一个类A
,用户编写了我的类的实例a
,它覆盖了方法g
:
>>> class A:
... def f(self): return self.g()
... def g(self): return 'foo'
...
>>> a = A()
>>> a.g = lambda self: 'bar'
>>> a.f() # oops!
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in f
TypeError: <lambda>() missing 1 required positional argument: 'self'
>>> del a.g
>>> a.f()
'foo'
在实例中重写类的方法是不是一种糟糕的做法?
这里唯一有效的答案与医生在你问自己反复戳眼睛时感到的疼痛该怎么办时给出的答案相同:如果疼痛,就停止这样做。你的眼睛并不是为了让手指戳进去而设计的。
因此,是,当一个方法违反了调用它的现有代码的期望时,将其添加到实例或类中是不好的做法。
请注意,这实际上与示例的具体内容无关任何现有代码都可以通过这种方式破坏,方法是用不符合现有期望的实现替换或隐藏其他代码。
你的例子可能是琐碎的固定;要替换类上的M.g
方法,请使用类方法。要替换实例上的A.g
,请使用不需要self
的函数。两者都符合现有代码的期望:调用g
不需要额外的参数。
Python是高度动态的,这给了你大量的自由,包括戳自己眼睛的自由。尽量不要做最后一件事或任何其他可能伤害代码的事情,这样你就应该没事了。
猴子补丁作为最后手段的罕见情况往往是您无法访问源代码来实现正确的解决方案,或者您无法定义希望修复或扩展的类的子类。
在大多数其他情况下,这是一种糟糕的做法,而且往往是代码库的最后手段,因为它已经承担了太多糟糕的设计决策,而不是试图修复你没有代码的第三方解决方案。