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. And as Python has no way of not implementing, you just pass on the method. Abstraction notwithstanding, it is still a class.
02:34 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. This is a bad Square
.
03:13
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. Enforcement here is happening at runtime, not compile time.
04:07 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. Hmm, duck l’orange.
04:30 The fancy academic term for this is polymorphism (or close enough between friends). Poly meaning many and morph being formed. 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. Well, it’s been a few minutes.
04:51 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. 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. Strings, lists, and tuples all implement this protocol, and you can too.
05:20
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. Inside .__init__()
, I’m using a list to store my content.
05:49
That would be the cheating part. The constructor expects zero or more tuples with three items in each, corresponding to RGB values. 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. Speaking of .add_color()
, it proxies the list, adding a hex value for the RGB arguments.
06:17
.__getitem__()
is part of the sequence protocol and is used to access an item in the sequence. As this code is just a proxy, I pass the same thing on to the internal colors list.
06:30
.__len__()
is what gets called when len()
is called on the object, another part of the sequence protocol. 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. And that’s the last of the three calls you need to create a sequence.
06:58 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. 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.