2012-02-07 25 views
8

mam pewne predykaty, np:kombinacja Pointfree funkcja w Pythonie

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

i chcą logicznie połączyć je jako w:

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

Pytanie brzmi teraz: Czy taka kombinacja jest napisane w pointfree style, takie jak:

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

To oczywiście nie jest pożądany efekt, ponieważ wartość prawdy funkcji lambda to True i and i or są operatorami zwierającymi. Najbliższą rzeczą, jaką wymyśliłem, było zdefiniowanie klasy P, która jest prostym kontenerem predykatów, który implementuje __call__() i ma metody łączenia predykatów. Definicja P następująco:

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 

z P mogę się tworzyć nowe orzeczenie, które jest połączeniem predykatach tak:

P(is_divisible_by_13).and_(is_palindrome) 

, która jest równoważna do powyższej funkcji lambda. Jest to bliższe temu, co chciałbym mieć, ale nie jest ono również bezprzedmiotowe (punkty są teraz samymi orzeczeniami zamiast ich argumentami). Teraz drugie pytanie brzmi: czy istnieje lepsza lub krótsza droga (może bez nawiasów i kropek) do łączenia predykatów w Pythonie niż używanie klas takich jak P i bez używania funkcji (lambda)?

+0

Wydaje się starasz strasznie ciężko, aby niefunkcjonalny język zachowywać się jak język funkcjonalnej. Czy jesteś przywiązany do Pythona? –

+0

@Eric: Tak, rodzaj. Mój kod Pythona jest osadzony w projekcie C++ i nie mogę po prostu przejść do innego języka. –

Odpowiedz

8

Można zastąpić operatora & (bitowe AND) w języku Python, dodając metodę __and__ do klasy P. Następnie można napisać coś takiego:

P(is_divisible_by_13) & P(is_palindrome) 

lub nawet

P(is_divisible_by_13) & is_palindrome 

Podobnie można zastąpić (bitowe OR) operator | dodając __or__ sposobu i ~ (bitową negację) operator dodając metoda __not__. Zauważ, że nie możesz przesłonić wbudowanego operatora and, or i not, więc jest to prawdopodobnie najbliższe Twojemu celowi. Nadal musisz mieć instancję P jako argument z lewej strony.

Dla Uzupełniająco można również zastąpić wariantów w miejscu (__iand__, __ior__) i warianty prawym boczne (__rand__, __ror__) tych podmiotów.

przykładem Code (niesprawdzone, swobodnie je poprawiaj):

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) 

Jeszcze jedna sztuczka, aby przynieść wam bliżej do punktu wolne nirwana jest następujący dekorator:

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 

Następnie można oznaczyć twoje predykaty z predicate dekoratora aby im wystąpienie P się automatycznie:

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

Nie jestem pewien, czy chcę przeciążyć _bitwise_ operatorów o innym znaczeniu, ale pomysł dekoratora jest bardzo interesujący. Dzięki! –

+0

@ FrankS.Thomas: Czy operacje bitowe mają sens ze swoimi predykatami? Jeśli nie, nic złego w przeciążeniu ich. –

3

Zasadniczo twoje podejście wydaje się być jedynym możliwym do wykonania w Pythonie. Jest python module on github przy użyciu mniej więcej tego samego mechanizmu do realizacji bezstratnej kompozycji funkcji.

Nie użyłem go, ale na pierwszy rzut oka jego rozwiązanie wygląda trochę ładniej (ponieważ używa on dekoratorów i przeciążenia operatora, gdy używasz klasy i __call__).

Ale poza tym, że nie jest technicznie bezwzrokowym kodem, jest po prostu "ukryty w punkcie", jeśli zechcesz. Co może lub nie wystarczy dla ciebie.

2

można użyć 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) 

daje

1001 
2002 
3003 
4004 
5005 
6006 
7007 
8008 
9009 
+0

To prawdopodobnie Hack Pythona roku 2012. Przynajmniej dla mnie. –

+0

@ Tamás: Przepis jest już najlepszym hackem w 2005 roku :-) Również, to jest Ferdinand Jamitzky, nie ja. Ja tylko (abu?) Użyłem go do mojej odpowiedzi. – WolframH

1

To byłoby moje rozwiązanie:

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 

I to jest mój wynik:

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 
1

Python ma już sposób na połączenie dwóch funkcji: lambda. Można łatwo tworzyć własne funkcje tworzenia i wielokrotnego komponować:

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

powinno to być 'reduce (compose2, ff)' lub just 'reduce (lambda f, g: lambda x: f (g (x)), ff) – modular

Powiązane problemy