I love object orientation, but for some things a functional programming style is really better. Python makes this pretty easy, and in the next posts I’d like to introduce some functions from the **functools** library that can modify your functions or methods to save you from doing a lot of programming push-ups. Today I’ll show you how to **total_ordering** decorator that gets all six comparative operators to work on your class with just two explicitly written, saving you the work of having to write the other four.

**functools** has a lot of great stuff, so if you like what you read here, take a look at the functools documentation to find a whole lot of other really cool functions. Today, I’ll only be talking about one: **total_ordering**.

# total_ordering: No more busy work on comparative operators

If you write a custom class in Python, chances are that you might want to compare multiple instances of that class. To illustrate this, let’s take a look at a custom implementation of fractions as numbers in Python (I know that fractions are already implemented, I’m just using this is an example):

class Fraction: """A very simple implementation of fraction numbers""" def __init__(self, numerator, denominator): self.numerator = numerator self.denominator = denominator

Now this stores your fractions with their numerators and denominators, nothing more. Pretty useless, but we see that it works:

>>> f1 = Fraction(1, 3) >>> f2 = Fraction(2, 4) >>> print(f1.numerator, f1.denominator) (1, 3) >>> print(f2.numerator, f2.denominator) (2, 4)

Okay, so that works, the numbers are tucked nicely away. But what happens when we try to compare them?

>>> f1 > f2 True >>> f2 > f1 False

Wait, f1 is 1/3 and f2 is 2/4, or 1/2 if we like reducing. 1/3 is 0.33, definitely **not** larger than 0.5. So obviously the comparison is working wrong. We need to tell Python explicitly how to compare instances of this class. Let’s do that:

class Fraction: """A very simple implementation of fraction numbers""" def __init__(self, numerator, denominator): self.numerator = numerator self.denominator = denominator def to_decimal(self): return float(self.numerator) / self.denominator def __lt__(self, other): return self.to_decimal() < other.to_decimal() def __eq__(self, other): return self.to_decimal() == other.to_decimal()

This is a kind of ugly solution because it compares by converting to a decimal, but I’m wanting to demonstrate the methods more than the numerics. The __lt__() method performs the < operator and the __eq__() method performs ==. Now comparison works! Check it out:

>>> f1, f2 = Fraction(1, 3), Fraction(2, 4) >>> f1 < f2 True >>> f1 == f2 False >>> f1 > f2 False

Not only does it work, but the greater than operator works as well! That’s because in Python, if you don’t define a greater than operator (__gt__), Python swaps the operands and uses __lt__. That’s awesome. But there’s one problem: It doesn’t know how to use < and == to combine into <=, nor can it turn that around and perform >=, even though technically all the information that you’d need for that are there. Check this out:

>>> f3 = Fraction(2, 6) >>> f1 <= f3 False

The solution? If you want => and <= to work right without **functools**, it would be to write each and every operand by hand. A lot of boilerplating, considering that with either greater than or less than plus equal can tell you all the other operands’ results. **functools** takes care of that for you using the decorator** @total_ordering**. If you want to know how it works, check out the documentation. Using it is very, very simple – you just add the **@total_ordering** decorator before your class definition:

import functools @functools.total_ordering class Fraction: """A very simple implementation of fraction numbers""" def __init__(self, numerator, denominator): self.numerator = numerator self.denominator = denominator def to_decimal(self): return float(self.numerator) / self.denominator def __lt__(self, other): return self.to_decimal() < other.to_decimal() def __eq__(self, other): return self.to_decimal() == other.to_decimal()

Now take a look at the results.

>>> f1, f2, f3 = Fraction(1, 3), Fraction(2, 4), Fraction(2, 6) >>> f1 < f2 True >>> f1 == f3 True >>> f1 < f3 False >>> f1 > f3 False >>> f1 <= f3 True

And that’s all there is to it!

## Leave a Reply