Python Pointfree Function Combination

I have some predicates, for example:

is_divisible_by_13 = lambda i: i % 13 == 0 is_palindrome = lambda x: str(x) == str(x)[::-1] 

and you want to logically combine them, as in:

 filter(lambda x: is_divisible_by_13(x) and is_palindrome(x), range(1000,10000)) 

The question arises: can such a combination be written in the style of pointfree , for example:

 filter(is_divisible_by_13 and is_palindrome, range(1000,10000)) 

This, of course, is not the desired effect, because the truth value of the lambda functions True and and and or are short-circuit operators. The closest I came up with is to define a class P , which is a simple predicate container that implements __call__() and has the and_() and or_() methods for combining predicates. The definition of P as follows:

 import copy class P(object): def __init__(self, predicate): self.pred = predicate def __call__(self, obj): return self.pred(obj) def __copy_pred(self): return copy.copy(self.pred) def and_(self, predicate): pred = self.__copy_pred() self.pred = lambda x: pred(x) and predicate(x) return self def or_(self, predicate): pred = self.__copy_pred() self.pred = lambda x: pred(x) or predicate(x) return self 

With P I can now create a new predicate, which is a combination of predicates like this:

 P(is_divisible_by_13).and_(is_palindrome) 

which is equivalent to the above lambda function. This comes close to what I would like to have, but it is also not meaningless (the points are now the predicates themselves, and not their arguments). Now the second question: is there a better or shorter way (perhaps without parentheses and dots) to combine predicates in Python than using classes like P and without using (lambda functions)?

+8
python function-composition pointfree predicate
source share
5 answers

You can override the & operator (bitwise AND) in Python by adding the __and__ method to the P class. You could write something like:

 P(is_divisible_by_13) & P(is_palindrome) 

or even

 P(is_divisible_by_13) & is_palindrome 

Similarly, you can override the operator | (bitwise OR) by adding the __or__ method and the ~ (bitwise negation) operator by adding the __not__ method. Please note that you cannot override the built-in and , or not statements, so this is probably as close to your target as possible. You should still have an instance of P as the leftmost argument.

For completeness, you can also override location options ( __iand__ , __ior__ ) and options on the right ( __rand__ , __ror__ ) of these operators.

Sample code (untested, feel free to fix):

 class P(object): def __init__(self, predicate): self.pred = predicate def __call__(self, obj): return self.pred(obj) def __copy_pred(self): return copy.copy(self.pred) def __and__(self, predicate): def func(obj): return self.pred(obj) and predicate(obj) return P(func) def __or__(self, predicate): def func(obj): return self.pred(obj) or predicate(obj) return P(func) 

Another trick that will help you get closer to silent nirvana is the following decorator:

 from functools import update_wrapper def predicate(func): """Decorator that constructs a predicate (``P``) instance from the given function.""" result = P(func) update_wrapper(result, func) return result 

You can then mark your predicates with the predicate decorator to make them an instance of P automatically:

 @predicate def is_divisible_by_13(number): return number % 13 == 0 @predicate def is_palindrome(number): return str(number) == str(number)[::-1] >>> pred = (is_divisible_by_13 & is_palindrome) >>> print [x for x in xrange(1, 1000) if pred(x)] [494, 585, 676, 767, 858, 949] 
+8
source share

Basically, your approach seems the only one possible in Python. There's a python module on github , using roughly the same mechanism for implementing a point function.

I did not use it, but at first glance its solution looks a little better (because it uses decorators and operator overloading, where you use the class and __call__ ).

But other than that, this is not technically a point code, it is just "hidden" if you want. This may or may not be enough for you.

+3
source share

You can use the Infix operator recipe :

 AND = Infix(lambda f, g: (lambda x: f(x) and g(x))) for n in filter(is_divisible_by_13 |AND| is_palindrome, range(1000,10000)): print(n) 

gives

 1001 2002 3003 4004 5005 6006 7007 8008 9009 
+2
source share

This would be my solution:

 class Chainable(object): def __init__(self, function): self.function = function def __call__(self, *args, **kwargs): return self.function(*args, **kwargs) def __and__(self, other): return Chainable( lambda *args, **kwargs: self.function(*args, **kwargs) and other(*args, **kwargs) ) def __or__(self, other): return Chainable( lambda *args, **kwargs: self.function(*args, **kwargs) or other(*args, **kwargs) ) def is_divisible_by_13(x): return x % 13 == 0 def is_palindrome(x): return str(x) == str(x)[::-1] filtered = filter( Chainable(is_divisible_by_13) & is_palindrome, range(0, 100000) ) i = 0 for e in filtered: print str(e).rjust(7), if i % 10 == 9: print i += 1 

And this is my result:

  0 494 585 676 767 858 949 1001 2002 3003 4004 5005 6006 7007 8008 9009 10101 11011 15951 16861 17771 18681 19591 20202 21112 22022 26962 27872 28782 29692 30303 31213 32123 33033 37973 38883 39793 40404 41314 42224 43134 44044 48984 49894 50505 51415 52325 53235 54145 55055 59995 60606 61516 62426 63336 64246 65156 66066 70707 71617 72527 73437 74347 75257 76167 77077 80808 81718 82628 83538 84448 85358 86268 87178 88088 90909 91819 92729 93639 94549 95459 96369 97279 98189 99099 
+1
source share

Python already has a way to combine two functions: lambda. You can easily create your own and several layout functions:

 compose2 = lambda f,g: lambda x: f(g(x)) compose = lambda *ff: reduce(ff,compose2) filter(compose(is_divisible_by_13, is_palindrome, xrange(1000))) 
+1
source share

All Articles