Readers, meet X. X is a class I wrote in Python as an alternative to using lambda. It has two main features:
- It acts as an identity function ( so X(3) == 3, etc. )
- When performing operations on it, it returns a new class that acts as a corresponding function.
Let me explain. Doing X+2 will return a new class that whenever called with an argument, will return that argument added with 2. So:
>>> map( X+2, [1, 2, 3] )
[3, 4, 5]
>>> filter( X>0, [5, -3, 2, -1, 0, 13] )
[5, 2, 13]
>>> l = ["oh", "brave", "new", "world"]
>>> sorted(l,key=X[-1])
['world', 'brave', 'oh', 'new']
These operations can be chained:
>>> map(2**(X+1), range(10))
[2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
>>> map( "P" + X[3:]*2 + "!", ["Hello", "Suzzy"] )
['Plolo!', 'Pzyzy!']
Caveats
X has a few limitations.
- Using X twice in the same expression probably won't work (this can be solved)
- Since calling X evaluates it, it can't emulate method calls. For that you have to do use call, like: X.upper.call() ('hello') --> 'HELLO'.
- Not all operations can be "captured". For example, the "in" operator. For that you have to use X.in_( ... ), like: X.in_(range(10)) (5) --> True
- Not all attributes will be accessible
- More problems? Likely.
Conclusion
While not innovative nor a complete solution, I believe X can be a useful replacement for some uses of anonymous functions, providing a shorter and simpler syntax which is easier to read and understand.
It is provided here in full, in hope that it will be useful to my readers (Improvements and fixes are welcome):
class _X(object): def __init__(self, func): self.__func = func def __call__(self, arg): return self.__func(arg) def __getattr__(self, attr): return _X(lambda x: getattr(self(x), attr)) def call(self, *args, **kwargs): return _X(lambda x: self(x)(*args,**kwargs)) # Containers def __getitem__(self, other): return _X(lambda x: self(x)[other]) def __getslice__(self, a,b=None,c=None): return _X(lambda x: self(x)[a:b:c]) def in_(self, other): return _X(lambda x: self(x) in other) # Arith def __add__(self, other): return _X(lambda x: self(x) + other) def __sub__(self, other): return _X(lambda x: self(x) - other) def __mul__(self, other): return _X(lambda x: self(x) * other) def __div__(self, other): return _X(lambda x: self(x) / other) def __floordiv__(self, other): return _X(lambda x: self(x) // other) def __mod__(self, other): return _X(lambda x: self(x) % other) def __pow__(self, other): return _X(lambda x: self(x) ** other) def __radd__(self, other): return _X(lambda x: other + self(x)) def __rsub__(self, other): return _X(lambda x: other - self(x)) def __rmul__(self, other): return _X(lambda x: other * self(x)) def __rdiv__(self, other): return _X(lambda x: other / self(x)) def __rfloordiv__(self, other): return _X(lambda x: other // self(x)) def __rmod__(self, other): return _X(lambda x: other % self(x)) def __rpow__(self, other): return _X(lambda x: other ** self(x)) # bitwise def __and__(self, other): return _X(lambda x: self(x) & other) def __or__(self, other): return _X(lambda x: self(x) | other) def __xor__(self, other): return _X(lambda x: self(x) ^ other) def __rand__(self, other): return _X(lambda x: other & self(x)) def __ror__(self, other): return _X(lambda x: other | self(x)) def __rxor__(self, other): return _X(lambda x: other ^ self(x)) def __rshift__(self, other): return _X(lambda x: self(x) >> other) def __lshift__(self, other): return _X(lambda x: self(x) << other) # Comparison def __lt__(self, other): return _X(lambda x: self(x) < other) def __le__(self, other): return _X(lambda x: self(x) <= other) def __eq__(self, other): return _X(lambda x: self(x) == other) def __ne__(self, other): return _X(lambda x: self(x) != other) def __ge__(self, other): return _X(lambda x: self(x) >= other) def __gt__(self, other): return _X(lambda x: self(x) > other) def __abs__(self): return _X(lambda x: abs(self(x))) X = _X(lambda x:x)
Put it in x.py and import as:
from x import X
13 Comments
It's a pretty fun exercise, I guess. Boost for C++ has exactly that as boost::lambda, and it is horrifying to use, partly because C++ does not have closures. C++ is now (for a certain value of "now") adding new lambda syntax (not a particularly comfortable one), because these things should just be handled as part of the language. There's also a performance penalty for a non-trivial use of X.
Personally I am quite in favor of the current Python lambda syntax. I dislike replacing simple "lambda employee: employee.salary" expressions with something as obscure as operator.attrgetter("salary"). I find it harder to read and understand, but to each his own I guess.
There is a performance penalty, that's true. However, a more serious implementation can solve this problem, and also make X-expressions pickle-able at the same time.
I'm don't think " X.salary " is harder to read or understand than its lambda counterpart. I think the opposite is true.
Perhaps in some cases where the context is not clear, argument names can add some clarity. But in my experience it's rare. Also, you can do " employeeX = X " 🙂
Very cool.
Looks really neat. A bit impractical in my opinion, however. It becomes just another concept that you have to explain to people if you ever decide to share your code.
If you omit __getslice__ then your getitem probably already does that. You could do '5 in X' if you implemented __contains__.
Otherwise nice. 🙂
Hi,
very cool indeed.
I have a question though.
What is the technical issue that prevents from support the " X in ..." construct. Wouldn't it suffice to override the __contains__ method?
Alain and Foord:
__contains__ can only return a bool. No matter what I return in it, it's converted into True or False.
Evan:
It's a good point, but I don't think it's much different from any utility library that you use in your code. When I first started using itertools, people at my office would ask me "what does chain do, what does groupby do" etc.
Cool! No support for two argument functions, but still neat!
Actually, there is.
Checkout the newest version at https://github.com/erezsh/lambdaX
Ok, wow. I would have expected X+Y though. Can't use the same argument twice this way. Still, wow.