2012-08-29 18 views
18

(da non confondere con itertools.chain)metodo di concatenamento in python

Stavo leggendo la seguente: http://en.wikipedia.org/wiki/Method_chaining

La mia domanda è: qual è il modo migliore per attuare il metodo concatenamento in Python?

Ecco il mio tentativo:

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 è se chiamate method(*args) modifica self.o ma non restituisce None. (allora dovrei restituire self o restituire cosa restituisce method(*args)).

Qualcuno ha modi migliori per implementare il concatenamento? Ci sono probabilmente molti modi per farlo.

Devo solo supporre che un metodo restituisca sempre None in modo che possa sempre restituire self.o?

+0

(nota che non sono sicuro che il metodo di concatenamento debba essere usato in python ma sono comunque interessato) –

+0

Dovresti usare [pure funzioni] (http://en.wikipedia.org/wiki/Pure_function) in modo che i metodi non modificano 'self.o' direttamente, ma restituiscono la versione modificata. Inoltre, '__getattr__' dovrebbe restituire un'istanza di catena. – reubano

risposta

16

C'è una libreria Pipe molto interessante che potrebbe essere la risposta alla tua domanda. Per esempio ::

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

+1 Dopo essersi abituato al LINQ di C#, 'Pipe' è la prima cosa che importa in Python. (Bisogna fare attenzione con 'da Pipe import *' comunque) – Kos

+4

In ogni caso, 'Pipe' è per la composizione di funzioni - passando il risultato della funzione A come argomento per la funzione B. Il concatenamento è (di solito) sul ritorno dello stesso oggetto da diverse chiamate per fare diverse modifiche in una catena. JQuery lo ha reso molto popolare. – Kos

+1

Vero. Ma credo che il chaining non debba essere usato in Python come viene usato in JavaScript. –

4

Non ci sarà alcun modo generale di consentire la concatenazione di qualsiasi metodo di qualsiasi oggetto, poiché non si può sapere quale tipo di valore restituisce quel metodo e perché senza sapere come funziona quel particolare metodo. I metodi potrebbero restituire None per qualsiasi motivo; non sempre significa che il metodo ha modificato l'oggetto. Allo stesso modo, i metodi che restituiscono un valore potrebbero non restituire un valore che può essere incatenato. Non c'è modo di concatenare un metodo come list.index: fakeList.index(1).sort() non può avere molte speranze di funzionare, perché l'intero punto di index è restituisce un numero, e quel numero significa qualcosa, e non può essere ignorato solo per concatenare l'originale oggetto.

Se stai solo scherzando con i tipi built-in di Python per concatenare alcuni metodi specifici (come ordinare e rimuovere), è meglio semplicemente avvolgere questi particolari metodi in modo esplicito (sovrascrivendoli nella classe wrapper), invece di cercando di fare un meccanismo generale con __getattr__.

+0

Grazie - hai ragione. Non pensavo fosse possibile essere generale.Il meglio che posso pensare è di lasciare che il programmatore decida se vogliono o meno concatenarsi. PER ESEMPIO. Di default non restituisce se stesso (restituisce solo ciò che il metodo restituisce) ma se il programmatore è andato esplicitamente list_.chain.sort() potrebbe restituire list_ anziché None. –

+3

@robertking: Giusto, l'accordo generale è che il concatenamento del metodo non è qualcosa che puoi facilmente attaccare agli oggetti esistenti. Fondamentalmente devi progettare una classe con in mente il metodo di concatenamento perché funzioni correttamente. – BrenBarn

13

E 'possibile se si utilizza solo pure functions in modo che i metodi non modificano self.data direttamente, ma invece restituiscono la versione modificata. Devi anche restituire le istanze di ritorno Chainable.

Ecco un esempio utilizzando collection pipelining con le liste:

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

usare in questo modo:

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] 

Nota che .chain si riferisce a itertools.chain e non l'OP del chain.

Problemi correlati