Python中的无点函数组合

Python中的无点函数组合,python,predicate,function-composition,pointfree,Python,Predicate,Function Composition,Pointfree,我有一些谓词,例如: is_divisible_by_13 = lambda i: i % 13 == 0 is_palindrome = lambda x: str(x) == str(x)[::-1] 并希望以逻辑方式将它们组合在一起,如下所示: filter(lambda x: is_divisible_by_13(x) and is_palindrome(x), range(1000,10000)) 现在的问题是:这样的组合是否可以用一种风格来书写,比如: filter(is_div

我有一些谓词,例如:

is_divisible_by_13 = lambda i: i % 13 == 0
is_palindrome = lambda x: str(x) == str(x)[::-1]
并希望以逻辑方式将它们组合在一起,如下所示:

filter(lambda x: is_divisible_by_13(x) and is_palindrome(x), range(1000,10000))
现在的问题是:这样的组合是否可以用一种风格来书写,比如:

filter(is_divisible_by_13 and is_palindrome, range(1000,10000))
这当然没有期望的效果,因为lambda函数的真值是
True
以及
是短路运算符。我想到的最近的一件事是定义一个类
P
,它是一个简单的谓词容器,实现
\uuuuuu调用
,并具有组合谓词的方法
P
的定义如下:

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
使用
p
我现在可以创建一个新的谓词,它是如下谓词的组合:

P(is_divisible_by_13).and_(is_palindrome)

这相当于上述lambda函数。这更接近于我想要的,但它也不是无点的(点现在是谓词本身,而不是它们的参数)。现在,第二个问题是:在Python中,有没有比使用类(如
P
和不使用(lambda)函数更好或更短的方法(可能没有括号和点)来组合谓词?

可以覆盖

(按位和)通过向
P
类添加
\u和
方法,使用Python中的运算符。然后你可以写这样的东西:

P(is_divisible_by_13) & P(is_palindrome)
甚至

P(is_divisible_by_13) & is_palindrome
类似地,您可以通过添加
\uuuu或
方法覆盖
(按位OR)运算符,并通过添加
\uu非方法覆盖
~
(按位求反)运算符。请注意,您无法覆盖内置的
运算符,因此这可能会尽可能接近您的目标。您仍然需要一个
P
实例作为最左边的参数

为完整起见,您还可以覆盖这些运算符的就地变量(
\uu i和
\uuu ior\uuuu
)和右侧变量(
\uu rand\uuu
\uu ror\uu

代码示例(未经测试,请随意更正):

让您更接近无点涅盘的另一个技巧是以下装饰器:

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
然后,您可以使用
谓词
装饰器标记谓词,使其自动成为
p
的实例:

@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]

基本上,您的方法似乎是Python中唯一可行的方法。有一种方法使用大致相同的机制来实现无点函数组合

我没有使用过它,但乍一看他的解决方案看起来更好一些(因为他在使用类和
\uu调用的地方使用了decorators和操作符重载)

但除此之外,它在技术上不是无点代码,如果你愿意,它只是“点隐藏”。这对您来说可能足够,也可能不够。

您可以使用:

屈服

1001
2002
3003
4004
5005
6006
7007
8008
9009

这就是我的解决方案:

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
这是我的结果:

    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

Python已经有了一种结合两个函数的方法:lambda。您可以轻松创建自己的合成和多个合成函数:

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

似乎您正在非常努力地使非函数式语言表现得像函数式语言。你喜欢蟒蛇吗?@Eric:是的,有点。我的Python代码嵌入C++项目中,我不能只切换到另一种语言。我不确定是否想用另一种意义来重载位运算器,但我发现装饰器的想法非常有趣。谢谢@托马斯:按位运算对谓词有意义吗?如果不是的话,重载也没什么错。这可能是2012年Python年度最佳黑客。至少对我来说。@Tamás:这个食谱已经是2005年最好的黑客了:-)而且,它是费迪南德·贾米茨基写的,不是我。我只是(阿布?)用它来回答。它应该是
reduce(compose2,ff)
,或者只是
reduce(lambda f,g:lambda x:f(g(x)),ff)
    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
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)))