Python中的无点函数组合
我有一些条件,比如:
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
,而 and
和 or
是短路运算符。最接近我想要的方式是定义一个类 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 个回答
你可以使用这个中缀运算符的例子:
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
基本上,你的做法在Python中似乎是唯一可行的方式。这里有一个GitHub上的Python模块,它使用了类似的机制来实现无点函数组合。
我没有使用过这个模块,但从表面上看,它的解决方案看起来更好一些(因为它使用了装饰器和运算符重载,而你是用类和__call__
)。
不过除此之外,这并不算真正的无点代码,可以说只是“点被隐藏”了。这可能对你来说够用,也可能不够。
在Python中,你可以通过在
类里面添加一个__and__
方法来重写&
(按位与)运算符。这样你就可以写出类似下面的代码:
P(is_divisible_by_13) & P(is_palindrome)
甚至可以写成这样:
P(is_divisible_by_13) & is_palindrome
同样的,你也可以通过添加__or__
方法来重写|
(按位或)运算符,或者通过添加__not__
方法来重写~
(按位取反)运算符。需要注意的是,你不能重写内置的and
、or
和not
运算符,所以这可能是你能做到的最接近目标的方式。你仍然需要有一个
类的实例作为最左边的参数。
为了完整起见,你还可以重写这些运算符的就地变体(__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]