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

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).

23 comments

Raymond Hettinger 12 years ago  # | flag

Associativity and Precedence. This gets my vote for best hack of 2005. ;-)

Be sure to add notes on associativity and precedence so it is clear that:

3 ** 2 *x* 4 ** 3 == (3 ** 2) *x* (4 ** 3)

To change the precedence, try other operators: | ^ & ** etc.

Maxim Krikun 12 years ago  # | flag

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?

i believe this is not possible, unless there is a new explicit binary operator, applied when there is a whitespace between two terms.

So far this works for string literals only (("A" "B")=="AB")

Phillip J. Eby 12 years ago  # | flag

Using '|' would work better for many situations. Because '|' is the operator with the lowest precedence that's still practical to use with this technique.

That having been said, I also have to say that this is probably the best Python hack ever, because the ability to do this has been in the language for many generations and nobody thought of it (or at least shared their invention) before. I ran the recipe on Python 1.5.2 and it worked once I changed the __class__ tests to type()!

Ian Bicking 12 years ago  # | flag

adjacent strings. Adjacent string literals are treated as a single string. This is only true of literals. You can use it for stuff like:

assert something, (
    "There is a problem with your something or another "
    "and you should do something about it.")

So it's not an operator at all, and there's no concatenation -- that's parsed as a single string.

Detlef Lannert 12 years ago  # | flag

Not reentrant. I agree that this is a fascinating hack -- unfortunately the new infix operator is not reentrant. I'm using your "div" example but with __or__ and __ror__ methods instead:

>>> 8 |div| (2 |div| 2)
2

With a slight modification, however, it works as expected -- just return a new (actually monadic) operator as the result of the first operation:

class Infix(object):
    def __init__(self, function):
        self.function = function
    def __ror__(self, other):
        return Infix(lambda x: self.function(other, x))
    def __or__(self, other):
        return self.function(other)

Now we get the conventional result:

>>> div = Infix(operator.div)
>>> 8 |div| (2 |div| 2)
8
Dominic Fox 12 years ago  # | flag

Monad combinators.

"""
See: http://www.nomaware.com/monads/html/index.html
"""

class Infix(object):
    def __init__(self, function):
        self.function = function
    def __ror__(self, other):
        return Infix(lambda x: self.function(other, x))
    def __or__(self, other):
        return self.function(other)

class Just:
    def __init__(self, value):
        self.value = value

def mbind(maybe, func):
    if maybe is None:
        return None
    else:
        return func(maybe.value)

mbind = Infix(mbind)

def mreturn(value):
    return Just(value)

class Sheep:
    def __init__(self, name):
        self.name = name
        self.mother = None
        self.father = None

def father(sheep):
    if sheep.father is None:
        return None
    else:
        return Just(sheep.father)

def mother(sheep):
    if sheep.mother is None:
        return None
    else:
        return Just(sheep.mother)

def mothersPaternalGrandfather(sheep):
    return mreturn(sheep) |mbind| mother |mbind| father |mbind| father

shawn = Sheep("Shawn")
gertrude = Sheep("Gertrude")
ernie = Sheep("Ernie")
frank = Sheep("Frank")

shawn.mother = gertrude
gertrude.father = ernie
ernie.father = frank

print mothersPaternalGrandfather(shawn).value.name # Should return "Frank"
print mothersPaternalGrandfather(ernie) # Should return None
David S 12 years ago  # | flag

observation from a novice. If, like me, you are just getting familiar with Python, you may be distracted, as I was, by the format of the examples. I had to realize that |myop| is not an atomic unit. It is the myop variable with the '|' operator (which is redefined in the Infix class) on either side.

So we can have (using the bar version):
>>> myop = Infix(lambda x,y: "Look: %s myop %s!!!"%(x,y) )
>>> 4 | myop | 3
'Look: 4 myop 3!!!'

We could have typed
>>> 4 |myop| 3
or
>>> 4|myop|3
er even
>>> 4| myop |3

