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 short answer: An abstract class allows you to create functionality that subclasses can implement or override. An interface only allows you to define functionality, not implement it. And whereas a class can extend only one abstract class, it can take advantage of multiple interfaces.
The main difference between abstraction and inheritance is that abstraction allows hiding the internal details and displaying only the functionality to the users, while inheritance allows using properties and methods of an already existing class. Object-Oriented Programming (OOP) is a major programming paradigm.
NO. Abstract methods(defintion) are overridden by base class' overriding methods.
An abstract class cannot be inherited by structures. It can contain constructors or destructors. It can implement functions with non-Abstract methods.
The most notable answer in terms of concrete specifics, besides what you mentioned in your question, is that the presence of the @abstractmethod
or @abstractproperty
1 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With