2010-10-03 9 views
7

Yeni bir jeneratörü birleştirmek istediğim bir dizi Python jeneratörü var. Bunu, bir grup yield ifadesini kullanarak elle yazılmış bir jeneratörle kolayca yapabilirim.itertools veya elle yazılmış jeneratör - hangisi tercih edilir?

Diğer taraftan, itertools modülü, bu gibi şeyler için yapılır ve bana ihtiyacım olan jeneratörü oluşturmanın pythonik yolu, bu itertools modülünün çeşitli yineleyicileri birbirine bağlamaktır. Bununla birlikte, eldeki problemde, oldukça karmaşık hale gelir (jeneratörün bir tür durumu korumaya ihtiyacı vardır - örneğin, ilk veya daha sonraki öğelerin işlenip işlenmediği ---, i-inci çıktısı daha fazladır.). i-inci giriş öğelerindeki koşullara bağlıdır ve oluşturulan girdi listesine eklenmeden önce çeşitli giriş listeleri farklı şekilde işlenmelidir.

Sorunumu çözecek standart yineleyiciler bileşimi olarak --- nedeniyle kaynak kod aşağı yazma tek boyutlu doğası için --- neredeyse anlaşılmaz, ben elle yazılmış jeneratör fonksiyonları (temel ve daha gelişmiş durumlarda) standart itertools jeneratörler kullanarak herhangi bir avantaj olup olmadığını merak ediyorum. vakaların% 90’ında ve yazılı versiyonları okumak çok daha kolay --- muhtemelen daha zorlayıcı stilleri nedeniyle zincirleme yineleyicileri işlevsel stili ile karşılaştırıldığında. a ve b aynı uzunlukta (giriş verileri) iki Iterables olalım: zaman sorunu göstermek için

DÜZENLEME

, burada (oyuncak) bir örnektir. a'un öğeleri tam sayılardan oluşur, b'un öğeleri, tek tek öğeleri dizeleri olan yinelenebilir öğelerdir.

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) 
: Ben jeneratör ifadeleri ve itertools modülü kullanarak işlevsel bir tarzda aynı programı yazarsan, ben böyle bir şey ile bitirmek

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 

: çıktı şu jeneratör fonksiyonunun çıkışına karşılık gelmelidir

Örnek

>>> 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'] 

Bu bir olası olduğu İlk çözümümden daha zarif bir şekilde daha zariftir, ancak bir keresinde bir kez yazılmamış bir kod parçası gibi görünür. Jeneratörümü yazmanın bu yolunun yapması gereken bir avantajı olup olmadığını merak ediyorum.

Not: Python'daki anahtar kelime miktarını en aza indirgemek için, "for", "if" ve "else" gibi anahtar sözcüklerin, ifadelerde kullanım için geri dönüştürülmüş olduğunu, işlevsel çözümle ilgili sorunumun bir kısmını tahmin ediyorum. Böylece, ifadedeki yerleşimleri alışmaya başlar (z for x in a for y in x for z in y jeneratör ifadesinde sipariş, en azından bana göre, klasik for döngüsündeki sıralamaya göre daha az doğal görünür: for x in a: for y in x: for z in y: yield z).

+0

PS: Ben sadece jeneratör işlevi sopa var öğrendim. Bir şey yapabilir, jeneratör ifadeleriyle yapamam, yani İstisnaları yakalamak. – Marc

cevap

7

Bazı profiller yaptım ve normal jeneratör işlevi ikinci jeneratörünüzden veya uygulamamdan çok daha hızlı.

$ 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 

Aynı zamanda en okunaklı olması da olur, bu yüzden onunla giderim.Söyledikten sonra yine de çözümümüzü yayınlayacağım çünkü itertoollerle yapabileceğiniz fonksiyonel programlama türünün daha temiz bir örneği olduğunu düşünüyorum (yine de optimal değil, normal jeneratör fonksiyonunu içebiliyormuş gibi hissediyorum). Ben referans için

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) 

test böyledir) üzerinde kesmek gerekir:

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

Yukarıdaki DÜZENLEME bölümünün altına bir kod gönderdiniz. – Marc

+0

@Marc, hala "Paramater:" ve "i" ayrı ayrı ya da bir tuple olarak verilip verilmediğine dair gerçek karışıklığı düzeltmediniz. – aaronasterling

+0

Kodun ilk parçasını düzeltdim (karışıklık için özür dilerim ama ilk jeneratör işlevini yazarken yerine yazmam için aceleyle yazdım.) "Verim x; verim y" ve "(x, y) ": Çıktı başka bir döngü tarafından tüketilmek zorundadır, bu yüzden benim amacım için çıktının" (x, y) "veya" iter ((x, y)) "gibi bir şey olup olmadığını farketmez. . Bununla birlikte, yukarıdaki örneğimde, işlev jeneratörü2, işlev üretecinin çıktısı gibi davranan bir jeneratörü çıkarır. Ben jeneratör2 çıktıları tuples göremiyorum (kodumu sistemimde çalıştırdım.) – Marc