Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Locked learning resources

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

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.