|
|
 |
|
Title: Infix operators
Submitter: Ferdinand Jamitzky
(other recipes)
Last Updated: 2005/02/17
Version no: 1.2
Category:
OOP
|
|
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
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)
x=Infix(lambda x,y: x*y)
print 2 |x| 4
isa=Infix(lambda x,y: x.__class__==y.__class__)
print [1,2,3] |isa| []
print [1,2,3] <<isa>> []
is_in=Infix(lambda x,y: y.has_key(x))
print 1 |is_in| {1:'one'}
print 1 <<is_in>> {1:'one'}
import operator
div=Infix(operator.div)
print 10 |div| (4 |div| 2)
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)
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
|
|
|
|
|
 |
|