Python 2.6 和 3 抽象基类误解
我在使用 ABCMeta 和 abstractmethod 时,看到的结果跟我预期的不一样。
在 Python 3 中,这样写是没问题的:
from abc import ABCMeta, abstractmethod
class Super(metaclass=ABCMeta):
@abstractmethod
def method(self):
pass
a = Super()
TypeError: Can't instantiate abstract class Super ...
在 2.6 版本中也是这样:
class Super():
__metaclass__ = ABCMeta
@abstractmethod
def method(self):
pass
a = Super()
TypeError: Can't instantiate abstract class Super ...
如果我让 Super 继承自 object,同时也继承 ABCMeta,这样做也能正常工作(我能得到预期的异常)。
但是如果我让 Super 继承自 list,就会“失败”(没有抛出异常)。
我想要一个抽象基类,它是一个列表,但又是抽象的,子类才是具体的实现。
我这样做是错了吗?还是说在 Python 中我不应该有这样的需求?
2 个回答
其实,问题出在 __new__
上,而不是 __init__
。举个例子:
from abc import ABCMeta, abstractmethod
from collections import OrderedDict
class Foo(metaclass=ABCMeta):
@abstractmethod
def foo(self):
return 42
class Empty:
def __init__(self):
pass
class C1(Empty, Foo): pass
class C2(OrderedDict, Foo): pass
C1()
会因为预期中的 TypeError
而失败,而 C2.foo()
则返回 42
。
>>> C1.__init__
<function Empty.__init__ at 0x7fa9a6c01400>
你可以看到,它既没有使用 object.__init__
,也没有调用它的父类(object
)的 __init__
。
你可以通过自己调用 __new__
来验证这一点:
C2.__new__(C2)
可以正常工作,而用 C1.__new__(C1)
则会得到通常的 TypeError
。
所以,我认为这并不是那么简单明了:
如果你想要一个抽象基类,它的所有基类也必须是抽象的。
虽然这是个不错的建议,但反过来说并不一定成立:OrderedDict
和 Empty
都不是抽象的,但前者的子类是“具体”的,而后者却是“抽象”的。
如果你在想,我在例子中用 OrderedDict
而不是 list
,是因为后者是一个“内置”的类型,因此你不能这样做:
OrderedDict.bar = lambda self: 42
我想明确说明这个问题与它无关。
在你的代码片段中,使用 Super
这个构建时,当你调用 Super()
时,其实是在做以下操作:
>>> Super.__init__
<slot wrapper '__init__' of 'object' objects>
如果 Super
是从 list
继承而来的,那就叫它 Superlist
:
>>> Superlist.__init__
<slot wrapper '__init__' of 'list' objects>
现在,抽象基类的目的是可以作为混合类使用,可以和具体类一起多重继承,以获得“模板方法”设计模式的特性,而不让最终的子类变成抽象类。所以考虑一下:
>>> class Listsuper(Super, list): pass
...
>>> Listsuper.__init__
<slot wrapper '__init__' of 'list' objects>
你看到了问题吗?根据多重继承的规则,调用 Listsuper()
(这不允许因为有未实现的抽象方法而失败)和调用 Superlist()
(你希望它失败)实际上运行的是同一段代码。那段代码,实际上是 list.__init__
,对未实现的抽象方法并不有异议——只有 object.__init__
会有。修复这个问题可能会破坏依赖于当前行为的代码。
建议的解决办法是:如果你想要一个抽象基类,所有的基类都必须是抽象的。所以,不要在基类中使用具体的 list
,而是用 collections.MutableSequence
作为基类,添加一个 __init__
方法来创建一个 ._list
属性,并通过直接委托给 self._list
来实现 MutableSequence
的抽象方法。虽然这不是完美的,但也不是特别痛苦。