2010-10-03 7 views
7

Mam wiele generatorów Python, które chcę połączyć w nowy generator. Mogę to łatwo zrobić za pomocą odręcznego generatora, używając kilku instrukcji: yield.itertools lub odręczny generator - co jest lepsze?

Z drugiej strony, moduł itertools jest stworzony dla takich rzeczy i wydaje mi się, że pytonowy sposób tworzenia potrzebnego generatora polega na podłączeniu różnych iteratorów tego modułu itertools.

Jednak w danym problemie szybko staje się to dość skomplikowane (generator musi utrzymywać pewien stan - np. Czy pierwszy lub późniejszy produkt jest przetwarzany ---, i-te wyjście dalej zależy od warunków na i-tych elementach wejściowych, a różne listy wejściowe muszą być przetwarzane inaczej, zanim zostaną dołączone do wygenerowanej listy:

Ponieważ skład standardowych iteratorów, które rozwiązałyby mój problem, jest następujący: do jednowymiarowego zapisu kodu źródłowego - prawie niezrozumiałe, zastanawiam się, czy istnieją jakiekolwiek zalety używania standardowych generatorów itertools w porównaniu z ręcznie pisanymi funkcjami generatora (w podstawowych i bardziej zaawansowanych przypadkach). w 90% przypadków h i napisane wersje są znacznie łatwiejsze do odczytania --- prawdopodobnie ze względu na ich bardziej bezwzględny styl w porównaniu do funkcjonalnego stylu łączenia iteratorów.

EDIT

Aby zilustrować mój problem, tu jest (zabawka) Przykład: Niech a i b dwa iterables tej samej długości (dane wejściowe). Pozycje a składają się z liczb całkowitych, elementy z b są iterables same, których poszczególne elementy są łańcuchami. Wyjście powinno odpowiadać wyjściu następującą funkcję generatora:

from itertools import * 
def generator(a, b): 
    first = True 
    for i, s in izip(a, b): 
     if first: 
      yield "First line" 
      first = False 
     else: 
      yield "Some later line" 
     if i == 0: 
      yield "The parameter vanishes." 
     else: 
      yield "The parameter is:" 
      yield i 
     yield "The strings are:" 
     comma = False 
     for t in s: 
      if comma: 
       yield ',' 
      else: 
       comma = True 
      yield t 

Gdybym zanotować ten sam program w stylu funkcjonalnym za pomocą wyrażeń generatora i modułu itertools, ja skończyć z czymś takim:

from itertools import * 
def generator2(a, b): 
    return (z for i, s, c in izip(a, b, count()) 
      for y in (("First line" if c == 0 else "Some later line",), 
         ("The parameter vanishes.",) if i == 0 
         else ("The parameter is:", i), 
         ("The strings are:",), 
         islice((x for t in s for x in (',', t)), 1, None)) 
      for z in y) 

Przykład

>>> a = (1, 0, 2), ("ab", "cd", "ef") 
>>> print([x for x in generator(a, b)]) 
['First line', 'The parameter is:', 1, 'The strings are:', 'a', ',', 'b', 'Some later line', 'The parameter vanishes.', 'The strings are:', 'c', ',', 'd', 'Some later line', 'The parameter is:', 2, 'The strings are:', 'e', ',', 'f'] 
>>> print([x for x in generator2(a, b)]) 
['First line', 'The parameter is:', 1, 'The strings are:', 'a', ',', 'b', 'Some later line', 'The parameter vanishes.', 'The strings are:', 'c', ',', 'd', 'Some later line', 'The parameter is:', 2, 'The strings are:', 'e', ',', 'f'] 

to POSS na przykład bardziej elegancko niż moje pierwsze rozwiązanie, ale wygląda na to, że napisałem "nie raz, zrozum, a później". Zastanawiam się, czy ten sposób pisania mojego generatora ma wystarczające zalety, że należy to zrobić.

PS: Domyślam się, że częścią mojego problemu z funkcjonalnym rozwiązaniem jest to, że aby zminimalizować ilość słów kluczowych w Pythonie, niektóre słowa kluczowe, takie jak "dla", "jeśli" i "else" zostały poddane recyklingowi do wykorzystania w wyrażeniach aby ich umieszczenie w wyrażeniu zaczęło się przyzwyczajać (porządek w wyrażeniu generatora z for x in a for y in x for z in y wygląda, przynajmniej dla mnie, mniej naturalny niż porządek w klasycznej pętli for: for x in a: for y in x: for z in y: yield z).

+0

PS: właśnie dowiedziałem się, że mam się trzymać z funkcją generatora. Może zrobić jedną rzecz, nie mogę zrobić z wyrażeń generujących, mianowicie wychwytywaniem wyjątków. – Marc

Odpowiedz

7

Zrobiłem profilowanie i regularna funkcja generatora jest znacznie szybsza niż drugi generator lub moja implementacja.

$ python -mtimeit -s'import gen; a, b = gen.make_test_case()' 'list(gen.generator1(a, b))' 
10 loops, best of 3: 169 msec per loop 

$ python -mtimeit -s'import gen; a, b = gen.make_test_case()' 'list(gen.generator2(a, b))' 
10 loops, best of 3: 489 msec per loop 

$ python -mtimeit -s'import gen; a, b = gen.make_test_case()' 'list(gen.generator3(a, b))' 
10 loops, best of 3: 385 msec per loop 

Zdarza się też, że jest najbardziej czytelny, więc myślę, że z tym skorzystam.Mimo to, nadal będę publikować moje rozwiązanie, ponieważ uważam, że jest to czystszy przykład programowania funkcjonalnego, które można wykonać za pomocą itertools (chociaż nadal nie jest optymalny, wydaje mi się, że powinien być w stanie zapalić regularną funkcję generatora. będę siekać na nim)

def generator3(parameters, strings): 
    # replace strings with a generator of generators for the individual charachters 
    strings = (it.islice((char for string_char in string_ for char in (',', string_char)), 1, None) 
       for string_ in strings) 

    # interpolate strings with the notices 
    strings = (it.chain(('The strings are:',), string_) for string_ in strings) 

    # nest them in tuples so they're ate the same level as the other generators 
    separators = it.chain((('First line',),), it.cycle((('Some later line',),))) 

    # replace the parameters with the appropriate tuples 
    parameters = (('The parameter is:', p) if p else ('The parameter vanishes.',) 
        for p in parameters) 

    # combine the separators, parameters and strings 
    output = it.izip(separators, parameters, strings) 

    # flatten it twice and return it 
    output = it.chain.from_iterable(output) 
    return it.chain.from_iterable(output) 

odsyłającym przypadek testowy jest:

def make_test_case(): 
    a = [i % 100 for i in range(10000)] 
    b = [('12345'*10)[:(i%50)+1] for i in range(10000)] 
    return a, b 
+0

Umieściłem kod w sekcji EDIT powyżej. – Marc

+0

@Marc, nadal nie naprawiłeś prawdziwego zamieszania, czyli tego, czy chcesz "The Paramater is:", i 'i' ustąpił osobno lub jako krotka. – aaronasterling

+0

Poprawiono pierwszy fragment kodu (przykro mi z powodu pomyłki, ale wpisałem pierwszą funkcję generatora w pośpiechu, co spowodowało, że napisałem, zamiast zamiast tego.) Co do różnicy między "yield x; yield y" i "(x, y) ": Dane wyjściowe mają zostać wykorzystane przez inną pętlę for, więc dla moich celów nie ma znaczenia, czy dane wyjściowe mają postać" (x, y) "lub" iter ((x, y)) " . Jednak w powyższym przykładzie funkcja generator2 generuje generator, który zachowuje się dokładnie tak samo jak wyjście generatora funkcji. Nie widzę, aby generator2 generował krotki (uruchomiłem kod w moim systemie). – Marc

Powiązane problemy