Python中的无点函数组合

9 投票
6 回答
3534 浏览
提问于 2025-04-17 12:32

我有一些条件,比如:

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,而 andor 是短路运算符。最接近我想要的方式是定义一个类 P,这个类是一个简单的条件容器,里面实现了 __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 函数是等价的。这样更接近我想要的效果,但它仍然不是无点风格(这里的点是条件本身,而不是它们的参数)。现在第二个问题是:有没有比使用像 P 这样的类,或者不使用(lambda)函数,更好或更简洁的方法(也许不需要括号和点)来组合 Python 中的条件呢?

6 个回答

3

你可以使用这个中缀运算符的例子

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
3

基本上,你的做法在Python中似乎是唯一可行的方式。这里有一个GitHub上的Python模块,它使用了类似的机制来实现无点函数组合。

我没有使用过这个模块,但从表面上看,它的解决方案看起来更好一些(因为它使用了装饰器和运算符重载,而你是用类和__call__)。

不过除此之外,这并不算真正的无点代码,可以说只是“点被隐藏”了。这可能对你来说够用,也可能不够。

11

在Python中,你可以通过在

类里面添加一个__and__方法来重写&(按位与)运算符。这样你就可以写出类似下面的代码:

P(is_divisible_by_13) & P(is_palindrome)

甚至可以写成这样:

P(is_divisible_by_13) & is_palindrome

同样的,你也可以通过添加__or__方法来重写|(按位或)运算符,或者通过添加__not__方法来重写~(按位取反)运算符。需要注意的是,你不能重写内置的andornot运算符,所以这可能是你能做到的最接近目标的方式。你仍然需要有一个

类的实例作为最左边的参数。

为了完整起见,你还可以重写这些运算符的就地变体(__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装饰器来标记你的谓词,这样它们就会自动变成

的实例:

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

撰写回答