Python 2.6 和 3 抽象基类误解

9 投票
2 回答
3780 浏览
提问于 2025-04-15 22:52

我在使用 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 个回答

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

所以,我认为这并不是那么简单明了:

如果你想要一个抽象基类,它的所有基类也必须是抽象的。

虽然这是个不错的建议,但反过来说并不一定成立:OrderedDictEmpty 都不是抽象的,但前者的子类是“具体”的,而后者却是“抽象”的。

如果你在想,我在例子中用 OrderedDict 而不是 list,是因为后者是一个“内置”的类型,因此你不能这样做:

OrderedDict.bar = lambda self: 42

我想明确说明这个问题与它无关。

15

在你的代码片段中,使用 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 的抽象方法。虽然这不是完美的,但也不是特别痛苦。

撰写回答