I agree with TooAngel, but I'd use the __new__ method.
class Shape(object):
def __new__(cls, *args, **kwargs):
if cls is Shape: # <-- required because Line's
description, args = args[0], args[1:] # __new__ method is the
if description == "It's flat": # same as Shape's
new_cls = Line
else:
raise ValueError("Invalid description: {}.".format(description))
else:
new_cls = cls
return super(Shape, cls).__new__(new_cls, *args, **kwargs)
def number_of_edges(self):
return "A shape can have many edges…"
class Line(Shape):
def number_of_edges(self):
return 1
class SomeShape(Shape):
pass
>>> l1 = Shape("It's flat")
>>> l1.number_of_edges()
1
>>> l2 = Line()
>>> l2.number_of_edges()
1
>>> u = SomeShape()
>>> u.number_of_edges()
'A shape can have many edges…'
>>> s = Shape("Hexagon")
ValueError: Invalid description: Hexagon.
Answer from Georg Schölly on Stack OverflowI agree with TooAngel, but I'd use the __new__ method.
class Shape(object):
def __new__(cls, *args, **kwargs):
if cls is Shape: # <-- required because Line's
description, args = args[0], args[1:] # __new__ method is the
if description == "It's flat": # same as Shape's
new_cls = Line
else:
raise ValueError("Invalid description: {}.".format(description))
else:
new_cls = cls
return super(Shape, cls).__new__(new_cls, *args, **kwargs)
def number_of_edges(self):
return "A shape can have many edges…"
class Line(Shape):
def number_of_edges(self):
return 1
class SomeShape(Shape):
pass
>>> l1 = Shape("It's flat")
>>> l1.number_of_edges()
1
>>> l2 = Line()
>>> l2.number_of_edges()
1
>>> u = SomeShape()
>>> u.number_of_edges()
'A shape can have many edges…'
>>> s = Shape("Hexagon")
ValueError: Invalid description: Hexagon.
I would prefer doing it with a factory:
def factory(description):
if description == "It's flat": return Line(description)
elif description == "It's spiky": return Triangle(description)
elif description == "It's big": return Rectangle(description)
or:
def factory(description):
classDict = {"It's flat":Line("It's flat"), "It's spiky":Triangle("It's spiky"), "It's big":Rectangle("It's big")}
return classDict[description]
and inherit the classes from Shape
class Line(Shape):
def __init__(self, description):
self.desc = description
def number_of_edges(self, parameters):
return 1
Videos
Sure, and you don't even have to define a method in the base class. In Python methods are better than virtual - they're completely dynamic, as the typing in Python is duck typing.
class Dog:
def say(self):
print "hau"
class Cat:
def say(self):
print "meow"
pet = Dog()
pet.say() # prints "hau"
another_pet = Cat()
another_pet.say() # prints "meow"
my_pets = [pet, another_pet]
for a_pet in my_pets:
a_pet.say()
Cat and Dog in Python don't even have to derive from a common base class to allow this behavior - you gain it for free. That said, some programmers prefer to define their class hierarchies in a more rigid way to document it better and impose some strictness of typing. This is also possible - see for example the abc standard module.
Summary of current implementation status with a focus on "blowup nicely if the method is not implemented" behavior:
| methd | exception | mypy static | sphinx |
|---|---|---|---|
raise NotImplementedError() |
on method call | n | n |
typing.Protocol |
n | y | n |
@abc.abstractmethod |
on instantiation | y | y |
raise NotImplementedError(): dynamic type checking
This is the recommended exception to raise on "pure virtual methods" of "abstract" base classes that don't implement a method.
https://docs.python.org/3.5/library/exceptions.html#NotImplementedError says:
This exception is derived from
RuntimeError. In user defined base classes, abstract methods should raise this exception when they require derived classes to override the method.
As others said, this is mostly a documentation convention and is not required, but this way you get a more meaningful exception than a missing attribute error.
dynamic.py
class Base(object):
def virtualMethod(self):
raise NotImplementedError()
def usesVirtualMethod(self):
return self.virtualMethod() + 1
class Derived(Base):
def virtualMethod(self):
return 1
assert Derived().usesVirtualMethod() == 2
Base().usesVirtualMethod()
gives:
Traceback (most recent call last):
File "./dynamic.py", line 13, in <module>
Base().usesVirtualMethod()
File "./dynamic.py", line 6, in usesVirtualMethod
return self.virtualMethod() + 1
File "./dynamic.py", line 4, in virtualMethod
raise NotImplementedError()
NotImplementedError
typing.Protocol: static type checking (Python 3.8)
Python 3.8 added typing.Protocol which now allows us to also statically type check that a virtual method is implemented on a subclass.
protocol.py
from typing import Protocol
class Bird(Protocol):
def fly(self) -> str:
pass
def peck(self) -> str:
return 'Bird.peck'
class Pigeon(Bird):
def fly(self):
return 'Pigeon.fly'
def peck(self):
return 'Pigeon.peck'
class Parrot(Bird):
def fly(self):
return 'Parrot.fly'
class Dog(Bird):
pass
pigeon = Pigeon()
assert pigeon.fly() == 'Pigeon.fly'
assert pigeon.peck() == 'Pigeon.peck'
parrot = Parrot()
assert parrot.fly() == 'Parrot.fly'
assert parrot.peck() == 'Bird.peck'
# mypy error
dog = Dog()
assert dog.fly() is None
assert dog.peck() == 'Bird.peck'
If we run this file, the asserts pass, as we didn't add any dynamic typechecking:
python protocol.py
but if we typecheck if mypy:
python -m pip install --user mypy
mypy protocol.py
we get an error as expected:
rotocol.py:31: error: Cannot instantiate abstract class "Dog" with abstract attribute "fly" [abstract]
Found 1 error in 1 file (checked 1 source file)
It is a bit unfortunate however that the error checking only picks up the error on instantiation, and not at class definition.
typing.Protocol counts methods as abstract when their body is "empty"
I'm not sure what they count as empty, but both all of the following count as empty:
pass...ellipsis objectraise NotImplementedError()
So the best possibility is likely:
protocol_empty.py
from typing import Protocol
class Bird(Protocol):
def fly(self) -> None:
raise NotImplementedError()
class Pigeon(Bird):
def fly(self):
return None
class Dog(Bird):
pass
Bird().fly()
Dog().fly()
which fails as desired:
protocol_empty.py:14: error: Cannot instantiate protocol class "Bird" [misc]
protocol_empty.py:15: error: Cannot instantiate abstract class "Dog" with abstract attribute "fly" [abstract]
protocol_empty.py:15: note: "fly" is implicitly abstract because it has an empty function body. If it is not meant to be abstract, explicitly `return` or `return None`.
Found 2 errors in 1 file (checked 1 source file)
but if e.g. we replace the:
raise NotImplementedError()
with some random "non-empty" statement such as:
x = 1
then mypy does not count them as virtual and gives no errors.
@abc.abstractmethod: dynamic + static + documentation in one go
Previously mentioned at: https://stackoverflow.com/a/19316077/895245 but the metaclass syntax changed in Python 3 to:
class C(metaclass=abc.ABCMeta):
instead of the Python 2:
class C:
__metaclass__=abc.ABCMeta
so now to use @abc.abstractmethod which was previously mentioned at https://stackoverflow.com/a/19316077/895245 you need:
abc_cheat.py
import abc
class C(metaclass=abc.ABCMeta):
@abc.abstractmethod
def m(self, i):
pass
try:
c = C()
except TypeError:
pass
else:
assert False
Vs raise NotImplementedError and Protocol:
- disadvantage: more verbose
- advantage: does all of dynamic checks, static checks and shows up on documentation (see below)
https://peps.python.org/pep-0544 does mention both approaches in passing
E.g.:
abc_bad.py
#!/usr/bin/env python
import abc
class CanFly(metaclass=abc.ABCMeta):
'''
doc
'''
@abc.abstractmethod
def fly(self) -> str:
'''
doc
'''
pass
class Bird(CanFly):
'''
doc
'''
def fly(self):
'''
doc
'''
return 'Bird.fly'
class Dog(CanFly):
'''
doc
'''
pass
def send_mail(flyer: CanFly) -> str:
'''
doc
'''
return flyer.fly()
assert send_mail(Bird()) == 'Bird.fly'
assert send_mail(Dog()) == 'Dog.fly'
then:
mypy abc_bad.py
fails as desired with:
main.py:40: error: Cannot instantiate abstract class "Dog" with abstract attribute "fly"
Sphinx: make it show on the documentation
See: How to annotate a member as abstract in Sphinx documentation?
Of the methods mentioned above, only one shows up on the sphinx documentation output: @abc.abstractmethod.

