[This post has been updated multiple times since it's initial posting; see the Corrections section at the end for what was changed.]

The reaction to my blog post on unravelling attribute access was positive enough that I'm inspired to do another post on how much of Python's syntax is actually just syntactic sugar. In this post I want to tackle binary arithmetic operations.

Specifically, I want to unravel how subtraction works: `a - b`

. Now I am purposefully choosing subtraction as it is non-commutative. This helps make sure that the order of operations matters compared to doing addition where you could make a mistake and flip `a`

and `b`

in the implementation and still end up with the same result.

## Finding the C code

As in my last post, we are going to start by seeing what bytecode the CPython interpreter compiles for the syntax.

So it looks like the `BINARY_SUBTRACT`

opcode is what we want to dive into. Looking that up in `Python/ceval.c`

shows you the C code to implement that opcode is as follows:

The key bit of code to see here is that `PyNumber_Subtract()`

implements the actual semantics for subtraction. Now untangling that function through some macros gets you to the `binary_op1()`

function. What this provides is a generic way to manage binary operations. Instead of using it as our reference for implementation, though, we are going to work from Python's data model as I think the documentation is nice and clear on what the semantics should be for subtraction.

## Learning from the data model

Reading through the data model, you will discover that two methods play a part in implementation subtraction: `__sub__`

, and `__rsub__`

.

### The `__sub__()`

method

When considering `a - b`

, the `__sub__()`

method is searched from the type of `a`

and then `b`

is passed as an argument (much like with `__getattribute__()`

in my blog post on attribute access, special/magic methods are resolved on the type of an object, not the object itself for performance purposes; I use `_mro_getattr()`

to represent this in the example code below). So if it's defined, `type(a).__sub__(a, b)`

will be used to do subtraction.

That means subtraction, at its simplest form, is just a method call! You can generalize this today using the `operator.sub()`

function. We will model our own implementation after that function. I will be using the names `lhs`

and `rhs`

to represent the left-hand side and right-hand side, respectively, of `a - b`

to make the example code easier to follow.

### Letting the right-hand side participate via `__rsub__()`

But what if `a`

doesn't implement `__sub__()`

? Then we try calling `__rsub__()`

from `b`

if `a`

and `b`

are different types (the "r" in `__rsub__`

stands for "right", as in right-hand side). This makes sure that both sides of the operation get a chance to try and make the expression work when they differ; when they are the same the assumption is `__sub__()`

would have been able to handle the situation. Even when the implementation is the same, though, you still want to call `__rsub__()`

in case it matters when one of the objects is a different (sub)class.

### Letting a type take a pass

Now both sides of the expression get to participate! But what if the type of an object doesn't support subtraction for some reason (e.g. `4 - "stuff"`

doesn't work)? What `__sub__`

or `__rsub__`

can do in that case is return `NotImplemented`

. That's a signal to Python that it should move on and try the next option in making the operation work. For our code that means we need to check what the methods return before we can assume it worked.

### Letting subclasses boss around their parents

If you take a look at the docs for `__rsub__()`

, you will notice there is a note. What it says is that if the right-hand side of a subtraction expression is a subclass of the left-hand side (and a true subclass; being the same class does not count) **and** the `__rsub__()`

method is different between the two objects, then `__rsub__()`

is called **before** `__sub__()`

. In other words, you reverse the order you try the methods if `b`

is a subclass of `a`

.

This might seem like a rather odd special-case, but there's logic behind it. When you subclass something it means you are injecting new logic into how a class should operate compared to its superclass. This logic is not necessarily exposed to the superclass, which means that if a superclass operates on a subclass it could very easily overlook how the subclass wants to be treated.

To put it concretely, imagine a class named `Spam`

, that when you do `Spam() - Spam()`

you get an instance of `LessSpam`

. Now imagine you created a subclass of `Spam`

called `Bacon`

, such that when you subtract `Bacon`

from `Spam`

you get `VeggieSpam`

. Without the rule above, `Spam() - Bacon()`

would lead to `LessSpam`

since `Spam`

doesn't know that removing `Bacon`

should lead to `VeggieSpam`

. But with the rule above, the expected outcome of `VeggieSpam`

will occur since `Bacon.__rsub__()`

gets first dibs on creating the value of the expression (had it been `Bacon() - Spam()`

then the proper outcome would still work out since `Bacon.__sub__()`

would be called first, hence why the rule says the classes have to differ with different methods and not just be a subclass as defined by `issubclass()`

).

As for why the implementation of `__rsub__`

must be different even when the "proper" subclass is met, that stems from how operator overlading was originally implemented at at the C level in CPython. Guido and I talked back and forth about it, but the key insight is that at the C level there's only a "subtraction" method, not `__sub__`

and `__rsub__`

. And so for code that calls extension modules the way that this left-side/right-side things occur is calling the "subtraction" method with different argument order (i.e. `A.subtract(a, b)`

for `A.__sub__`

, `A.subtract(b, a)`

for `A.__rsub__`

). Without that rule you would then call the exact same method twice which is redundant and could lead to subtle bugs. With this specific rule you avoid those issues.

And that's it! This gives us a complete implementation of subtraction.

## Generalizing to other binary operations

With subtraction out of the way, what about the other binary operations? Well, it turns out they all operate the same, they just happen to have different special/magic method names that they use. So if we can generalize this approach, then we will have implemented the semantics for 13 operations: `+`

, `-`

, `*`

, `@`

, `/`

, `//`

, `%`

, `**`

, `<<`

, `>>`

, `&`

, `^`

, and `|`

.

And thanks to closures and Python's flexibility in how objects know details about themselves, we can generalize the creation of the `operator`

functions.

With this code, you can define the operation for subtraction as `_create_binary_op("sub", "-")`

and then repeat as necessary for the other operations.

## More Info

You can find more posts by me unravelling Python's syntax by checking out the "syntactic sugar" tag of this blog. The source code can be found at https://github.com/brettcannon/desugar.

## Corrections

- 2020-08-19: Fixed the rules for when
`__rsub__()`

is called before`__sub__()`

. - 2020-08-22: Fixed
`__rsub__`

not being called when the types are the same; also stripped out transitional code in favour of just opening code and final code to make my life simple. - 2020-08-23: Added back in most of the examples.
- 2020-09-29: Expanded on why the
`__r*__`

method is skipped when it's the same on both sides of the expression and each object is a "proper" subclass of each other