Welcome, guest | Sign In | My Account | Store | Cart

Python has the wonderful "in" operator and it would be nice to have additional infix operator like this. This recipe shows how (almost) arbitrary infix operators can be defined.

Python, 56 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# definition of an Infix operator class
# this recipe also works in jython
# calling sequence for the infix is either:
#  x |op| y
# or:
# x <<op>> y

class Infix:
    def __init__(self, function):
        self.function = function
    def __ror__(self, other):
        return Infix(lambda x, self=self, other=other: self.function(other, x))
    def __or__(self, other):
        return self.function(other)
    def __rlshift__(self, other):
        return Infix(lambda x, self=self, other=other: self.function(other, x))
    def __rshift__(self, other):
        return self.function(other)
    def __call__(self, value1, value2):
        return self.function(value1, value2)

# Examples

# simple multiplication
x=Infix(lambda x,y: x*y)
print 2 |x| 4
# => 8

# class checking
isa=Infix(lambda x,y: x.__class__==y.__class__)
print [1,2,3] |isa| []
print [1,2,3] <<isa>> []
# => True

# inclusion checking
is_in=Infix(lambda x,y: y.has_key(x))
print 1 |is_in| {1:'one'}
print 1 <<is_in>> {1:'one'}
# => True

# an infix div operator
import operator
div=Infix(operator.div)
print 10 |div| (4 |div| 2)
# => 5

# functional programming (not working in jython, use the "curry" recipe! )
def curry(f,x):
    def curried_function(*args, **kw):
        return f(*((x,)+args),**kw)
    return curried_function
curry=Infix(curry)

add5= operator.add |curry| 5
print add5(6)
# => 11

Of course this is a hack that plays with Python's ability of operator overloading. It is some sort of similar to the Ruby syntactic sugar recipe, but I think this might really be useful. One could e.g. define infix operators for set arithmetics like union, intersection and so on which would really enhance the readability of the code. I wonder whether it would be possible to use decorators for the definition of the infixes and one could omit the stars around the infix?

Addendum: Thanks for all the interest and the helpful comments for the recipe. I generated a revised version that took them into account and makes the recipe more useful. Now you can call an operator by using bars || or using << and >>. In principle it is easily possible to define different function calls for different operands. Also added an example from functional programming which might be helpful and shows how the hack greatly enhances readability. Time will tell whether the recipe stays a nice hack or whether people will start using it as a programming style.

Here is a discussion how the hack works: In python there are two ways for overloading an operator. Assume we want to overload the multiplication operator * for a certain class. i.e. Xy is redefined for all expressions containing X as the first operand where X is of a special class. Then we have to overload (i.e. redefine) the method __mul__(X,y) of the class of X. This is nice and works in many programming languages in some way. But python gives you more: You can also redefine the meaning yX with arbitrary y for all X. Then you have to overload the method __rmul__(X,y). Thus you can define a different meaning of the multiplication operator for left and right multiplication. This is also possible for +,-,/,*,<<,>>,& and | which call the methods __add__, __sub__, __div__, __pow__, __lshift__, __rshift__, __and__ and __or__. Now we define a special class "Infix" which exploits this property and by leaving out the blanks we can write it in a form a *op b.

Be careful that this is a hack of the language and not the definition of a new keyword. (as David S. pointed out in the comments). Of course the precedence and associativity rules for the operators still apply and therefore 23 x 4 means (23) x 4 while 2+3 x 4 means 2 + ( 3 x 4). (thanks to Raymond Hettinger for pointing this out).