Why use (or not use) interfaces in Python?

Reading Time: 7 minutes

In October 2020 I took Dave Beazley’s Advanced Programming with Python course. Since I wrote a series about his SICP course and the Raft course, I figured I’d write about this one too :).

In the last post of this series, we talked about Python’s special methods. We implemented several special methods on a Fraction class to illustrate how they work.

Now, suppose we wanted to make sure that we could perform a variety of operations with our fractions:

def test_frac():
    a = Fraction(4, 6)
    b = Fraction(3, 4)
    c = Fraction(3, -4)

    sum = a + b
    assert (sum.numerator, sum.denominator) == (17, 12)

    difference = a - b
    assert (difference.numerator, difference.denominator) == (-1, 12)

    product = a * b
    assert (product.numerator, product.denominator) == (1, 2)

    quotient = a / b
    assert (quotient.numerator, quotient.denominator) == (8, 9)


What do these tests tell us about the implementation of our Fraction class?

We might imagine that we have some methods on Fraction that store the numerator and denominator in a tuple or a list, like so:

def __init__(self, numer, denom):
    self.data = (numer, denom)

def numerator(self):
    return self.data[0]

def denominator(self):
    return self.data[1]

But, those same tests would work if we stored the Fraction info in a dictionary:

def __init__(self, numer, denom):
    self.data = {
          "numerator" : numer,
          "denominator" : denom

def numerator(self):
    return self.get("numerator")

def denominator(self):
    return self.get("denominator")

We could even upset our mothers and everyone who has ever loved us with a boolean implementation:

def __init__(self, numer, denom):
    self.numer = numer
    self.denom = denom
    def subfraction(what):
        return numer if what else denom
    self.data = subfraction

def numerator(self):
    return self.subfraction(True)

def denominator(self):
    return self.subfraction(False)

Why would we want to do something like that?*

*I mean, a reason besides upsetting our mothers.

Perhaps we’re not craving this particular set of implementation changes. But, because our three different fraction implementations each adhere to a common interface, they can be tested—and used—interchangeably. This matters when we’re writing software that other people use because it allows us to make updates without breaking their code.

Suppose, for example, that we are writing a popular library with a method in it called doComplicatedThing(). The complicated thing takes a while, but it has to be done, so people do it. Then, we come up with a faster way, or maybe a more memory-efficient way. We want to be able to change out the implementation in doComplicatedThing() without any of our clients needing to change their code. So, we change out the implementation, but leave the shape of the calling interface the same.

The idea of interface adherence isn’t new—in fact, Python provides fewer affordances for it than many other programming languages. Java and Kotlin have the interface keyword. Swift has the protocol keyword. We use these concepts to describe the set of methods that we expect a class to implement.

In Python, we don’t have that: instead, we have the ABC module, which gives us an abstract class. An abstract class implements some methods and require subclasses to implement others. As far as Python is concerned, an interface is just a special case of abstract class that implements no methods and requires subclasses to implement all the methods. It might look something like this:

class Fractionable(ABC):
    def numerator(self):

    def denominator(self):

Why doesn’t Python bother with an interface keyword or similar? I don’t know for sure, but I have some hypotheses. The Python language development team has made a deliberate choice to avoid overcomplicating things with unnecessary language constructs. Naomi Ceder points out another example of this in The Quick Python Book: the case statement. Or rather, the lack of it. Python doesn’t have case statements. More than one Python developer has chalked this up to an oversight and submitted a pull request to the Python repo to add the syntax. But the absence of a case statement isn’t an oversight: rather, structured conditionals do the job just fine already. I wouldn’t be surprised if a similar philosophy governs the absence of an interface keyword.

This might have detrimental practical effects, though. In the Advanced Programming with Python class, Dave Beazley mentioned that he hasn’t seen interfaces used much in Python, even though they can free us up to change objects’ underlying implementations. Is this because Python’s philosophy about keeping a flat hierarchy discourages developers from using inheritance or interface implementation?

Although Python does have that philosophy, I don’t think it’s the reason we don’t see much interface use in Python. Rather, I think two things are happening:

  1. Look: the Python language team declared the interface to be a special case of the already-rarely-used Abstract Base Class, then tucked it away in a module you have to import. That cuh-learly cuts down on its use relative to if it were a keyword in the base standard library. If you display candy at the register in a convenience store, more people buy it than if you keep it in a bureau at the back of the shop on the second floor.
  2. I don’t think it’s the case that most Python developers have considered the utility of interfaces and decided it’s not for them. I think it’s the case that most Python developers have no idea what an interface is.

Before everybody jumps down my throat about this: I am including, in my definition of “Python developers,” anyone who writes Python for work. That includes a lot of people who aren’t full-time software engineers. Data scientists regularly trade in an exact set of about 8 lines of Python that help them get their work done. Academics regularly “do machine learning” via a pastiche of Python lines they found on StackOverflow. Python lends itself to these pursuits, and as a result, there’s a lot of Python out there that was only ever meant to be used one time.

Let me provide you another example, to this point: it is only in Python that I have had to explain, to director-level-and-above practitioners, that you can stick four lines of code in a method and reuse the method, rather than copying and pasting the four lines of code. These practitioners don’t have a philosophical aversion to extracting methods; they just haven’t come across method extraction, period. It’s my extremely unscientific estimate that method extraction is, by and large, better-known than interface adherence. So if there’s a sizeable chunk of Pythonistas who aren’t familiar with method extraction, there’s likely an even more sizeable chunk of Pythonistas who aren’t familiar with interfaces.

And more broadly, making the thing easy to maintain isn’t a goal in roles where Python is an inconvenient means to an end. Somebody found some Python that helped them address a short-term need. “Maintainability” was not that need.

We talked about this concept in more detail in this piece: Paper vs. Ceramic. Data scientists and the like typically imagine their code to serve them over a days-long time span. Software engineers tend to imagine a longer time span. But here’s the kicker: regardless of how long these practitioners imagine needing a particular piece of code, that code often sticks around for longer than their estimate. So there might be value in considering what we can do to keep code maintainable, even if we think we won’t need to maintain it, because the likelihood that we’re wrong about that is probably higher than we think.

So, that’s interface implementation in Python. In the next piece in this series, we’ll talk about inheritance. Python inheritance has a few unique characteristics for us to explore.

If you liked this piece, you might also like:

How to Jump-Start a New Programming Language, or maybe, even, gain a more concrete mental model of the one you already use!

Lessons from Space: Edge-Free Programming, which also explores an Android app’s design and the engineering choices behind it (plus, cool pictures of rockets!)

How does git detect renames?—This piece is about how git detects renames, but it’s also about how to approach questions and novel problems in programming in general

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.