Как объединить два генератора в Python?


188

Я хочу изменить следующий код

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()

Я получаю ошибку:

неподдерживаемые типы операндов для +: «генератор» и «генератор»

Как объединить два генератора в Python?


1
Я также хотел бы, чтобы Python работал таким образом. Получилась точно такая же ошибка!
Адам Куркевич

Ответы:


236

Я думаю, itertools.chain()должен сделать это.


5
Следует помнить, что возвращаемое значение itertools.chain()не возвращает types.GeneratorTypeэкземпляр. На всякий случай, точный тип имеет решающее значение.
Рига,

1
почему бы тебе не записать отработанный пример?
Чарли Паркер

76

Пример кода:

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()ответу?
Жан-Франсуа Корбетт

52

В 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 Да. 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)))гораздо более кратким.
Корман

Согласно этому вопросу, понимание было простым способом создания двух генераторов. Может быть, мой ответ немного запутан в этом отношении.
Эндрю Пэйт

Я предполагаю, что причина, по которой я добавил этот ответ к уже существующим, заключалась в том, чтобы помочь тем, кому приходится иметь дело со многими генераторами.
Эндрю Пэйт

Это не легкий путь, есть много простых способов. Использование выражений генератора в существующем генераторе снизит производительность, и 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.