Join us and get access to thousands of tutorials and a community of expert Pythonistas.
This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.
Abstracts and Interfaces
00:00 In the previous lesson, I showed you some of the classes in the standard library. In this lesson, I’ll introduce abstract base classes and other ways of defining interfaces.
00:10 Sometimes you don’t want to implement the thing, you just want to say what the thing should look like. Consider a file. You want to be able to read and write to the file, but you don’t want to care how that is implemented underneath.
00:24 It might be on your hard drive. It might be on an S3 server. The interface defines the read/write protocol, while the implementation defines how it is actually stored.
00:34 You can define this kind of interface in two ways: you can build an abstraction that the compiler or interpreter enforces (i.e., if you don’t implement something, an error is thrown), or you can be a little more laissez-faire about it and just document that if you give me something with read and write, it’ll work with my protocol.
00:55 The abstraction concept is built into many object-oriented-first languages. Python is a little more flexible. In practice, you’ll find most folks tend towards the laissez-faire approach, but if you want to get all hardcore object-oriented, there is a class and a decorator that can help you build such a beast.
01:15
The ABC class, short for abstract base class, allows you to mark methods as being abstract with a decorator. You still have to implement something, but that something is just the pass keyword.
01:29 Anyone inheriting from your abstract base class and failing to implement all the methods marked with the decorator gets an error. Let’s go play with the alphabet.
01:41
There are, like, laws about teaching object-oriented coding and examples being based on shapes. You’ve got to obey the law. Here, I’m building an abstract Shape class that stores a color value in RGB.
01:55
First, I need the ABC class that I will use as a base class, and so I’ve imported it as well as the decorator to go with it. I inherit from ABC and then define my class like I otherwise would.
02:09 Let me scroll down a bit.
02:13
Any extender of my Shape class must provide a .get_area() method. I indicate this requirement by wrapping the method with the @abstractmethod decorator.
02:23 And as Python has no way of not implementing, you just pass on the method. Abstraction notwithstanding, it is still a class. I can implement regular old instance methods on it, like this one that converts the internal RGB representation into the equivalent hex notation.
02:46
With my abstract Shape in place, I extend it to create a circle. As Shape has a .__init__() that handles the RGB values, I use super() to invoke it, saving me from writing it again.
02:59
And here I’ve implemented the .get_area() method that was declared abstract in the parent. A circle on its own is lonely, so here’s a Square to go with it.
03:11
This is a bad Square. It hasn’t implemented the .get_area() method, so you get to see how it fails. But before that, let’s start with a Circle.
03:29
There’s a Circle with a nice color purple, a good object instance like any other.
03:41 And now let’s do the broken thing. Importing square …
03:46 and instantiating it … and
03:52
this error is because .get_area() doesn’t exist on the Square. If you’re coming to Python from a strictly typed object-oriented language, this probably makes your skin crawl.
04:03 Enforcement here is happening at runtime, not compile time. I’ll argue it’s better than nothing and let you go off to the Internet and have a flame war about it.
04:15 Although abstract based classes exist in Python, it’s far more common to just do duck typing. That’s typing based on if it looks like a duck and talks like a duck, well then, let’s treat it like a duck.
04:28 Hmm, duck l’orange. The fancy academic term for this is polymorphism (or close enough between friends). Poly meaning many and morph being formed.
04:39 This is about objects having many forms or, alternatively, passing different kinds of objects into the same place and treating them equivalently based on their form.
04:48 Well, it’s been a few minutes. Did you take a bet on when you’d hear the next pun? What was the over and dunder? An example of duck typing in practice is implementing these three dunder methods.
05:01 By doing so, your object is following the sequence protocol, meaning it can be treated like a list, it can be iterated upon, it can have members accessed via square brackets, and you can get at its length.
05:14
Strings, lists, and tuples all implement this protocol, and you can too. Let’s do just that. This example is kind of cheating a little. The HexColorContainer is a proxy to a list, but a user of the class doesn’t need to know that.
05:31
They just need to know that it can be treated as a sequence. The purpose of the HexColorContainer is to keep color values. Colors are passed in as separate RGB values and accessed as their hex color equivalent.
05:44
Inside .__init__(), I’m using a list to store my content. That would be the cheating part. The constructor expects zero or more tuples with three items in each, corresponding to RGB values.
05:59
This chunk of the .__init__() loops through each of the constructor arguments, grabs their individual parts from the tuple, and passes them to the .add_color() method as separate values.
06:09
Speaking of .add_color(), it proxies the list, adding a hex value for the RGB arguments. .__getitem__() is part of the sequence protocol and is used to access an item in the sequence.
06:23
As this code is just a proxy, I pass the same thing on to the internal colors list. .__len__() is what gets called when len() is called on the object, another part of the sequence protocol.
06:37
Once again, I just proxy the list implementation. And finally, in true proxy fashion, .__iter__(), which gets called when the object participates in an iteration, just forwards to the same method on the list.
06:52 And that’s the last of the three calls you need to create a sequence. Importing it so I can play with it … constructing a container with white and purple.
07:11
And here I’ll access the first item. That called .__getitem__(), and the response is the stored hex value.
07:21
Same goes for the purple. Calling len() on my object invokes .__len__(), which was implemented as a len() call on the internal color list.
07:33 Let’s add a new color, a mild gray,
07:41 and as you would expect, the length updates accordingly.
07:48 And of course, I can get at the new item because it is a proxy. I can do more than just pass integers to those square brackets.
07:58
.__getitem__() supports slicing. When I use the slice, I pass the same slice to the list, not having to worry about how to implement a slice.
08:07 Again, proxy for the win.
08:16
Inside a for loop, I’m iterating, so .__iter__() gets called, invoking the underlying .iter() on list.
08:24 By implementing these few methods, I’ve got a specialty container that I can use as a list. Quack. That’s it for part two of the course. In the last lesson, I’ll review what you learned and point you at part three.
Become a Member to join the conversation.