Шаг 75.
Python: сборник рецептов.
Итераторы и генераторы. Превращение вложенной последовательности в плоскую

    На этом шаге мы рассмотрим решение этой задачи.

Задача

    У вас есть вложенная последовательность, и вы хотите превратить ее в один плоский список значений.

Решение

    Это легко решается с помощью рекурсивного генератора с инструкцией yield from. Например:

>>> from collections.abc import Iterable
>>> def flatten(items, ignore_types=(str, bytes)):
	for x in items:
		if isinstance(x, Iterable) and not isinstance(x, ignore_types):
			yield from flatten(x)
		else:
			yield x

			
>>> items = [1, 2, [3, 4, [5, 6], 7], 8]
>>> # Выводит 1 2 3 4 5 6 7 8
>>> for x in flatten(items):
	print(x)

	
1
2
3
4
5
6
7
8
>>>

    В этой программе isinstance(x, Iterable) просто проверяет, является ли элемент итерируемым объектом. Если это так, то yield from используется в качестве некой подпрограммы, чтобы выдать все его значения. Конечный результат - одна последовательность без вложенности.

    Дополнительный аргумент ignore_types и проверка not isinstance(x, ignore_types) нужны для предотвращения определения строк и байтов как итерируемых последовательностей, разбиения их на отдельные символы. Это позволяет вложенным спискам строк работать так, как большинство людей этого и ожидает:

>>> items = ['Dave', 'Paula', ['Thomas', 'Lewis']]
>>> for x in flatten(items):
	print(x)

	
Dave
Paula
Thomas
Lewis
>>> 


Обсуждение

    Инструкция yield from - отличный способ написания генераторов, которые вызывают другие генераторы в качестве подпроцедур. Без использования этой инструкции вам придется вставить в код дополнительный цикл. Например:

def flatten(items, ignore_types=(str, bytes)): 
    for x in items:
        if isinstance(x, Iterable) and not isinstance(x, ignore_types): 
            for i in flatten(x): 
                yield i
        else:
            yield x

    Хотя это незначительное изменение, инструкция yield from просто приятнее и делает код чище.

    Как было отмечено, дополнительная проверка на строки и байты нужна для предотвращения их разбивки на отдельные символы. Если есть еще какие-то типы, которые вы не хотите раскрывать, вы просто можете передать другие значения в ignoretypes.

    Стоит отметить, что yield from играет более важную роль в продвинутых программах, использующих корутины (сопрограммы) и основанную на генераторах многопоточность.

    На следующем шаге мы рассмотрим последовательное итерирование по слитым отсортированным итерируемым объектам.




Предыдущий шаг Содержание Следующий шаг