python 2.6,3抽象基类误解

Python 2.6, 3 abstract base class misunderstanding

当我使用abcmeta和abstractmethod时,我看不到我所期望的。

在python3中效果很好:

1
2
3
4
5
6
7
8
9
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:

1
2
3
4
5
6
7
8
class Super():
    __metaclass__ = ABCMeta
    @abstractmethod
    def method(self):
         pass

a = Super()
TypeError: Can't instantiate abstract class Super ...

如果我从对象派生super,除了abcmeta之外,它们都可以正常工作(我得到预期的异常)。

如果我从列表中派生super,它们都会"失败"(没有例外)。

我希望一个抽象的基类是一个列表,但抽象的、具体的子类。

是我做错了,还是我不想在python中这样做?


使用Super构建,就像在您的工作片段中一样,您在执行Super()时所调用的是:

1
2
>>> Super.__init__
<slot wrapper '__init__' of 'object' objects>

如果Super继承list的,称之为Superlist

1
2
>>> Superlist.__init__
<slot wrapper '__init__' of 'list' objects>

现在,抽象基类被认为是可以作为混合类使用的,可以与一个具体的类一起进行乘法继承(以获得ABC可能提供的"模板方法"设计模式特性),而不必使生成的后代成为抽象的。所以考虑一下:

1
2
3
4
>>> 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的抽象方法。不完美,但也没有那么痛苦。


实际上,问题出在__new__上,而不是__init__上。例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

1
2
>>> C1.__init__
<function Empty.__init__ at 0x7fa9a6c01400>

如你所见,它没有使用object.__init__,甚至没有调用它的超类(object__init__

你可以自己打电话给__new__来核实:

C2.__new__(C2)工作得很好,而你会得到通常的TypeErrorC1.__new__(C1)一起使用。

所以,天哪,它不像

if you want an abstract base class, all its bases must be abstract.

虽然这是一个很好的建议,但反过来也不一定是正确的:OrderedDictEmpty都不是抽象的,但前者的子类是"具体的",后者是"抽象的"。

如果您想知道,我在示例中使用了OrderedDict,而不是list,因为后者是"内置"类型,因此您不能这样做:

1
OrderedDict.bar = lambda self: 42

我想明确指出这个问题与此无关。