9

我有一些谓词,例如:

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并且是短路运算符。我想出的最接近的事情是定义一个类,它是一个简单的谓词容器,它实现并具有方法并组合谓词。的定义如下:andorP__call__()and_()or_()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) 函数的类?

4

5 回答 5

10

您可以通过向类添加方法来覆盖&Python 中的(按位与)运算符。然后,您可以编写如下内容:__and__P

P(is_divisible_by_13) & P(is_palindrome)

甚至

P(is_divisible_by_13) & is_palindrome

类似地,您可以通过添加方法来覆盖|(按位或)运算符,并通过添加__or__方法来覆盖(按~位否定)运算符__not__。请注意,您不能覆盖内置的and,ornot运算符,因此这可能尽可能接近您的目标。您仍然需要有一个P实例作为最左边的参数。

为了完整起见,您还可以覆盖这些运算符的就地变体 ( __iand__, __ior__) 和右侧变体 ( __rand__, __ror__)。

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

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)

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

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

然后,您可以使用装饰器标记您的谓词,predicate以使它们成为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]
于 2012-02-07T22:03:25.617 回答
3

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

我没有使用它,但乍一看他的解决方案看起来更好一些(因为他在你使用类的地方使用了装饰器和运算符重载和__call__)。

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

于 2012-02-07T22:18:00.610 回答
2

您可以使用中缀运算符配方

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)

产量

1001
2002
3003
4004
5005
6006
7007
8008
9009
于 2012-02-07T22:33:38.707 回答
2

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)))
于 2013-07-20T16:55:41.867 回答
1

那将是我的解决方案:

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
于 2012-02-08T13:23:52.560 回答