Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 871
Derek Chiang Avatar asked Mar 17 '14 07:03

Derek Chiang


People also ask

Why should we use abstract class instead of normal class?

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.

What is the difference between abstract class and inheritance?

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.

Can you inherit an abstract base class?

NO. Abstract methods(defintion) are overridden by base class' overriding methods.

Can we use abstract class without inheritance?

An abstract class cannot be inherited by structures. It can contain constructors or destructors. It can implement functions with non-Abstract methods.


1 Answers

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
like image 164
rykener Avatar answered Sep 20 '22 04:09

rykener