Python 2.6, 3 base class misunderstanding

I do not see 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 work fine (I get the expected exception) if I get Super from the object, in addition to ABCMeta.

Both of them “fail” (no exception) if I get Super from the list.

I want the abstract base class to be a list, but abstract and concrete in subclasses.

Am I doing it wrong, or don't I need it in python?

+7
python abstract-class
source share
2 answers

With Super create, as in your working snippets, what you call when you do Super() :

 >>> 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 the abstract base classes are intended to be used as mixin classes that should be inherited from (to get the template functions of the Template template that ABC can offer) together with a specific class without creating a resulting descendant. Therefore, consider:

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

See the problem? According to the rules of multiple inheritance, calling Listsuper() (which is not allowed to fail just because a dangling abstract method exists) works with the same code as calling Superlist() (which you want to fail), This code is in practice ( list.__init__ ) does not an object for dangling abstract methods - only object.__init__ does. And a fix that is likely to break code that relies on current behavior.

Suggested workaround: if you want an abstract base class, all its bases must be abstract. So, instead of having a specific list among your databases, use collections.MutableSequence as the base, add __init__ , which makes the ._list attribute, and implement the abstract MutableSequence methods by direct delegation to self._list . Not perfect, but not everything that hurts.

+14
source share

Actually the problem is related to __new__ , not __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() does not work with TypeError as expected, and C2.foo() returns 42 .

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

As you can see, it does not use object.__init__ , and does not even call its superclass ( object ) __init__

You can check it by calling __new__ yourself:

C2.__new__(C2) works just fine, while you get a regular TypeError with C1.__new__(C1)

So imho it's not as clear as

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

While this is a good suggestion, the opposite is not necessarily true: neither OrderedDict nor Empty are abstract, and yet the first subclass is "concrete" and the last is "abstract"

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

 OrderedDict.bar = lambda self: 42 

And I wanted to make it clear that the problem is not related to it.

+1
source share

All Articles