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 的抽象方法。虽然这不是完美的,但也不是特别痛苦。