Python Tricks-- Abstract Base Classes Keep Inheritance in Check

简介: Python Tricks-- Abstract Base Classes Keep Inheritance in Check

Python Tricks-- Abstract Base Classes Keep Inheritance in Check
Abstract Base Classes(ABCs) ensure that derived classes implement particular methods from the base class. In this chapter you’ll learn about the benefits of abstract base classes and how to define them with Python’s built-in abc module.

So what are Abstract Base Classes good for? A while agao I had a discussion at work about which pattern to use for implementing a maintainable class hierarchy in Python. More specially, the goal was to define a simple class hierarchy for a service backend in the most programmer-friendly and maintainable way.

We had a BaseService class that defined a common interface and several concrete implementations. The concrete implementations do different things but all of them provide the same interface(MockService, RealService, and so on). To make this relationship explicit, the concrete implementations all subclass BaseService.

To make this code as maintainable and programmer-friendly as possible we wanted to make sure that:

Instantiating the base class is impossible; and
Forgetting to implement interface methods in one of the subclasses raises an error as early as possible.
Now why would you want to use Python’s abc module to solve this problem? The above design is pretty common in more complex systems. To enforce that a derived class implements a number of methods from the base class, something like this Python idiom is typically used:

[1]: class Base:
   ...:     def foo(self):
   ...:         raise NotImplementedError()
   ...:     def bar(self):
   ...:         raise NotImplementedError()

In [2]: class Concrete(Base):
   ...:     def foo(self):
   ...:         return 'foo() called'
   ...:     # Oh, no, we forgot to override bar()...
   ...:     # def bar(self):
   ...:     #    return "bar() called"
   ...:

So, what do we get from this first attempt at solving the problem?Calling methods on an instance of Base correctly raises NotImplementedError exceptions:

In [4]: b = Base()

In [5]: b.foo()
---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)

Furthermore, instantiating and using Concrete works as expected. And, if we call an unimplemented method like bar() on it, this also raises an exception:

In [6]: c = Concrete()

In [7]: c.foo()
Out[7]: 'foo() called'

In [8]: c.bar()
---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)

This first implementation is decent, but it isn’t perfect yet. The downsides here are that we can still:

Instantiate Base just fine without getting an error; and
Provide incomplete subclasses–instantiating Concrete will not raise an error until we call the missing method bar().
With Python’s abc module that was added in Python 2.6, we can do better and solve these remaining issues. Here’s an updated implementation using an Abstract Base Class defined with the abc module:

from abc import ABCMeta, abstractmethod

class Base(metaclass=ABCMeta):
    @abstractmethod
    def foo(self):
        pass

    @abstractmethod
    def bar(self):
        pass
class Concrete(Base):
    def foo(self):
        pass
    # We forget to declare bar() again...

This still behaves as expected and creates the correct class hierarchy:

In [9]: assert issubclass(Concrete, Base)

Yet, we do get another very useful benefit here. Subclasses of Base raise a TypeError at instantiation time

TyperError:
"Can't instantiate abstract class Concrete with abstract methods bar"

Without abc, we’d only get a NotImplementedError if a missing method was actually called. Being notified about missing methods at instantiation time is a great advantage. It makes it more difficult to write invalid subclasses. This might not be a big deal if you’re writing new code, but a few weeks or months down the line, I promise it’ll be helpful.

This pattern is not a full replacement for compile-time type checking, of course. However, I found it often makes my class hierarchies more robust and more readily maintainable. Using ABCs states the programmer’s intent clearly and thus makes the code more communicative. I’d encourage you to read the abc module documentation and to keep an eye out for situations where applying this pattern makes sense.

相关文章
|
2月前
|
Go C++ Python
Python Tricks: String Conversion(Every Class Needs a ___repr__)
Python Tricks: String Conversion(Every Class Needs a ___repr__)
24 5
|
2月前
|
Go C# Python
Python Tricks:Python‘s Functions Are First-Class
Python Tricks:Python‘s Functions Are First-Class
33 3
|
2月前
|
C++ Python
Python Tricks--- Object Comparisons:“is” vs “==”
Python Tricks--- Object Comparisons:“is” vs “==”
21 1
|
2月前
|
Python
Python Tricks: Nothing to Return Here
Python Tricks: Nothing to Return Here
17 1
|
2月前
|
C# Python
Python Tricks : Function Argument Unpacking
Python Tricks : Function Argument Unpacking
27 1
|
2月前
|
Python
Python Tricks : How to Write Debuggable Decorators
Python Tricks : How to Write Debuggable Decorators
15 1
|
2月前
|
Linux Go Python
Python Tricks :The Power Of Decorators
Python Tricks :The Power Of Decorators
18 1
|
2月前
|
安全 JavaScript 前端开发
Python Tricks: A Shocking Truth About String Formatting(二)
Python Tricks: A Shocking Truth About String Formatting(二)
26 2
|
2月前
|
API Python
Python Tricks : Fun With args and kwargs
Python Tricks : Fun With args and kwargs
15 0
|
2月前
|
Go C# Python
Python Tricks :Lambdas Are Single-Expression Functions 原创
Python Tricks :Lambdas Are Single-Expression Functions 原创
15 0