2012-07-05 6 views
8

Ho una sottoclasse oggetto che implementa una distribuzione dinamica __ iter __ utilizzando un generatore di cache (Ho anche un metodo per invalidare la cache iter) in questo modo:__iter __() implementato come un generatore

def __iter__(self): 
    print("iter called") 
    if self.__iter_cache is None: 
     iter_seen = {} 
     iter_cache = [] 
     for name in self.__slots: 
      value = self.__slots[name] 
      iter_seen[name] = True 
      item = (name, value) 
      iter_cache.append(item) 
      yield item   
     for d in self.__dc_list: 
      for name, value in iter(d): 
       if name not in iter_seen: 
        iter_seen[name] = True 
        item = (name, value) 
        iter_cache.append(item) 
        yield item 
     self.__iter_cache = iter_cache 
    else: 
     print("iter cache hit") 
     for item in self.__iter_cache: 
      yield item 

sembra sta lavorando ... Ci sono dei trucchi che potrei non essere a conoscenza? Sto facendo qualcosa di ridicolo?

+2

vorrei almeno usare un [ 'set'] (http://docs.python.org/library/stdtypes.html#set) invece di un' dict' per la struttura 'iter_seen'. –

+0

Hm, cosa mi guadagnerebbe davvero? Dal momento che non ho bisogno dell'algebra, non sarebbe un'implementazione più ragionevole e leggera? –

+2

sostituisci 'per _ in iter (qualunque cosa)' con 'for _ in qualunque'. Non hai mai bisogno di 'iter' all'interno di' for' statement – jfs

risposta

1

Sembra un approccio molto fragile. È sufficiente modificare qualsiasi __slot, __dc_list, __iter_cache durante l'iterazione attiva per mettere l'oggetto in uno stato incoerente.

È necessario o vietare la modifica dell'oggetto durante l'iterazione o generare tutti gli elementi della cache contemporaneamente e restituire una copia dell'elenco.

+0

Vero. __slots è cambiato solo da __setitem__ o __delitem__, potrei facilmente vietare quelle operazioni (raise exc) quando un generatore è attivo. __dc_list è attualmente impostato/modificato solo in __init__, se aggiungo un metodo per aggiornarlo (probabilmente lo farò) ho bisogno di copiare la semantica proibita da __slots. __iter_cache non è un problema. Viene sempre aggiornato da __iter__ e solo dopo che l'intera sequenza è stata enumerata. –

+1

più iterazioni simultanee sono simili al multithreading in alcuni aspetti. È molto più facile ragionare su di esso se gli oggetti sono immutabili. Immagina tre iterazioni: il primo popola la cache, il terzo usa la cache, il secondo inizia un po 'di tempo prima che la cache sia impostata, ma dopo che l'oggetto è cambiato (potrebbe vedere valori più recenti poi la terza iterazione che è stata avviata) – jfs

+1

I amo questo sito Le tue risposte rapide e immediate hanno notevolmente aumentato la mia conoscenza di Py, grazie ragazzi! –

2

container.__iter__() restituisce un oggetto iteratore. Gli stessi oggetti iteratori vengono richiesti per supportare i seguenti due metodi, che formano insieme il protocollo iteratore:

iterator.__iter__() 

restituisce l'oggetto iteratore.

iterator.next() 

Restituire l'elemento successivo dal contenitore.

Questo è esattamente ciò che ha ogni generatore. Quindi non temere alcun effetto collaterale.

+3

Fare in modo che un generatore di oggetti '__iter __()' metodi un generatore usando una o più istruzioni 'yield' è una scorciatoia comune che evita di dover definire e codificare esplicitamente una classe iteratore separata e i suoi metodi. – martineau

2

Potrebbe essere preferibile separare l'iterazione dell'oggetto dalla memorizzazione nella cache dei valori restituiti. Ciò semplificherebbe il processo di iterazione e ti consentirà di controllare facilmente come viene eseguita la memorizzazione nella cache e se è abilitata o meno, ad esempio.

Un altro possibilmente considerazione importante è il fatto che il codice non gestirà in modo prevedibile la situazione in cui l'oggetto che viene iterato viene cambiato tra chiamate successive al metodo. Un modo semplice per gestirlo sarebbe quello di popolare completamente il contenuto della cache alla prima chiamata, e quindi solo il yield ciò che contiene per ogni chiamata e documentare il comportamento.

+0

Buon punto sulla separazione, in effetti l'ho provato prima :-) –

0

Quello che stai facendo è valido anche se strano. Che cos'è un __slots o un __dc_list ?? Generalmente è meglio descrivere il contenuto del tuo oggetto in un nome di attributo, piuttosto che nel suo tipo (es .: self.users piuttosto che self.u_list).

È possibile utilizzare il mio decoratore LazyProperty per semplificare notevolmente questo aspetto.

Basta decorare il tuo metodo con @LazyProperty. Sarà chiamato la prima volta e il decoratore sostituirà quindi l'attributo con i risultati. L'unico requisito è che il valore sia ripetibile; non dipende dallo stato mutabile. Hai anche quel requisito nel tuo codice attuale, con te stesso .__ iter_cache.

def __iter__(self) 
    return self.__iter 

@LazyProperty 
def __iter(self) 
    def my_generator(): 
     yield whatever 
    return tuple(my_generator()) 
+0

Stranamente, forse. gli slot sono gli attributi propri (sostituiti) degli oggetti e dc_list è un elenco di oggetti prototipo da cui copiare (ricorsivamente) gli slot. Sto cercando di implementare qualcosa come la delegazione di Self's mechanishm in Py. –

+0

'__iter__' deve restituire un iteratore, la tupla non è uno. – jfs

+0

Buon punto, cambierei __iter per restituire semplicemente my_generator –