Implementations in ABCs - python

I'm creating a class hierarchy with a base type that will have 2 (and probably more in the future) subclass implementations.
My first idea is to create an abstract base class (inheriting from abc.ABC) with #abstractmethods where necessary (for methods that will be different in my concrete subclasses) but also with common method implementations (for methods used by all concrete subclasses).
Here's an example of what I mean:
from abc import ABC, abstractmethod
class BaseClass(ABC):
def __init__(self, var1, var2):
self.var1 = var1
self.var2 = var2
def common_method(self):
"""This method is used as-is by all subclasses."""
return self.var1 + 1
#abstractmethod
def specific_method(self):
"""This method needs specific implementations."""
Is this good practice (not "best practice"; I'm looking for whether this is an appropriate use of these constructs) for writing a base class? Is using instance methods in my BaseClass appropriate?

i would say: yes.
There is a design pattern called Template Method which captures what you describe in your question, see https://sourcemaking.com/design_patterns/template_method.

Abstract Base Classes are used to ensure that any derived class provides some specified behavior (https://www.python.org/dev/peps/pep-3119 -> rationale). For example, you want to create custom data manager that must be able to save/load data and must support iteration over elementary pieces of data:
import abc
class SavableLoadable(abc.ABC):
#abc.abstractmethod
def save(self):
raise NotImplementedError
#abc.abstractmethod
def load(self):
raise NotImplementedError
class Iteratable(abc.ABC):
#abc.abstractmethod
def __iter__(self):
raise NotImplementedError
class MyDataManager(Iteratable, SavableLoadable):
def __init__(self):
pass
def __iter__(self):
pass
def save(self):
pass
def load(self):
pass
It is not a class hierarchy. It is a way to define behaivioral layer through inheritance. ABCs are very usefull for large-scale projects where different parts are created by different authors and should be able to run together.
So you do not need ABCs to create class hierarchy. ABCs serve another (although pretty close) purpose.

Related

When and how to check Python subclasses meet specification

I have a base class that looks something like this:
class myBaseClass:
def __init__(self):
self.name = None # All subclasses must define this
def foo(self): # All subclasses must define this
raise NotImplementedError
def bar(self): # Optional -- not all subclasses will define this
raise NotImplementedError
My API specification stipulates that anyone creating a subclass of myBaseClass must provide a meaningful value for .name, and for the function .foo(). However, .bar() is optional and calling code should be able to handle the case where that results in a NotImplementedError.
When and how should I check that subclasses contributed by third parties meet these requirements?
The options seem to be:
Build subclasses exclusively via metaclasses. However, this approach will be unfamiliar and potentially confusing to most of the contributors to my project, who tend not to be expert developers.
Add an __init_subclass__ method to the base class and use this to infer whether the subclass has overridden everything it is supposed to override. Seems to work, but feels a bit 'kludgy'.
Write build-time tests to instantiate each subclass, call each 'required' method, and verify that they do not raise a NotImplementedError. Seems like an excessive computational effort to answer such a simple question (calling .foo() may be expensive).
Ignore the issue. Deal with it if and when it causes something else to break.
I'm sure I'm not the only person who needs to deal with this issue - is there a 'correct' approach here?
Here's how I would structure it.
First off, what you're looking for here is an abstract base class. Using the built-in modules you can easily define it as such and have methods be forced to have an implementation, otherwise the class will raise an exception when instantiated.
If the name attribute needs to be set always, then you should make it part of the constructor arguments.
Because bar is not always required I wouldn't define it as a method in the base class you have. Instead I would make a child class that is also abstract and define it there as required. When checking to see if the method is available you can use isinstance.
This is what my final code would look like:
from abc import ABC, abstractmethod
class FooBaseClass(ABC):
def __init__(self, name):
self.name = name
#abstractmethod
def foo(self):
"""Some useful docs for foo"""
class FooBarBaseClass(FooBaseClass, ABC):
#abstractmethod
def bar(self):
"""Some useful docs for bar"""
When creating instances you can pick the base class you want and will be forced to define the methods.
class FooClass(FooBaseClass):
def __init__(self):
super().__init__("foo")
def foo(self):
print("Calling foo from FooClass")
class FooBarClass(FooBarBaseClass):
def __init__(self):
super().__init__("foobar")
def foo(self):
print("Calling foo from FooBarClass")
def bar(self):
print("Calling bar from FooBarClass")
Example checking if bar is callable:
def do_operation(obj: FooBaseClass):
obj.foo()
if isinstance(obj, FooBarBaseClass):
obj.bar()
Example:
do_operation(FooClass())
do_operation(FooBarClass())
Calling foo from FooClass
Calling foo from FooBarClass
Calling bar from FooBarClass
An example of invalid code
class InvalidClass(FooBaseClass):
def __init__(self):
super().__init__("foo")
InvalidClass()
Traceback (most recent call last):
File "C:\workspace\so\test.py", line 52, in <module>
InvalidClass()
TypeError: Can't instantiate abstract class InvalidClass with abstract method foo

Python: How to create an ABC that inherits from others ABC?

I am trying to create a simple abstract base class Abstract that along with its own methods provides the methods of two others abstract base classes: Publisher and Subscriber. When I try to initialize the concrete class Concrete, built on Abstract I get this error: Cannot create a consistent method resolution order (MRO) for bases ABC, Publisher, Subscriber. What is the right way to do it?
from abc import ABC, abstractmethod
class Publisher(ABC):
subscribers = set()
def register(self, obj):
self.subscribers.add(obj)
def unregister(self, obj):
self.subscribers.remove(obj)
def dispatch(self, event):
print("dispatching", event)
class Subscriber(ABC):
#abstractmethod
def handle_event(self, event):
raise NotImplementedError
class Abstract(ABC, Publisher, Subscriber):
#abstractmethod
def do_something(self, event):
raise NotImplementedError
class Concrete(Abstract):
def handle_event(self, event):
print("handle_event")
def do_something(self, event):
print("do_something")
c = Concrete()
Abstract classes don't have to have abc.ABC in their list of bases. They have to have abc.ABCMeta (or a descendant) as their metaclass, and they have to have at least one abstract method (or something else that counts, like an abstract property), or they'll be considered concrete. (Publisher has no abstract methods, so it's actually concrete.)
Inheriting from ABC is just a way to get ABCMeta as your class's metaclass, for people more comfortable with inheritance than metaclasses, but it's not the only way. You can also inherit from another class with ABCMeta as its metaclass, or specify metaclass=ABCMeta explicitly.
In your case, inheriting from Publisher and Subscriber will already set Abstract's metaclass to ABCMeta, so inheriting from ABC is redundant. Remove ABC from Abstract's base class list, and everything should work.
Alternatively, if you really want ABC in there for some reason, you can move it to the end of the base class list, which will resolve the MRO conflict - putting it first says you want ABC methods to override methods from the other classes, which conflicts with the fact that the other classes are subclasses of ABC.
Change from this:
class Abstract(ABC, Publisher, Subscriber):
To this:
class Abstract(Publisher, Subscriber):
The two subclasses are already abstract, thus you don't need to inherit from ABC again.

Updating Classes that inherit from abstract classes

I have an abstract class ship.
from abc import ABC, abstractmethod
class ship(ABC):
def __init__(self):
...
#abstractmethod
def do_stuff(self,stuff,things):
pass
I have multiple classes that inherit from it (destroyer,cruiser,patrol_boat, etc...)
class carrier(ship):
def __init__(self):
....
def do_stuff(self,stuff,things):
....
Currently, if I were to add, let's say def do_more_stuff(self): to ship
class ship(ABC):
def __init__(self):
...
#abstractmethod
def do_stuff(self,stuff,things):
pass
#abstractmethod
def do_more_stuff(self,stuff,things):
pass
The changes would not affect any of the subclasses until I reentered them into the console. How do I change this?
If you actually redefine a class from scratch, it's a different class; the subclasses are still inheriting from the old version of ship. You can't just define a new class named ship and expect the subclasses to find it magically.
Normally, if you wanted to monkey-patch ship after creation to add new methods, you could just do something like:
def do_more_stuff(self,stuff,things):
pass
ship.do_more_stuff = do_more_stuff
But unfortunately, abstractmethods for ABCs are an explicit exception to this rule:
Dynamically adding abstract methods to a class, or attempting to modify the abstraction status of a method or class once it is created, are not supported.
You must either define the abstract base class completely up front, or redefine the entire class hierarchy later if you want to add new abstract methods to the base class.

Using abstract base class VS plain inheritance

I'm trying to understand the benefits of using abstract base classes. Consider these two pieces of code:
Abstract base class:
from abc import ABCMeta, abstractmethod, abstractproperty
class CanFly:
__metaclass__ = ABCMeta
#abstractmethod
def fly(self):
pass
#abstractproperty
def speed(self):
pass
class Bird(CanFly):
def __init__(self):
self.name = 'flappy'
#property
def speed(self):
return 1
def fly(self):
print('fly')
b = Bird()
print(isinstance(b, CanFly)) # True
print(issubclass(Bird, CanFly)) # True
Plain inheritance:
class CanFly(object):
def fly(self):
raise NotImplementedError
#property
def speed(self):
raise NotImplementedError()
class Bird(CanFly):
#property
def speed(self):
return 1
def fly(self):
print('fly')
b = Bird()
print(isinstance(b, CanFly)) # True
print(issubclass(Bird, CanFly)) # True
As you see, both methods support inflection using isinstance and issubclass.
Now, one difference I know is that, if you try to instantiate a subclass of an abstract base class without overriding all abstract methods/properties, your program will fail loudly. However, if you use plain inheritance with NotImplementedError, your code won't fail until you actually invoke the method/property in question.
Other than that, what makes using abstract base class different?
The most notable answer in terms of concrete specifics, besides what you mentioned in your question, is that the presence of the #abstractmethod or #abstractproperty1 decorators, along with inheriting from ABC (or having the ABCMeta metaclass) prevents you from instantiating the object at all.
from abc import ABC, abstractmethod
class AbsParent(ABC):
#abstractmethod
def foo(self):
pass
AbsParent()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class AbsParent with abstract methods foo
However, there's more at play here. Abstract Base Classes were introduced to Python in PEP 3119. I'd recommend reading through the "Rationale" section for Guido's take on why they were introduced in the first place. My sophomoric summary would be that they're less about their concrete features and more about their philosophy. Their purpose is to signal to external inspectors that the object is inheriting from the ABC, and because it's inheriting from an ABC it will follow a good-faith agreement. This "good-faith agreement" is that the child object will follow the intention of the parent. The actual implementation of this agreement is left up to you, which is why it's a good-faith agreement, and not an explicit contract.
This primarily shows up through the lens of the register() method. Any class that has ABCMeta as its metaclass (or simply inherits from ABC) will have a register() method on it. By registering a class with an ABC you are signaling that it inherits from the ABC, even though it technically doesn't. This is where the good-faith agreement comes in.
from abc import ABC, abstractmethod
class MyABC(ABC):
#abstractmethod
def foo(self):
"""should return string 'foo'"""
pass
class MyConcreteClass(object):
def foo(self):
return 'foo'
assert not isinstance(MyConcreteClass(), MyABC)
assert not issubclass(MyConcreteClass, MyABC)
While MyConcreteClass, at this point is unrelated to MyABC, it does implement the API of MyABC according to the requirements laid out in the comments. Now, if we register MyConcreteClass with MyABC, it will pass isinstance and issubclass checks.
MyABC.register(MyConcreteClass)
assert isinstance(MyConcreteClass(), MyABC)
assert issubclass(MyConcreteClass, MyABC)
Again, this is where the "good-faith agreement" comes into play. You do not have to follow the API laid out in MyABC. By registering the concrete class with the ABC we are telling any external inspectors that we, the programmers, are adhering to the API we're supposed to.
1 note that #abstractproperty is no longer preferred. Instead you should use:
#property
#abstractmethod
def foo(self):
pass

Abstraction in Python?

I'm trying to pick up Python for a project, and I'm a bit confused about how to use abstraction and classes. (I'm not a very experienced programmer, so apologies for the basic level of this question.) I come from a Java/Ocaml background, and what I've been trying to do is as follows: I have abstract classes for a graph and a graphadvanced (a graph with some more fancy methods), that look something like this
class AbstractGraph:
def method1(self):
raise NotImplementedError
...
class AbstractAdvanced:
def method2(self):
raise NotImplementedError
...
I then have an implementation of a graph:
class Graph(AbstractGraph):
def method1(self):
* actual code *
Now my question is: can I do something like this?
class Advanced(AbstractAdvanced, AbstractGraph):
def method2(self):
*actual code, using the methods from AbstractGraph*
In other words, how can I define the methods of Advanced abstractly in terms of the methods of AbstractGraph, and then somehow pass Graph into a constructor to get an instance of Advanced that uses Advanced's definitions with Graph's implementation?
In terms of Ocaml, I'm trying to treat AbstractAdvanced and AbstractGraph as module types, but I've played around a little with python and I'm not sure how to get this to work.
If you want to create abstract base classes, you can, but they are of limited utility. It's more normal to start your class hierarchy (after inheriting from object, or some other third-party class) with concrete classes.
If you want to create a class that pieces together various classes that partially some protocol, then just inherit from your implementing classes:
#Always inherit from object, or some subtype thereof, unless you want your code to behave differently in python 2 and python 3
class AbstractGraph(object):
def method1(self):
raise NotImplementedError
class Graph(AbstractGraph):
def method1(self):
* actual code *
class GraphToo(AbstractGraph):
def method1(self):
* actual code *
class AbstractAdvanced(AbstractGraph):
def method2(self):
raise NotImplementedError
class Advanced(Graph,AbstractAdvanced):
def method2(self):
*actual code, using the methods from Graph*
# order of classes in the inheritance list matters - it will affect the method resolution order
class AdvancedToo(GraphToo, Advanced): pass

Categories