【问题标题】:Python 2.6, 3 abstract base class misunderstandingPython 2.6、3抽象基类误区
【发布时间】:2011-02-21 04:09:48
【问题描述】:

当我使用 ABCMeta 和 abstractmethod 时,我没有看到我期望的结果。

这在 python3 中运行良好:

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,除了 ABCMeta,它们都可以正常工作(我得到预期的异常)。

如果我从列表中派生 Super,它们都会“失败”(不引发异常)。

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

我做错了吗,还是我不应该在 python 中这样做?

【问题讨论】:

  • 你到底想用这个实现什么?一个具体的例子在这里真的会有很长的路要走。
  • 抽象基类是中毒心灵的需要 o_O stackoverflow.com/questions/372042/…
  • musicfreak:我正在尝试实现一个基于列表的抽象基类,它为其派生类定义了一个接口,并且不能实例化哪个基类。我希望能够将派生类用作列表,因为这是我的对象的本质,还有额外的接口位,因为我的对象更加特别。 :) msw:为什么需要 deiderata (“被毒化的头脑所渴望的东西”?我猜你是说我不应该想要这个(在 python 中),但是为什么呢?什么是更 Pythonic 的机制?
  • @msv,ABC 现在是 Python 2.6 及更高版本的重要组成部分——交易。 @Aaron,你没有做错任何事,只是没有按照你想要的方式工作——让我写一个答案来解释一下。
  • @Aaron,我是在夸张,但除了我提到的 SO 链接中的一些晦涩的情况外,有些人认为 Python 需要抽象类,就像鱼需要自行车一样。一个什么都不做的类在我看来不是一个类,它是一个接口定义。甚至在 Python 中包含 ABC 的理由也有些模棱两可:python.org/dev/peps/pep-3119

标签: python abstract-class


【解决方案1】:

使用 Super 在您的工作 sn-ps 中构建,您在执行 Super() 时调用的是:

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

如果Super继承自list,则称它为Superlist

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

现在,抽象基类可以用作 mixin 类,可以从多个继承(以获得 ABC 可能提供的“模板方法”设计模式特性)与具体的类,而不使产生的后代抽象。所以考虑:

>>> 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 的抽象方法.不完美,但也不是那么痛苦。

【讨论】:

  • (我认为我的评论搞砸了。)亚历克斯,谢谢,这很清楚,很中肯。我现在有一些阅读要做。
【解决方案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) 工作正常,而您将获得通常的 TypeErrorC1.__new__(C1)

所以,恕我直言,它不像

那么清晰

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

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

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

OrderedDict.bar = lambda self: 42

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

【讨论】:

    猜你喜欢
    • 2016-07-05
    • 2018-11-06
    • 1970-01-01
    • 2018-07-11
    • 2020-04-03
    • 2011-06-15
    • 2012-04-03
    • 2013-09-24
    • 1970-01-01
    相关资源
    最近更新 更多