The important operator overloading is happening on the instance named myop, for which the bar, |, has been redefined.

Also, notice that it is redefined such that it requires a right and left operand (almost obviously), which is why it must be sandwiched so.

By the way:
>>> 4 | myop | 'dog'
'Look: 4 myop dog!!!'
David Welden 12 years ago  # | flag

1st form works on Jython, 2nd form does not :-(. C:\jython>jython

Jython 2.1 on java1.4.1_02 (JIT: null)

Type "copyright", "credits" or "license" for more information.

>>> class Infix(object):

... def __init__(self, function):

... self.function = function

... def __ror__(self, other):

... return Infix(lambda x: self.function(other, x))

... def __or__(self, other):

... return self.function(other)

...

:4:[SyntaxWarning]: local name 'other' in '__ror__' shadows use as global in nested scopes

:4:[SyntaxWarning]: local name 'self' in '__ror__' shadows use as global in nested scopes

Traceback (innermost last):

File "", line 1, in ?

NameError: object

Ian Bicking 12 years ago  # | flag

nested scopes. I'm guessing that's because Jython doesn't have nested scopes. Use this definition instead (__ror__ changed):

class Infix(object):
    def __init__(self, function):
        self.function = function
    def __ror__(self, other):
        return Infix(lambda x, other=other: self.function(other, x))
    def __or__(self, other):
        return self.function(other)
Robert Kern 12 years ago  # | flag

Add __call__ method. I think that it would be good to add a __call__ method to the infix object so that the object can act, more or less, like the original function object.

class infix:
    ... magic ...
    def __call__(self, value1, value2):
        return self.function(value1, value2)

def myfunc(x, y):
    ...

myfunc = infix(myfunc)

myfunc(1, 2) == 1 |myfunc| 2

And I agree with everyone else who said that this is an extremely cool hack.

Oliver Horn 12 years ago  # | flag

@infix Decorator. It's easy to define a corresponding decorator @infix.

def infix(f):
    return Infix(f)

The decorator can be applied to any function definition and simply returns a new Infix object that wraps the original function definition.

@infix
def x(x, y):
    return x * y

@infix
def isa(x, y):
    return x.__class__ == y.__class__

Note that since the Infix class provides an appropriate __call__ method, @infix-decorated functions can still be called like "ordinary" functions.

print x(2, 4)
print 2 |x| 4
Phillip J. Eby 12 years ago  # | flag

You don't even need the 'def infix' part... ...because classes can be used as decorators. Just use @Infix, or rename the Infix class to lower case.

The class also needs __getattr__ and __setattr__ routines that delegate to self.function, as well as __class__ and __doc__ delegates (so that anInfix.__class__ returns function.__class__) in order to complete the illusion that Infix instances are functions.

Dave Benjamin 12 years ago  # | flag

Nested scopes work in Jython. You just need the line:

from __future__ import nested_scopes
Nick Coghlan 11 years, 11 months ago  # | flag

Unfortunately string (and sundry other) objects don't play well with this hack. They raise a TypeError which prevents the __rop__ method from getting called.

Still very cool though - and all the more reason to try and make standard classes play more nicely with others.

Michael Hudson 11 years, 11 months ago  # | flag

hah! Masterful.

B T 11 years, 11 months ago  # | flag

try livelogix instead. Instead of using a neat hack, try requesting custom infix operators as a feature for python (good luck), or use LiveLogix which runs on top of the CPython VM and already has this feature: http://logix.livelogix.com

Y S 7 years, 10 months ago  # | flag

bindable infix.

>>> f = Infix(lambda x,y: x*y)
>>> g = 2 | f
>>> print g | 4
8

ok. but...

>>> g = f | 4
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "...", line 42, in __or__
TypeError: <lambda>() takes exactly 2 arguments (1 given)

And it just does'nt make sense.

>>> g = 2 | f
>>> h = 3 | g
>>> print h | 4
8

If you want to bind the infix operator, you should fix the code like this:

class Infix:
    ... implementation ...
    def __ror__(self, other):
        return lbind(self.function, other)
    def __or__(self, other):
        return rbind(self.function, other)

class rbind:
    def __init__(self, function, binded):
        self.function = function
        self.binded = binded
    def __ror__(self, other):
        return self.function(other, self.binded)
    def __call__(self, other):
        return self.function(other, self.binded)

class lbind:
    def __init__(self, function, binded):
        self.function = function
        self.binded = binded
    def __or__(self, other):
        return self.function(self.binded, other)
    def __call__(self, other):
        return self.function(self.binded, other)
krister hedfors 6 years, 11 months ago  # | flag

Check out inrex, regular expressions as inline operators!

easy_install inrex

from inrex import  match, search, split, findall, finditer

if 'asd 123' |match| r'(\w+) (\d+)':
    print 'word is', match[0]
    print 'digit is', match[1]

if 'asd 123' |match| r'(?P<word>\w+) (?P<digit>\d+)':
    print 'word is', match['word']
    print 'digit is', match['digit']

print 'asd 123 qwe 456' |findall| r'\d+'
print 'asd 123 qwe 456' |split| r'\d+'
print 'asd 123 qwe 456' |split(maxsplit=1)| r'\d+'

with open("logfile.txt") as f:
    for m in f |match| r"(\w+) (\w+) (\w+)":
        print match[0], match[1], match[2]
        # or
        print m.group(0), m.group(1), m.group(2)
Bob Marley 5 years, 11 months ago  # | flag

Ferdinand- In your Recipe description, your text below the recipe seems to be formatted wrong, triggering italics and bold in the Markdown syntax. I think since you use * next to alphabetic characters (such as 2 * X * 3: - which shows up as "2X3" if you don't include spaces after your stars.)

Markdown changes it to italics, and we lose the stars. (According to instructions, * * bold * *, * italic *, indent 4 spaces for a code block.)

Jason Grout 5 years, 10 months ago  # | flag

Sage has a version of this sort of idea, but allows the user to also specify the precedence level for a few common operator precedences. In the Sage source, it uses the sage_wraps decorator, but outside of Sage, you could just as easily change it to use the wraps decorator from functools.

http://hg.sagemath.org/sage-main/file/361a4ad7d52c/sage/misc/decorators.py#l74

fra 4 years, 7 months ago  # | flag

Wouldn't it be nice to have a unit testing framework based on this recipe? Writing self.assertEquals(foo, bar) is tedious :

foo |eq| bar
1 |In| [1, 2, 3]
sqrt(-1) |raises| ValueError

And so on...

Tuomas Laakkonen 1 year, 1 month ago  # | flag

Once, you realize that

3 |myop| 4

is not an atomic operation: it is actually

x = 3 | myop
y = x | 4

You can take advantage of that to perform statements that effect the global/local state between evaluating the left and right hand sides, in your operators. For example, here is a pipe operation:

class pOp:
    def __ror__(self, other):
        global _
        _ = other

    def __or__(self, other):
        return other

p = pOp()

Because Python evaluates left-to-right, this means that you can do stuff like this:

3 + 4 |p| _ + 2          #evaluates to 9

Or maybe, you could even do something unspeakable with exceptions to try and create short-circuit evaluation...

Fran Hrženjak 11 months, 3 weeks ago  # | flag

We can go crazy and make operators that take arguments:

def mapnget(foo):
    """ map foo to x and then return x[y] """
    return Infix(lambda x, y: map(foo, x)[y])

[1, 2, 3] | mapnget(float) | 1   # returns 2.0

That's what I call a ternary operator! :)

Add a comment

Sign in to comment

Created by Ferdinand Jamitzky on Fri, 11 Feb 2005 (PSF)
Python recipes (4543)
Ferdinand Jamitzky's recipes (7)

Required Modules

Other Information and Tasks