ASPN ActiveState Programmer Network  
ActiveState, a division of Sophos
/ Home / Perl / PHP / Python / Tcl / XSLT /
/ Safari / My ASPN /
Cookbooks | Documentation | Mailing Lists | Modules | News Feeds | Products | User Groups
Submit Recipe
My Recipes

All Recipes
All Cookbooks


View by Category

Title: Infix operators
Submitter: Ferdinand Jamitzky (other recipes)
Last Updated: 2005/02/17
Version no: 1.2
Category: OOP

 

5 stars 16 vote(s)


Description:

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.

Source: Text Source

# 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

Discussion:

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. X*y 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 y*X 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
2**3 *x* 4 means (2**3) *x* 4
while 2+3 *x* 4 means 2 + ( 3 *x* 4).
(thanks to Raymond Hettinger for pointing this out).



Add comment

Number of comments: 16

Associativity and Precedence, Raymond Hettinger, 2005/02/12
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.
Add comment

Using '|' would work better for many situations, Phillip J. Eby, 2005/02/15
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()!
Add comment

Not reentrant, Detlef Lannert, 2005/02/15
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

Add comment

1st form works on Jython, 2nd form does not :-(, David Welden, 2005/02/16
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
Add comment

nested scopes, Ian Bicking, 2005/02/16
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)

Add comment

Nested scopes work in Jython, Dave Benjamin, 2005/02/17
You just need the line:

from __future__ import nested_scopes

Add comment

Maxim Krikun, 2005/02/15
> 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")
Add comment

adjacent strings, Ian Bicking, 2005/02/15
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.
Add comment

Monad combinators, Dominic Fox, 2005/02/16

"""
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

Add comment

observation from a novice, David S, 2005/02/16
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!!!'

Add comment

Add __call__ method, Robert Kern, 2005/02/17
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.
Add comment

@infix Decorator , Oliver Horn, 2005/02/17
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

Add comment

You don't even need the 'def infix' part..., Phillip J. Eby, 2005/02/17
...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.
Add comment

Nick Coghlan,Nick Coghlan, 2005/03/02
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.
Add comment

hah!, Michael Hudson, 2005/03/02
Masterful.
Add comment

try livelogix instead, B T, 2005/03/02
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
Add comment



Highest rated recipes:

1. A simple XML-RPC server

2. Web service accessible ...

3. IPy Notify

4. Treat the Win32 Registry ...

5. a friendly mkdir()

6. Wrapping template engine ...

7. Assignment in expression

8. Changing return value ...

9. Implementation of sets ...

10. bag collection class




Privacy Policy | Email Opt-out | Feedback | Syndication
© 2006 ActiveState Software Inc. All rights reserved.