2012-07-05 19 views
8

Mam podklasy obiektu, który implementuje dynamiczny wysyłkę __ iter __ pomocą generatora buforowania (mam również sposobu unieważniania ITER cache) tak:__iter __() realizowane jako generator

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 

Wydaje Działaj ... Czy są jakieś błędy, o których może nie być świadomy? Czy robię coś absurdalnego?

+2

będę przynajmniej używać [ 'set'] (http://docs.python.org/library/stdtypes.html#set) zamiast' dict' do 'iter_seen' struktury. –

+0

Hm, co by mnie to naprawdę zyskało? Ponieważ nie potrzebuję ustawionej algebry, czy dyktowanie nie byłoby bardziej rozsądną i lekką implementacją? –

+2

zamień 'dla _ w iter (cokolwiek)' z 'dla _ w czymkolwiek '. Nigdy nie potrzebujesz 'iter' wewnątrz' for' oświadczenia – jfs

Odpowiedz

1

Wygląda na bardzo kruche podejście. Wystarczy zmienić dowolne z __slots, __dc_list, __iter_cache podczas aktywnej iteracji, aby umieścić obiekt w niespójnym stanie.

Musisz zabezpieczyć się przed zmianą obiektu podczas iteracji lub wygenerować wszystkie elementy pamięci podręcznej jednocześnie i zwrócić kopię listy.

+0

Prawda. __slots zmienia się tylko przez __setitem__ lub __delitem__, mogę łatwo zabraniać tych operacji (raise exc), gdy generator jest aktywny. __dc_list jest aktualnie ustawiona/zmieniona tylko w __init__, jeśli dodam metodę do jej aktualizacji (najprawdopodobniej), muszę skopiować semantykę zakazującą z __slots. __iter_cache nie jest problemem. Jest on zawsze aktualizowany tylko przez __iter__ i dopiero po wyliczeniu całej sekwencji. –

+1

wiele równoczesnych iteracji jest podobnych do wielowątkowości w niektórych aspektach. O wiele łatwiej jest o tym myśleć, jeśli obiekty są niezmienne. Wyobraźmy sobie trzy iteracje: pierwsza zapełnia pamięć podręczną, trzecia używa pamięci podręcznej, druga rozpoczyna się trochę przed ustawieniem pamięci podręcznej, ale po zmianie obiektu (może pojawić się nowsza wartość, a następnie trzecia iteracja, która została uruchomiona po niej) – jfs

+1

kocham tę stronę. Twoje szybkie i trafne odpowiedzi znacznie zwiększyły moją wiedzę z Py, dzięki chłopaki! –

2

zwraca obiekt iteratora. Iterator samych przedmiotów są wymagane do tworzenia następujące dwa sposoby, które razem tworzą protokół iteracyjnej:

iterator.__iter__() 

Zwraca sam obiekt iteracyjnej.

iterator.next() 

Umożliwia powrót następnego elementu z pojemnika.

Dokładnie to ma każdy generator. Więc nie bój się żadnych skutków ubocznych.

+3

Wykonywanie metody '__iter __()' obiektu kontenera za pomocą generatora przy użyciu jednej lub kilku instrukcji 'yield' jest powszechnym skrótem, który pozwala uniknąć konieczności jawnego definiowania i kodowania oddzielnej klasy iteratora i jego metod. – martineau

2

Lepiej może oddzielić iterację obiektu od buforowania zwracanych wartości. Uprościłoby to proces iteracji i umożliwiłoby łatwe kontrolowanie, na przykład, jak buforowanie jest wykonywane, a także czy jest włączone czy nie.

Innym ważnym powodem jest to, że Twój kod nie przewiduje przewidywalnie sytuacji, w której obiekt, na którym wykonywane są iteracje, zmienia się między kolejnymi wywołaniami metody. Jednym z prostych sposobów radzenia sobie z tym byłoby zapełnienie zawartości pamięci podręcznej całkowicie przy pierwszym połączeniu, a następnie tylko yield, co zawiera dla każdego połączenia - i udokumentowanie zachowania.

+0

Dobra uwaga na temat separacji, tak naprawdę to wypróbowałem :-) –

0

To, co robisz, jest ważne, ale dziwne. Co to jest __slots lub __dc_list? Generalnie lepiej opisać zawartość obiektu w nazwie atrybutu, a nie w jego typie (np. Self.users zamiast self.u_list).

Możesz użyć mojego dekoratora LazyProperty, aby uprościć to znacznie.

Wystarczy udekorować swoją metodę za pomocą @LazyProperty. Będzie się to nazywać po raz pierwszy, a dekorator zastąpi atrybut atrybutem wyników. Jedynym wymogiem jest to, że wartość jest powtarzalna; nie zależy od stanu zmiennego. Masz również to wymaganie w swoim bieżącym kodzie, ze swoim self .__ iter_cache.

def __iter__(self) 
    return self.__iter 

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

Dziwne, możliwe. sloty są własnymi (przesłoniętymi) atrybutami obiektu, a dc_list jest listą obiektów prototypowych, z których (rekurencyjnie) kopiują gniazda. Próbuję zaimplementować coś podobnego do mechanizmu delegacji Self w Py. –

+0

'__iter__' musi zwracać iterator, krotka nie jest. – jfs

+0

Dobra rzecz, chciałbym zmienić __iter, aby po prostu zwrócić my_generator –