Abusing Python's magic methods: ƒ
Anyone who ever did anything in Python will know of the existence of the
__init__
method in classes, which are sort of like a constructor in other
languages (but not really). When the Python interpreter creates an instance of
a class, it looks for a method called __init__
and calls it on the object
after creating it. It is a so-called “magic method”, because the only thing
that makes this happen is the name of the method, which magically causes it
to be called in a specific case.
__init__
isn’t the only such function, there are many others. Some are
commonly used (like __str__
), some almost never (like __new__
). For some
reason, I’m very fascinated with abusing them to make Python behave weirdly.
This is about one such case.
When sorting a list of tuples based on the second element of the tuple, you can
use operator.itemgetter(1)
as a key function:
Similarily, if you want to sort a list of objects based on an attribute, you
can use operator.attrgetter
. Sadly, this is not repeatable: If you would want
to sort a list of tuples of objects by an attribute of the second element of
the tuples, you’d have to use lambda expressions, which to my eyes don’t look
too nice in these cases. I wanted to try if I could make it more compact.
Ideally, I would like to specify it somehow directly without writing lambda
x:
This means of course that I’d need to have some kind of magical variable x
defined somewhere, which sounds like trouble. I chose to take a different name,
one that will very likely not be used by anyone else, and one that is still
short and not hard to type. Not many people know about Python’s weird mix of
supporting some parts of Unicode as parts of identifiers, converting some
characters to others without telling you (like an ⅿ to an m), and banning the
use of others. So I chose an allowed unicode character: ƒ (conveniently typable
on macOS using ⌥f), because of this somehow being related to functional
programming.
But how do I get an object to work as a key function like that? A key function is simply called on every element of the sequence when sorting. But before that happens in our case, we want to subscript it and access an attribute of it, in any order, and potentially multiple times, and it would be cool if we could do even more than that.
Thankfully, one of the magic methods is __call__
which defined what happens
when an object is called like a function, and there are other methods to handle
attribute access (__getattr__
, with which you need to be careful in order not
to break everything) and subscripting (__getitem__
).
So I define a list of actions I want to happen on each item in the sequence
inside of __init__
and just append to that list in __getattr__
and
__getitem__
. In __call__
, I simply call each function in the list of
actions on the item and return the result at the end. Finally, I do a little
singleton trick to make it work more than once. And with that, it works. And
not just as a key function:
A few minutes later, it supports all other operators I could be bothered about implementing, each using the respective magic method:
There was one more interesting challenge: how to make a correct representation
of the current ƒ object for repr()
, I solved it by looking at an operator
precedence table and adding parantheses iff they are needed.
In the end, I threw it in my Python library of miscellaneous
things, and ƒ is then importable with
from toolib.magic import ƒ
.
If you’re interested in Python’s magic methods in-depth, look at Rafe Kettler’s Guide to Magic Methods.