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中这样做?
- 你到底想用这个达到什么目的?一个具体的例子在这里会有很大的帮助。
- 抽象基类是中毒思想的desiderata o_o stackoverflow.com/questions/372042/…
- MusicFreak:我正在尝试实现一个基于列表的抽象基类,它为派生类定义接口,并且不能实例化哪个基类。我希望能够使用派生类作为列表,因为这是我的对象的本质,因为我的对象非常特殊,所以有了额外的接口位。:)MSW:为什么要对一个被毒害的心灵进行"必要的东西"?我猜你是说我不应该(用python)这个,但是为什么?什么是更Python的机制?
- @MSV、ABC现在在很大程度上是Python的一部分,2.6及更高版本——Deal。@亚伦,你没有做错什么,只是做不到你想要的方式——让我写一个答案来解释。
- @Aaron,我是双曲线的,但是除了我提到的so链接中一些不明显的情况外,还有一些人认为python需要抽象类,比如fish需要自行车。一个什么都不做的类在我看来不是一个类,它是一个接口定义。甚至在python中包含abc的基本原理也是模棱两可的:python.org/dev/peps/pep-3119
- @MSW,我在发布这个问题之前阅读了你提供的链接。这是一个关于Python是否需要或应该拥有抽象基类的哲学讨论,这是一个有价值的讨论,但不是我想要的。不管你喜欢与否,我对我的例子中的机制很感兴趣,如果你用abcmeta和list相乘,为什么不产生一个异常,但是如果你只从abcmeta或者abcmeta和object派生,它确实会产生预期的异常。
- @亚历克斯-很抱歉弄脏了你的网站,成交。@亚伦,让我看看我能不能把我的即兴讲话说得清楚。第一条评论是询问您试图完成什么,似乎您试图完成的是了解ABC如何在Python中工作。在我对Python无可否认的有限经验中,我看不到abc相对于其他语言的价值。你是对的,你想知道为什么你的测试代码不起作用,这是一个公平的问题。我的妙语是一个糟糕的说法,那就是用其他语言的习惯接近Python可能会带来不必要的负担。
- @MSW,python是guido所说的,abcs在2.6版和更高版本中扮演的核心、关键角色是他的决定:这不是民主,他是一个终生仁慈的独裁者。不喜欢吗?换一种由委员会设计的语言,有很多——祝你生活愉快。"用其他语言的习惯接近Python可能会带来不必要的负担:"当然,但是指责guido这一点太荒谬了——你怎么可能相信gvr,在与Python共事20年后,突然决定abcs是一个伟大的想法,因为"来自其他语言的习惯"。!
- 如果没有适当的接口和抽象类,我在设计任何严肃的事情时都会遇到一个心理障碍。代码复制是邪恶的,这正是Python建议我做的。或者我是不是错过了一大块拼图?我希望看到一个没有抽象类和接口的干净的大项目。
- @亚历克斯,那么你是否认为PEP 3119模棱两可?如果没有,你的范博伊故作姿态的努力将更好地放在阅读我实际写的。尤其是你可能会注意到,我没有像你所说的那样对我提出任何要求,我不喜欢你用我的网站来大张旗鼓地谈论你自己想象中的一个流浪汉。
- @MSW,我绝对不同意你对PEP 3119的批评。你的说法是,我引用的是,"抽象的基础类是一个被毒害的思想的终结",它们是和是gvr的——然后你"澄清"这意味着"用其他语言的习惯接近python可能会带来不必要的负担",而且,因为我们谈论的是abc,并且gvr热情地接受它们进入python 2.6+。联系是显而易见的。"你的"网站"?我的,我见过狂妄自大,但这种拥有权的主张比我个人见过的任何人都要疯狂。别这样,马特!
使用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)工作得很好,而你会得到通常的TypeError和C1.__new__(C1)一起使用。
所以,天哪,它不像
if you want an abstract base class, all its bases must be abstract.
虽然这是一个很好的建议,但反过来也不一定是正确的:OrderedDict和Empty都不是抽象的,但前者的子类是"具体的",后者是"抽象的"。
如果您想知道,我在示例中使用了OrderedDict,而不是list,因为后者是"内置"类型,因此您不能这样做:
1
| OrderedDict.bar = lambda self: 42 |
我想明确指出这个问题与此无关。