Infix Operators in Python January 22, 2012
As you may already know, there are 3 kinds of operators calling-notations: prefix (
+ 3 5),
3 + 5), and postfix (
3 5 +). Prefix (as well as postfix) operators are used
in languages like LISP/Scheme, and have the nice property of not requiring parenthesis —
there’s only one way to read an expression like
3 5 + 2 *, unlike
3 + 5 * 2.
On the other hand, it reduces code readability and the locality of operators and their arguments.
This is why we all love infix operators.
Now imagine I have a function,
add(x,y), and I have an expression like
wouldn’t it be cool if I could use infix notation here? Sadly though, Python won’t allow you to
define new operators or change how functions take their arguments... but that doesn’t
mean we have to give up!
Haskell, for instance, allows you to define custom operators and set their precedence, as well
as invoking "normal" functions as infix operators. Suppose you have a function
f(x,y) — you
can invoke it like
f 5 6 or
5 `f` 6 (using backticks). This allows us to turn
our previous expression,
5 `add` 6 `add` 7 `add` 8,
which is much more readable. But how can we do this in Python?
Well, there’s this Cookbook recipe that provides a very nice way to achieving the same functionality in Python (adapted a little by me):
from functools import partial class Infix(object): def __init__(self, func): self.func = func def __or__(self, other): return self.func(other) def __ror__(self, other): return Infix(partial(self.func, other)) def __call__(self, v1, v2): return self.func(v1, v2)
Using instances of this peculiar class, we can now use a new "syntax" for calling functions as infix operators:
>>> @Infix ... def add(x, y): ... return x + y ... >>> 5 |add| 6 11
Surrounding decorated functions with pipes (bitwise ORs) allows them to take their parameters infix-ly. Using this, we can do all sorts of cool things:
>>> instanceof = Infix(isinstance) >>> >>> if 5 |instanceof| int: ... print "yes" ... yes
And even curry functions:
>>> curry = Infix(partial) >>> >>> def f(x, y, z): ... return x + y + z ... >>> f |curry| 3 <functools.partial object at 0xb7733dec> >>> g = f |curry| 3 |curry| 4 |curry| 5 >>> g() 12
Ain’t that cool?