9

I'm not seeing what I expect when I use ABCMeta and abstractmethod.

This works fine in python3:

from abc import ABCMeta, abstractmethod

class Super(metaclass=ABCMeta):
    @abstractmethod
    def method(self):
        pass

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

And in 2.6:

class Super():
    __metaclass__ = ABCMeta
    @abstractmethod
    def method(self):
         pass

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

They both also work fine (I get the expected exception) if I derive Super from object, in addition to ABCMeta.

They both "fail" (no exception raised) if I derive Super from list.

I want an abstract base class to be a list but abstract, and concrete in sub classes.

Am I doing it wrong, or should I not want this in python?

Aaron
  • 295
  • 4
  • 13
  • 1
    What exactly are you trying to achieve with this? A concrete example would really go a long way here. – Sasha Chedygov May 19 '10 at 01:19
  • abstract base classes are the desiderata of a poisoned mind o_O http://stackoverflow.com/questions/372042/difference-between-abstract-class-and-interface-in-python – msw May 19 '10 at 01:27
  • musicfreak: I'm trying to achieve a list-based abstract base class that defines an interface for its derived classes, and which base class cannot be instantiated. I want to be able to use derived classes as a list because that's the nature of my objects, with additional bits of interface because my objects are that much more special. :) msw: Why desiderata ("something desired as essential" of a poisoned mind? I'm guessing you're saying I shouldn't want this (in python), but why? What's a more pythonic mechanism? – Aaron May 19 '10 at 01:45
  • 1
    @msv, ABCs are now very much a part of Python, 2.6 and beyond -- deal. @Aaron, you're doing nothing wrong, it just doesn't work the way you desire -- let me write an answer to explain. – Alex Martelli May 19 '10 at 01:50
  • @Aaron, I was being hyperbolic, but with the exception of some obscure cases in the SO link I referred to, there are some who feel that Python needs abstract classes like a fish needs a bicycle. A class that does nothing isn't a class in my mind, it is an interface definition. Even the rationale for including ABCs in Python is kind of equivocal: http://www.python.org/dev/peps/pep-3119/ – msw May 19 '10 at 01:52
  • @msw, I read the link you supplied before I posted this question. It is a philosophical discussion as to whether python needs or should have abstract base classes, which is a worthy discussion but not what I'm after. Like them or not, I'm interested in the mechanics of why my example does not raise an exception if you multiply-derive from ABCMeta and list, but it does raise the expected exception if you only derive from ABCMeta, or ABCMeta and object. – Aaron May 19 '10 at 01:59
  • @Alex - I am sorry to have sullied your website, deal. @Aaron, let me see if I can make my off-hand remark explicit. The first comment was asking what you were trying to accomplish, it appears that what you are trying to accomplish is to understand how ABCs work in Python. In my admittedly limited experience with Python, I don't see the value of ABCs relative to other languages. You are right, you want to know why your test code doesn't work, that's a fair question. My quip was a poor way to state that approaching Python with habits from other languages may bring unneeded baggage. – msw May 19 '10 at 02:12
  • @msw, Python is what Guido says it is, and the core, crucial role that ABCs play in 2.6 and later releases has been very much **his** decision: it's not a democracy, he's the Benevolent Dictator For Life. Don't like it? Move to a language designed by committee, there's plenty -- have a good life. "approaching Python with habits from other languages may bring unneeded baggage": sure, but accusing _Guido_ of **that** is beyond ridiculous -- how can you **possibly** believe that GvR, after 20 years with Python, suddenly decided ABCs were a great idea because of "habits from other languages"?! – Alex Martelli May 19 '10 at 02:33
  • Without proper interfaces and abstract classes I have a mental block when it comes to designing anything serious. Code duplication is evil, and this is what Python suggests I do instead. Or am I missing a big piece of a puzzle here? I'd like to see a serious big clean project written without abstract classes and interfaces. – Hamish Grubijan May 19 '10 at 02:45
  • @Alex, so do you dispute that the PEP 3119 is equivocal? If not, your fanboi posturing effort would be better placed in reading what I actually wrote. In particular you may notice that I made no such claims as you impute to me, and I don't like you using my website to grandstand about a strawman of your own imagining. – msw May 19 '10 at 03:03
  • @msw, I definitely disagree with your criticism of Pep 3119. Your claim was, and I quote, "abstract base classes are the desiderata of a poisoned mind", and they were and are GvR's -- and then you "clarified" this means "approaching Python with habits from other languages may bring unneeded baggage", and, since we're talking about ABCs, and given that GvR enthusiastically accepted them into Python 2.6+, the connection is obvious. "**Your**" website?! My, I've seen megalomaniacs, but _this_ claim of ownership is crazier than any I'd personally met yet. Knock it off, Matt! – Alex Martelli May 19 '10 at 03:34

2 Answers2

15

With Super build as in your working snippets, what you're calling when you do Super() is:

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

If Super inherits from list, call it Superlist:

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

Now, abstract base classes are meant to be usable as mixin classes, to be multiply inherited from (to gain the "Template Method" design pattern features that an ABC may offer) together with a concrete class, without making the resulting descendant abstract. So consider:

>>> class Listsuper(Super, list): pass
... 
>>> Listsuper.__init__
<slot wrapper '__init__' of 'list' objects>

See the problem? By the rules of multiple inheritance calling Listsuper() (which is not allowed to fail just because there's a dangling abstract method) runs the same code as calling Superlist() (which you'd like to fail). That code, in practice (list.__init__), does not object to dangling abstract methods -- only object.__init__ does. And fixing that would probably break code that relies on the current behavior.

The suggested workaround is: if you want an abstract base class, all its bases must be abstract. So, instead of having concrete list among your bases, use as a base collections.MutableSequence, add an __init__ that makes a ._list attribute, and implement MutableSequence's abstract methods by direct delegation to self._list. Not perfect, but not all that painful either.

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • (I botched my comment I think.) Alex, thanks, this was clear and to the point. I have some reading to do now. – Aaron May 19 '10 at 05:15
2

Actually, the issue is with __new__, rather than with __init__. Example:

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() fails with a TypeError as expected, while C2.foo() returns 42.

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

As you can see, it's not using object.__init__ nor is it even invoking its superclass (object) __init__

You can verify it by calling __new__ yourself:

C2.__new__(C2) works just fine, while you'll get the usual TypeError with C1.__new__(C1)

So, imho it's not as clear cut as

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

While that's a good suggestion, the converse it's not necessarily true: neither OrderedDict nor Empty are abstract, and yet the former's subclass is "concrete", while the latter is "abstract"

If you're wondering, I used OrderedDict in the example instead of list because the latter is a "built-in" type, and thus you cannot do:

OrderedDict.bar = lambda self: 42

And I wanted to make it explicit that the issue is not related to it.

berdario
  • 1,851
  • 18
  • 29