2012-08-29 29 views
18

(que no debe confundirse con itertools.chain)método de encadenamiento en Python

estaba leyendo el siguiente: http://en.wikipedia.org/wiki/Method_chaining

Mi pregunta es: cuál es la mejor manera de poner en práctica el método de encadenamiento en Python?

Aquí es mi intento:

class chain(): 
    def __init__(self, my_object): 
     self.o = my_object 

    def __getattr__(self, attr): 
     x = getattr(self.o, attr) 
     if hasattr(x, '__call__'): 
      method = x 
      return lambda *args: self if method(*args) is None else method(*args) 
     else: 
      prop = x 
      return prop 

list_ = chain([1, 2, 3, 0]) 
print list_.extend([9, 5]).sort().reverse() 

""" 
C:\Python27\python.exe C:/Users/Robert/PycharmProjects/contests/sof.py 
[9, 5, 3, 2, 1, 0] 
""" 

Un problema es si llama method(*args) modifica self.o pero no vuelve None. (entonces debo devolver self o devolver lo que method(*args) devuelve).

¿Alguien tiene mejores formas de implementar el encadenamiento? Probablemente haya muchas maneras de hacerlo.

¿Debo asumir que un método siempre devuelve None por lo que siempre puedo devolver self.o?

+0

(tenga en cuenta que no estoy seguro de si el método de encadenamiento se debe utilizar en Python, pero todavía estoy interesado) –

+0

Debe usar [funciones puras] (http://en.wikipedia.org/wiki/Pure_function) para que los métodos no modifican 'self.o' directamente, sino que devuelven la versión modificada. Además, '__getattr__' debería devolver una instancia de cadena. – reubano

Respuesta

16

Hay una biblioteca Pipe muy interesante que puede ser la respuesta a su pregunta. Por ejemplo ::

seq = fib() | take_while(lambda x: x < 1000000) \ 
      | where(lambda x: x % 2) \ 
      | select(lambda x: x * x) \ 
      | sum() 
+2

+1 Después de acostumbrarme al LINQ de C#, 'Pipe' es lo primero que importo en Python. (Debe tener cuidado con 'from Pipe import *' though) – Kos

+4

De todos modos, 'Pipe' es para funciones de composición - pasa el resultado de la función A como argumento para la función B. El encadenamiento es (generalmente) sobre devolver el mismo objeto varias llamadas para hacer varias modificaciones en una cadena. JQuery lo ha popularizado mucho. – Kos

+1

cierto. Pero creo que el encadenamiento no debería usarse en Python de la misma forma que se usa en JavaScript. –

4

No va a haber ninguna forma general de encadenar ningún método de ningún objeto, ya que no puede saber qué tipo de valor devuelve ese método y por qué, sin saber cómo funciona ese método en particular. Los métodos pueden devolver None por cualquier razón; no siempre significa que el método ha modificado el objeto. Del mismo modo, los métodos que devuelven un valor aún pueden no devolver un valor que pueda ser encadenado. No hay forma de encadenar un método como list.index: fakeList.index(1).sort() no puede haber muchas esperanzas de funcionar, porque todo el punto de index es que devuelve un número, y ese número significa algo, y no se puede ignorar solo para encadenar el original objeto.

Si solo está jugueteando con los tipos integrados de Python para encadenar ciertos métodos específicos (como ordenar y eliminar), es mejor que simplemente ajuste explícitamente esos métodos (anulándolos en su clase contenedora), en lugar de tratando de hacer un mecanismo general con __getattr__.

+0

Gracias - tienes razón. No pensé que fuera posible ser general.Lo mejor que puedo pensar es dejar que el programador decida si quiere encadenar o no. P.EJ. Por defecto no devuelve self (solo devuelve lo que devuelve el método), pero si el programador fue explícitamente list_.chain.sort() podría devolver list_ en lugar de None. –

+3

@robertking: Correcto, el problema general es que el método de encadenamiento no es algo que pueda pasar fácilmente a los objetos existentes. Básicamente, debes diseñar una clase con el método de encadenamiento en mente para que funcione correctamente. – BrenBarn

13

Es posible si utiliza solamente pure functions por lo que los métodos no modifican self.data directamente, sino que devuelven la versión modificada. También debe devolver las instancias de devolución Chainable.

Aquí hay un ejemplo usando collection pipelining con listas:

import itertools 

try: 
    import builtins 
except ImportError: 
    import __builtin__ as builtins 


class Chainable(object): 
    def __init__(self, data, method=None): 
     self.data = data 
     self.method = method 

    def __getattr__(self, name): 
     try: 
      method = getattr(self.data, name) 
     except AttributeError: 
      try: 
       method = getattr(builtins, name) 
      except AttributeError: 
       method = getattr(itertools, name) 

     return Chainable(self.data, method) 

    def __call__(self, *args, **kwargs): 
     try: 
      return Chainable(list(self.method(self.data, *args, **kwargs))) 
     except TypeError: 
      return Chainable(list(self.method(args[0], self.data, **kwargs))) 

utilizar de esta manera:

chainable_list = Chainable([3, 1, 2, 0]) 
(chainable_list 
    .chain([11,8,6,7,9,4,5]) 
    .sorted() 
    .reversed() 
    .ifilter(lambda x: x%2) 
    .islice(3) 
    .data) 
>> [11, 9, 7] 

Tenga en cuenta que se refiere a .chainitertools.chain y no el OP de chain.

Cuestiones relacionadas