Python中的Pointfree函数组合

发布于 2025-01-04 03:50:02 字数 1519 浏览 0 评论 0原文

我有一些谓词,例如:

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

现在的问题是:这样的组合可以写在 pointfree 风格,如:

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

这当然达不到预期的效果,因为 lambda 函数的真值是 Trueand 是短路运算符。我想到的最接近的事情是定义一个类 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 中组合谓词?

I have some predicates, e.g.:

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

and want to logically combine them as in:

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

The question is now: Can such combination be written in a pointfree style, such as:

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

This has of course not the desired effect because the truth value of lambda functions is True and and and or are short-circuiting operators. The closest thing I came up with was to define a class P which is a simple predicate container that implements __call__() and has the methods and_() and or_() to combine predicates. The definition of P is 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 that 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 closer to what I'd like to have, but it is also not pointfree (the points are now the predicates itself instead of their arguments). Now the second question is: Is there a better or shorter way (maybe without parentheses and dots) to combine predicates in Python than using classes like P and without using (lambda) functions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

南城追梦 2025-01-11 03:50:02

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

P(is_divisible_by_13) & P(is_palindrome)

甚至

P(is_divisible_by_13) & is_palindrome

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

为了完整起见,您还可以覆盖就地变体(__iand____ior__)和右侧变体(__rand__、< code>__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]

You can override the & (bitwise AND) operator in Python by adding an __and__ method to the P class. You could then 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 | (bitwise OR) operator by adding an __or__ method and the ~ (bitwise negation) operator by adding a __not__ method. Note that you cannot override the built-in and, or and not operator, so this is probably as close to your goal as possible. You still need to have a P instance as the leftmost argument.

For sake of completeness, you may also override the in-place variants (__iand__, __ior__) and the right-side variants (__rand__, __ror__) of these operators.

Code example (untested, feel free to correct):

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)

One more trick to bring you closer to point-free 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 tag 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]
始于初秋 2025-01-11 03:50:02

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

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

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

Basically, your approach seems to be the only feasible one in Python. There's a python module on github using roughly the same mechanism to implement point-free function composition.

I have not used it, but at a first glance his solution looks a bit nicer (because he uses decorators and operator overloading where you use a class and __call__).

But other than that it's not technically point-free code, it's just "point-hidden" if you will. Which may or may not be enough for you.

×眷恋的温暖 2025-01-11 03:50:02

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

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

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

yields

1001
2002
3003
4004
5005
6006
7007
8008
9009
走走停停 2025-01-11 03:50:02

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

Python already has a way of combining two functions: lambda. You can easily make your own compose and multiple compose 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)))
吲‖鸣 2025-01-11 03:50:02

这将是我的解决方案:

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

That 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
感情洁癖 2025-01-11 03:50:02

这是一个基于 Ramda.js 库的解决方案,该库具有 allPass、< code>anyPass 和 complement 组合器专门用于此目的。以下是 Python 3.10 中实现的函数:

from typing import Callable, TypeVar

T = TypeVar('T')


def any_pass(predicates: list[Callable[[T], bool]]) -> Callable[[T], bool]:
    def inner(value: T) -> bool:
        for predicate in predicates:
            if predicate(value):
                return True
        return False
    return inner


def all_pass(predicates: list[Callable[[T], bool]]) -> Callable[[T], bool]:
    def inner(value: T) -> bool:
        for predicate in predicates:
            if not predicate(value):
                return False
        return True
    return inner


def complement(predicate: Callable[[T], bool]) -> Callable[[T], bool]:
    def inner(value: T) -> bool:
        return not predicate(value)
    return inner


if __name__ == '__main__':

    def is_divisible_by_13(n: int) -> bool:
        return n % 13 == 0

    def is_palindrome(x: int) -> bool:
        return str(x) == str(x)[::-1]

    nums = list(range(1000, 10000))
    assert (list(filter(all_pass([is_divisible_by_13, is_palindrome]), nums)) 
            == [1001, 2002, 3003, 4004, 5005, 6006, 7007, 8008, 9009])

    assert (list(filter(any_pass([is_divisible_by_13, is_palindrome]), nums))[:9] 
            == [1001, 1014, 1027, 1040, 1053, 1066, 1079, 1092, 1105])

    assert (list(filter(complement(is_palindrome), nums))[:9]
            == [1000, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009])

Here's a solution based on the Ramda.js library which has the allPass, anyPass, and complement combinators especially for this purpose. Here are those functions implemented in Python 3.10:

from typing import Callable, TypeVar

T = TypeVar('T')


def any_pass(predicates: list[Callable[[T], bool]]) -> Callable[[T], bool]:
    def inner(value: T) -> bool:
        for predicate in predicates:
            if predicate(value):
                return True
        return False
    return inner


def all_pass(predicates: list[Callable[[T], bool]]) -> Callable[[T], bool]:
    def inner(value: T) -> bool:
        for predicate in predicates:
            if not predicate(value):
                return False
        return True
    return inner


def complement(predicate: Callable[[T], bool]) -> Callable[[T], bool]:
    def inner(value: T) -> bool:
        return not predicate(value)
    return inner


if __name__ == '__main__':

    def is_divisible_by_13(n: int) -> bool:
        return n % 13 == 0

    def is_palindrome(x: int) -> bool:
        return str(x) == str(x)[::-1]

    nums = list(range(1000, 10000))
    assert (list(filter(all_pass([is_divisible_by_13, is_palindrome]), nums)) 
            == [1001, 2002, 3003, 4004, 5005, 6006, 7007, 8008, 9009])

    assert (list(filter(any_pass([is_divisible_by_13, is_palindrome]), nums))[:9] 
            == [1001, 1014, 1027, 1040, 1053, 1066, 1079, 1092, 1105])

    assert (list(filter(complement(is_palindrome), nums))[:9]
            == [1000, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009])

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文