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.
Related
Suppose you have the following class:
class Base(object):
def abstract_method(self):
raise NotImplementedError
Can you then implement a inheriting class, which does not implement the abstract method? For example, when it does not need that specific method. Will that give problems or is it just bad practice?
If you're implementing abstract methods the way you show, there's nothing enforcing the abstractness of the class as a whole, only of the methods that don't have a concrete definition. So you can create an instance of Base, not only of its subclasses.
b = Base() # this works following your code, only b.abstract_method() raises
def Derived(Base):
... # no concrete implementation of abstract_method, so this class works the same
However, if you use the abc module from the standard library to designate abstract methods, it will not allow you to instantiate an instance of any class that does not have a concrete implementation of any abstract methods it has inherited. You can leave inherited abstract methods unimplemented in an intermediate abstract base class (e.g. a subclass of the original base, that is itself intended to still be abstract), but you can't make any instances.
Here's what using abc looks like:
from abc import ABCMeta, abstractmethod
class ABCBase(metaclass=ABCMeta):
#abstractmethod
def abstract_method(self, arg):
...
class ABCDerived(ABCBase):
... # we don't implement abstract_method here, so we're also an abstract class
d = ABCDerived() # raises an error
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.
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.
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
I initially defined the following abstract class:
from abc import ABC, abstractmethod
class Primitive(ABC):
Now I want to create another abstract class that inherits from Primitive:
class InstrumentName(Primitive)
I need this class to be abstract since I ultimately want to create the following two concrete classes:
class CurrencyInstrumentName(InstrumentName)
class MetalInstrumentName(InstrumentName)
I have read the documentation and searched SO, but they mostly pertain to sublcassing concrete classes from abstract classes, or discussing how Python handles abstraction
Just subclass, you don't need to do anything special.
A class only becomes concrete when there are no more abstractmethod and abstractproperty objects left in the implementation.
Let's illustrate this:
from abc import ABC, abstractmethod
class Primitive(ABC):
#abstractmethod
def foo(self):
pass
#abstractmethod
def bar(self):
pass
class InstrumentName(Primitive):
def foo(self):
return 'Foo implementation'
Here, InstrumentName is still abstract, because bar is left as an abstractmethod. You can't create an instance of that subclass:
>>> InstrumentName()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class InstrumentName with abstract methods bar
Subclasses can also add #abstractmethod or #abstractproperty methods as needed.
Under the hood, all subclasses inherit the ABCMeta metaclass that enforces this, and it simply checks if there are any #abstractmethod or #abstractproperty attributes left on the class.
As #MartijnPieters wrote, you don't need to do anything special for Python, but PyCharm will warn:
Class InstrumentName must implement all abstract methods
One way to suppress that warning:
import abc
class Primitive(abc.ABC):
#abc.abstractmethod
def foo(self):
pass
# noinspection PyAbstractClass
class InstrumentName(Primitive):
def is_tuba(self):
return False
Another way:
...
class InstrumentName(Primitive, metaclass=abc.ABCMeta):
def is_tuba(self):
return False