10

Abstract base classes can still be handy in Python. In writing an abstract base class where I want every subclass to have, say, a spam() method, I want to write something like this:

class Abstract(object): def spam(self): raise NotImplementedError 

The challenge comes in also wanting to use super(), and to do it properly by including it in the entire chain of subclasses. In this case, it seems I have to wrap every super call like the following:

class Useful(Abstract): def spam(self): try: super(Useful, self).spam() except NotImplementedError, e: pass print("It's okay.") 

That's okay for a simple subclass, but when writing a class that has many methods, the try-except thing gets a bit cumbersome, and a bit ugly. Is there a more elegant way of subclassing from abstract base classes? Am I just Doing It Wrong?

3
  • 5
    That makes very little sense. You should know which superclass methods are implemented (for which super makes sense) and which are not implemented because they're abstract. You can read the source. Commented Jan 25, 2011 at 22:09
  • raise SyntaxError is also in the language. The question is "why write all that code when simple inspection of the abstract class can save you writing all that code"? Commented Jan 25, 2011 at 22:15
  • @S.Lott Ah, understood now. You should submit that as an answer, by the way, because it is. Commented Jan 25, 2011 at 22:16

3 Answers 3

11

You can do this cleanly in python 2.6+ with the abc module:

import abc class B(object): __metaclass__ = abc.ABCMeta @abc.abstractmethod def foo(self): print 'In B' class C(B): def foo(self): super(C, self).foo() print 'In C' C().foo() 

The output will be

In B In C 
Sign up to request clarification or add additional context in comments.

5 Comments

This is not necessarily the same - one can't instantiate B from your example.
Interesting! I hadn't even caught the addition of the abc module!
@Tomasz Also interesting. What are the implications for unit testing, then, if one can't instantiate the class?
If you change the body of B.foo to be raise NotImplementedError it doesn't skirt around the issue. It would be still worthwhile to notify the programmer, "Hey, dummy, you forgot to override this abstract method!" I'm not sure at the moment how abc really solves that problem.
Ah! I was Doing It Wrong; you define def foo(): pass in B, and then you can not instantiate any subclasses until you override B.foo in some subclass. Basically, until you override all the undefined methods, you will be unable to instantiate that subclass. Very neat!
8

Do not write all that code. Simple inspection of the abstract class can save you writing all that code.

If the method is abstract, the concrete subclass does not call super.

If the method is concrete, the concrete subclass does call super.

1 Comment

That is a simple solution unless you are going for a different sort of cooperative inheritance. ie. Abstract is A. A <- B, but then you want to support insertion of C like so A <- C <- B. B does not call super so it does not work properly. I think the main point is that when you use super you should be aware of cooperative inheritance and your goals for your specific class hierarchy. See my answer for implementation of my example above.
5

The key point to understand this is super() is for implementing cooperative inheritance. How the classes cooperate is up to you the programmer. super() is not magic and does not know exactly what you want! There is not much point in using super for a flat hierarchy that doesn't need cooperative inheritance, so in that case S. Lott's suggestion is spot on. Subclasses of Useful may or may not want to use super() depending their goals :)

For example: Abstract is A. A <- B, but then you want to support insertion of C like so A <- C <- B.

class A(object): """I am an abstract abstraction :)""" def foo(self): raise NotImplementedError('I need to be implemented!') class B(A): """I want to implement A""" def foo(self): print('B: foo') # MRO Stops here, unless super is not A position = self.__class__.__mro__.index if not position(B) + 1 == position(A): super().foo() b = B() b.foo() class C(A): """I want to modify B and all its siblings (see below)""" def foo(self): print('C: foo') # MRO Stops here, unless super is not A position = self.__class__.__mro__.index if not position(C) + 1 == position(A): super().foo() print('') print('B: Old __base__ and __mro__:\n') print('Base:', B.__bases__) print('MRO:', B.__mro__) print('') # __mro__ change implementation B.__bases__ = (C,) print('B: New __base__ and __mro__:\n') print('Base:', B.__bases__) print('MRO:', B.__mro__) print('') b.foo() 

And the output:

B: foo B: Old __base__ and __mro__: Base: (<class '__main__.A'>,) MRO: (<class '__main__.B'>, <class '__main__.A'>, <class 'object'>) B: New __base__ and __mro__: Base: (<class '__main__.C'>,) MRO: (<class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>) B: foo C: foo 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.