Python'da iki jeneratöre nasıl katılabilirsiniz?


188

Aşağıdaki kodu değiştirmek istiyorum

for directory, dirs, files in os.walk(directory_1):
    do_something()

for directory, dirs, files in os.walk(directory_2):
    do_something()

bu koda:

for directory, dirs, files in os.walk(directory_1) + os.walk(directory_2):
    do_something()

Hatayı alıyorum:

+: 'jeneratör' ve 'jeneratör' için desteklenmeyen işlem gören türler

Python'da iki jeneratöre nasıl katılabilirsiniz?


1
Ayrıca Python'un bu şekilde çalışmasını istiyorum. Aynı hatayı aldım!
Adam Kurkiewicz

Yanıtlar:


236

Bence itertools.chain()yapmalıyım.


5
Unutulmamalıdır ki, dönüş değeri itertools.chain()bir types.GeneratorTypeörnek döndürmez . Kesin tipin çok önemli olması durumunda.
Riga

1
neden çalışılmış bir örnek de yazmıyorsunuz?
Charlie Parker

76

Kod örneği:

from itertools import chain

def generator1():
    for item in 'abcdef':
        yield item

def generator2():
    for item in '123456':
        yield item

generator3 = chain(generator1(), generator2())
for item in generator3:
    print item

10
Neden bu örneği zaten var olan ve son derece doğru değerlendirilen itertools.chain()cevaba eklemiyorsunuz ?
Jean-François Corbett

52

Python'da (3.5 veya üstü) şunları yapabilirsiniz:

def concat(a, b):
    yield from a
    yield from b

7
Çok pitonik.
Ramazan Polat

9
Daha genel: def chain(*iterables): for iterable in iterables: yield from iterable( Çalıştırdığınızda defve forsatırlarını ayrı satırlara koyun .)
wjandrea

Her şey den mi bir herhangi bir şey daha önce elde b vermiştir ya da dönüşümlü ediliyor?
sorunlu memur

@problemofficer Yup. Sadece bir yineleyici aolmasa bile, ondan her şey elde edilene kadar kontrol edilir b. TypeErrorİçin bolmak değil, bir yineleyici sonra çıkacaktır.
GeeTransit

36

Basit örnek:

from itertools import chain
x = iter([1,2,3])      #Create Generator Object (listiterator)
y = iter([3,4,5])      #another one
result = chain(x, y)   #Chained x and y

3
Neden bu örneği zaten var olan ve son derece doğru değerlendirilen itertools.chain()cevaba eklemiyorsunuz ?
Jean-François Corbett

Bu doğru değil, çünkü itertools.chainbir yineleyici döndürüyor, bir jeneratör değil.
David J.

Sadece yapamaz chain([1, 2, 3], [3, 4, 5])mısın?
Corman

10

İtertools.chain.from_iterable ile aşağıdakileri yapabilirsiniz:

def genny(start):
  for x in range(start, start+3):
    yield x

y = [1, 2]
ab = [o for o in itertools.chain.from_iterable(genny(x) for x in y)]
print(ab)

Gereksiz bir liste kavrayışı kullanıyorsunuz. Ayrıca genny, zaten bir jeneratör döndürdüğünde gereksiz bir jeneratör ifadesi kullanıyorsunuz . list(itertools.chain.from_iterable(genny(x)))çok daha özlü.
Corman

! İst kavrayışı, soruya göre iki jeneratörü yaratmanın kolay bir yoluydu. Belki de cevabım bu açıdan biraz kıvrıktır.
andrew pate

Sanırım bu cevabı mevcut cevaplara eklememin sebebi başa çıkacak birçok jeneratöre sahip olanlara yardım etmekti.
andrew pate

Bu kolay bir yol değil, birçok daha kolay yol var. Mevcut bir jeneratörde jeneratör ifadelerinin kullanılması performansı düşürür ve listyapıcı liste kavrayışından çok daha okunabilir. Yöntemin bu konularda çok daha okunaksız.
Corman

Corman, liste yapıcısının gerçekten daha okunabilir olduğuna katılıyorum. Yine de 'çok daha kolay yollarınızı' görmek güzel olurdu ... Bence wjandrea'nın yukarıdaki yorumu itertools.chain.from_iterable ile aynı şeyi yapıyor gibi görünüyor onları yarışmak ve en hızlı kim olduğunu görmek iyi olurdu.
andrew pate

8

Burada yuvalanmış s ile bir jeneratör ifadesi kullanıyor for:

a = range(3)
b = range(5)
ab = (i for it in (a, b) for i in it)
assert list(ab) == [0, 1, 2, 0, 1, 2, 3, 4]

2
Küçük bir açıklama incitmezdi.
Ramazan Polat

Python'un belgelerinden daha iyi açıklayabileceğimi sanmıyorum.
Alexey

(Üretici ifadeleriyle ilgili belgeler cevabımla bağlantılıdır. Dokümanları kopyalayıp cevabıma yapıştırmak için iyi bir neden görmüyorum.)
Alexey

3

Bir de açma operatörü kullanabilirsiniz *:

concat = (*gen1(), *gen2())

NOT: 'Tembel olmayan' tekrarlanabilirler için en verimli şekilde çalışır. Farklı türden kavramalarla da kullanılabilir. Jeneratör concat için tercih edilen yol @Uduse cevabından olacaktır.


1

Jeneratörleri ayrı tutmak, ancak aynı zamanda yine de yinelemek istiyorsanız zip () kullanabilirsiniz:

NOT: Yineleme iki jeneratörün daha kısa kısmında durur

Örneğin:

for (root1, dir1, files1), (root2, dir2, files2) in zip(os.walk(path1), os.walk(path2)):

    for file in files1:
        #do something with first list of files

    for file in files2:
        #do something with second list of files

0

Diyelim ki jeneratörlere (gen1 ve gen 2) ihtiyacımız var ve her ikisinin de sonucunu gerektiren bazı ekstra hesaplamalar yapmak istiyoruz. Bu tür bir fonksiyonun / hesaplamanın sonucunu, üzerine dönebileceğimiz bir jeneratör döndüren harita yöntemi ile döndürebiliriz.

Bu senaryoda, işlev / hesaplamanın lambda işlevi aracılığıyla uygulanması gerekir. Zor kısım, haritanın içinde yapmayı amaçladığımız şey ve lambda işlevi.

Önerilen çözümün genel şekli:

def function(gen1,gen2):
        for item in map(lambda x, y: do_somethin(x,y), gen1, gen2):
            yield item

0

Tüm bu karmaşık çözümler ...

sadece yap:

for dir in director_1, directory_2:
    for directory, dirs, files in os.walk(dir):
        do_something()

Her iki jeneratöre de gerçekten "katılmak" istiyorsanız, şunu yapın:

for directory, dirs, files in 
        [x for osw in [os.walk(director_1), os.walk(director_2)] 
               for x in osw]:
    do_something()

0

Kullanıcı "wjandrea" tarafından yapılan yorumlarda önerildiği gibi, en iyi çözüm olduğunu söyleyebilirim

def concat_generators(*args):
    for gen in args:
        yield from gen

Döndürülen türü değiştirmez ve gerçekten pitoniktir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.