Outro
Bibiography:
- https://peps.python.org/pep-0544 the
typing.ProtocolPEP - Is it possible to make abstract classes in Python?
- What to use in replacement of an interface/protocol in python
Tested on Python 3.10.7, mypy 0.982, Ubuntu 21.10.
Currently I'm reading the book 'Fluent Python' and I don't quite get the concept of virtual subclasses. Can anyone give me a simple example using this kind of syntax:
@Tombola.register class TomboList(list): ...
Looking to learn python, but I'm definitely someone who functions better in a structured (almost classroom-like) environment over self-paced learning. Any recommendations for live virtual classes to learn Python?
Abstract base classes already do what you want. abstractmethod has nothing to do with letting you call the method with super; you can do that anyway. Instead, any methods decorated with abstractmethod must be overridden for a subclass to be instantiable:
Python 3:
>>> class Foo(metaclass=abc.ABCMeta):
... @abc.abstractmethod
... def foo(self):
... pass
...
>>> class Bar(Foo):
... pass
...
>>> class Baz(Bar):
... def foo(self):
... return super(Baz, self).foo()
...
>>> Foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Foo with abstract methods foo
>>> Bar()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Bar with abstract methods foo
>>> Baz()
<__main__.Baz object at 0x00000210D702E2B0>
Python 2:
>>> class Foo(object):
... __metaclass__ = abc.ABCMeta
... @abc.abstractmethod
... def foo(self): pass
...
>>> class Bar(Foo): pass
...
>>> class Baz(Bar):
... def foo(self): return super(Baz, self).foo()
...
>>> Foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Foo with abstract methods foo
>>> Bar()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Bar with abstract methods foo
>>> Baz()
<__main__.Baz object at 0x0000000001EC10B8>
Problem is that this error is found at runtime!
Well, it is Python... most errors are going to show up at runtime.
As far as I know, the most common pattern to deal with is in Python is basically what you describe: just have the base class's speak method throw an exception:
class Animal():
def speak(self):
raise NotImplementedError('You need to define a speak method!')