Як приєднатися до двох генераторів в Python?


187

Я хочу змінити наступний код

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

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

до цього коду:

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

Я отримую помилку:

непідтримувані типи операндів для +: 'generator' та 'generator'

Як приєднатися до двох генераторів в Python?


1
Я також хотів би, щоб Python працював таким чином. Отримала абсолютно таку ж помилку!
Адам Куркевич

Відповіді:


236

Я думаю, що itertools.chain()слід це зробити.


5
Слід пам’ятати, що повернене значення itertools.chain()не повертає types.GeneratorTypeекземпляр. Про всяк випадок вирішальне значення має саме тип.
Рига

1
чому б ви також не записали відпрацьований приклад?
Чарлі Паркер

75

Приклад коду:

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
Чому б не додати цей приклад до вже існуючої, висококваліфікованої itertools.chain()відповіді?
Жан-Франсуа Корбетт

51

У Python (3.5 або більше) ви можете:

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

7
Стільки пітонічного.
Рамазан Полат

9
Більш загальне: def chain(*iterables): for iterable in iterables: yield from iterable(Розмістіть defі forна окремих рядках, коли ви запускаєте його.)
wjandrea

Все чи з дали , перш ніж що - небудь з б в дали , або вони бути чергувалися?
problemofficer

@problemofficer Yup. Тільки aперевіряється , поки все не отримували від нього, навіть якщо bце не итератор. TypeErrorДля bне будучи итератор прийде пізніше.
GeeTransit

36

Простий приклад:

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
Чому б не додати цей приклад до вже існуючої, висококваліфікованої itertools.chain()відповіді?
Жан-Франсуа Корбетт

Це не зовсім правильно, оскільки itertools.chainповертає ітератор, а не генератор.
Девід Дж.

Ви не можете просто зробити chain([1, 2, 3], [3, 4, 5])?
Корман

10

За допомогою itertools.chain.from_iterable ви можете робити такі речі, як:

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)

Ви використовуєте непотрібне розуміння списку. Ви також використовуєте зайвий вираз генератора, gennyколи він вже повертає генератор. list(itertools.chain.from_iterable(genny(x)))набагато стисліший.
Корман

Розуміння! Ist було простим способом створення двох генераторів, відповідно до питання. Можливо, моя відповідь у цьому відношенні трохи перекручена.
андрю паштет

Я думаю, що причиною того, що я додав цю відповідь до існуючих, було допомогти тим, у кого трапляється багато генераторів.
андрю паштет

Це не простий спосіб, є багато легших способів. Використання генераторних виразів на наявному генераторі знизить продуктивність, а listконструктор набагато читає, ніж розуміння списку. У цьому плані ваш метод набагато нечитабельніший.
Корман

Корман, я згоден, ваш конструктор списків справді читабельніший. Хоча було б побачити ваші "багато простіших способів", хоча ... Я думаю, що коментар wjandrea вище виглядає так само, як itertools.chain.from_iterable, було б добре змагатись з ними і бачити, хто найшвидший.
андрю паштет

8

Тут використовується вираз генератора з вкладеними fors:

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
Невелике пояснення не зашкодило б.
Рамазан Полат

Ну, я не думаю, що я можу пояснити це краще, ніж документація Python.
Олексій

(Документація на вирази генератора пов'язана з моєю відповіддю. Я не бачу вагомих причин скопіювати та вставити документацію у свою відповідь.)
Олексій

3

Можна також використовувати оператор розпакування *:

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

ПРИМІТКА: Найефективніше працює для "не лінивих" ітерабелей. Також може використовуватися при різних видах розумінь. Кращим способом для генератора concat був би відповідь від @Uduse


1

Якщо ви хочете тримати генератори окремими, але все ж повторювати їх одночасно, ви можете використовувати zip ():

ПРИМІТКА: Ітерація зупиняється на коротшому з двох генераторів

Наприклад:

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

Скажімо, що ми маємо генератори (gen1 та gen 2), і ми хочемо виконати додатковий розрахунок, який вимагає результатів обох. Ми можемо повернути результат такої функції / обчислення за допомогою методу map, який, у свою чергу, повертає генератор, на який ми можемо циклічно працювати.

У цьому сценарії функцію / обчислення потрібно реалізувати за допомогою лямбда-функції. Хитра частина - це те, що ми прагнемо зробити всередині карти та її лямбда-функції.

Загальна форма запропонованого рішення:

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

0

Усі ці складні рішення ...

просто роби:

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

Якщо ви дійсно хочете "приєднатися" до обох генераторів, тоді робіть:

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

0

Я б сказав, що, як це запропоновано в коментарях користувача "wjandrea", найкраще рішення

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

Він не змінює повернений тип і справді є пітонічним.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.