2012-02-07 20 views
8

tengo algunos predicados, por ejemplo:Pointfree combinación función en Python

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

y quiere combinar lógicamente como en:

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

La pregunta ahora es: ¿Puede dicha combinación puede escribir en una pointfree estilo, tales como:

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

Esto tiene, por supuesto, no el efecto deseado, porque el valor de verdad de las funciones lambda es True y and y or son operadores de cortocircuito. Lo más parecido que se me ocurrió fue definir una clase P que es un contenedor de predicado simple que implementa __call__() y tiene los métodos and_() y or_() para combinar predicados. La definición de P es el siguiente:

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 

Con P ahora puedo crear un nuevo predicado que es una combinación de predicados como esto:

P(is_divisible_by_13).and_(is_palindrome) 

que es equivalente a la función lambda anteriormente. Esto se acerca más a lo que me gustaría tener, pero tampoco es sin puntos (los puntos son ahora los predicados en lugar de sus argumentos). Ahora la segunda pregunta es: ¿hay una forma mejor o más corta (tal vez sin paréntesis y puntos) para combinar predicados en Python que usar clases como P y sin usar las funciones (lambda)?

+0

Parece que está haciendo un gran esfuerzo para que un lenguaje no funcional se comporte como un lenguaje funcional. ¿Estás atado a Python? –

+0

@Eric: Sí, tipo de. Mi código de Python está incrustado en un proyecto de C++ y no puedo cambiar a otro idioma. –

Respuesta

8

Puede anular el operador & (AND bit a bit) en Python agregando un método __and__ a la clase P. A continuación, podría escribir algo como:

P(is_divisible_by_13) & P(is_palindrome) 

o incluso

P(is_divisible_by_13) & is_palindrome 

Del mismo modo, se puede anular la (OR bit a bit) operador | mediante la adición de un método __or__ y la ~ (negación bit a bit) del operador mediante la adición un método __not__. Tenga en cuenta que no puede anular el operador integrado and, or y not, por lo que probablemente sea lo más cercano posible a su objetivo. Todavía necesita tener una instancia P como el argumento más a la izquierda.

Para mayor abundamiento, también puede anular las variantes en el lugar (__iand__, __ior__) y las variantes del lado derecho (__rand__, __ror__) de estos operadores.

ejemplo de código (no probado, no dude en corregir):

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) 

un truco más para lograr que más cerca del punto libre de nirvana es el siguiente decorador:

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 

entonces usted puede etiquetar sus predicados con el decorador predicate para hacerlos una instancia de P automáticamente:

@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

No estoy seguro de si quiero sobrecargar a los operadores _bitwise_ con otro significado, pero la idea del decorador me parece muy interesante. ¡Gracias! –

+0

@ FrankS.Thomas: ¿Las operaciones bit a bit tienen algún sentido con tus predicados? De lo contrario, no hay nada de malo en sobrecargarlos. –

3

Básicamente, su enfoque parece ser el único factible en Python. Hay un python module on github que utiliza aproximadamente el mismo mecanismo para implementar la composición de funciones sin puntos.

No lo he usado, pero a primera vista su solución se ve un poco mejor (porque usa decoradores y sobrecarga de operador donde usa una clase y __call__).

Pero, aparte de eso, técnicamente no es un código de punto, es solo "punto oculto" si se quiere. Que puede o no ser suficiente para ti.

2

podría utilizar el 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) 

produce

1001 
2002 
3003 
4004 
5005 
6006 
7007 
8008 
9009 
+0

Este es probablemente el hack de Python del año para 2012. Al menos para mí. –

+0

@ Tamás: La receta ya es el mejor truco de 2005 :-) También, es por Ferdinand Jamitzky, no por mí. Solo (abu?) Lo usé para mi respuesta. – WolframH

1

Esa sería mi solución:

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 

y este es mi resultado:

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 ya tiene una forma de combinar dos funciones: lambda. Puede crear fácilmente sus propias funciones de redacción y redacción múltiple:

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

debe ser 'reduce (compose2, ff)', o simplemente 'reduce (lambda f, g: lambda x: f (g (x)), ff)' – modular

Cuestiones relacionadas