В чем разница между chain и chain.from_iterable в itertools?

Я не смог найти в Интернете ни одного действительного примера, где бы я мог увидеть разницу между ними и почему выбрать один над другим.


person user1994660    schedule 21.02.2013    source источник


Ответы (6)


Первый принимает 0 или более аргументов, каждый из которых является итерируемым, второй принимает один аргумент, который, как ожидается, создаст итерируемые объекты:

from itertools import chain

chain(list1, list2, list3)

iterables = [list1, list2, list3]
chain.from_iterable(iterables)

но iterables может быть любым итератором, который дает итерации:

def gen_iterables():
    for i in range(10):
        yield range(i)

itertools.chain.from_iterable(gen_iterables())

Использование второй формы обычно удобно, но поскольку она лениво перебирает входные итераторы, это также единственный способ связать бесконечное количество конечных итераторов:

def gen_iterables():
    while True:
        for i in range(5, 10):
            yield range(i)

chain.from_iterable(gen_iterables())

Приведенный выше пример даст вам итерацию, которая дает циклический шаблон чисел, который никогда не остановится, но никогда не будет потреблять больше памяти, чем требуется для одного вызова range().

person Martijn Pieters    schedule 21.02.2013
comment
я до сих пор не могу понять. Можете ли вы дать мне выходную разницу и вариант использования в практической ситуации, где что использовать - person user1994660; 22.02.2013
comment
@ user1994660: разницы в выводе нет. Это разница input. Это упрощает использование определенных входов. - person Martijn Pieters; 22.02.2013
comment
@ user1994660: я использую вторую форму в этот ответ. - person Martijn Pieters; 22.02.2013
comment
@user1994660 user1994660: хороший вариант использования для второй формы: идиома Python для объединения (сглаживания) бесконечной итерации конечных итераций? - person Martijn Pieters; 22.02.2013
comment
@ user1994660: Запустите этот код: # Return an iterator of iterators def it_it(): return iter( [iter( [11, 22] ), iter( [33, 44] )] ) print( list(itertools.chain.from_iterable(it_it())) ) print( list(itertools.chain(it_it())) ) print( list(itertools.chain(*it_it())) ) Лучше всего первый; второй не обращается к вложенным итераторам, возвращает итераторы вместо нужных чисел; третий выдает правильный вывод, НО он не совсем ленив: * заставляет создавать все итераторы. Для этого тупого ввода это не имеет значения. - person ToolmakerSteve; 20.12.2013
comment
Обратите внимание, что если итерации не слишком велики, вы также можете сделать itertools.chain(*iterables) - person balki; 15.02.2014
comment
@MartijnPieters, не возражали бы, если бы я процитировал вас по поводу единственного способа связать бесконечное количество конечных итераторов? - person Ryan Haining; 09.06.2014
comment
Недавно я добавил chain.from_iterable в проект C++. - person Ryan Haining; 09.06.2014

Я не смог найти ни одного действительного примера ... где я могу увидеть разницу между ними [chain и chain.from_iterable] и почему выбрать один над другим

Принятый ответ тщательный. Для тех, кто ищет быстрое приложение, рассмотрите возможность объединения нескольких списков:

list(itertools.chain(["a", "b", "c"], ["d", "e"], ["f"]))
# ['a', 'b', 'c', 'd', 'e', 'f']

Возможно, вы захотите повторно использовать эти списки позже, поэтому создайте итерацию списков:

iterable = (["a", "b", "c"], ["d", "e"], ["f"])

Попытка

Однако передача итерации в chain дает несглаженный результат:

list(itertools.chain(iterable))
# [['a', 'b', 'c'], ['d', 'e'], ['f']]

Почему? Вы передали один элемент (кортеж). chain нужен каждый список отдельно.


Решения

Когда это возможно, вы можете распаковать итерируемый объект:

list(itertools.chain(*iterable))
# ['a', 'b', 'c', 'd', 'e', 'f']

list(itertools.chain(*iter(iterable)))
# ['a', 'b', 'c', 'd', 'e', 'f']

В более общем случае используйте .from_iterable (поскольку он также работает с бесконечными итераторами):

list(itertools.chain.from_iterable(iterable))
# ['a', 'b', 'c', 'd', 'e', 'f']

g = itertools.chain.from_iterable(itertools.cycle(iterable))
next(g)
# "a"
person pylang    schedule 17.07.2018

Они делают очень похожие вещи. Для небольшого количества итераций itertools.chain(*iterables) и itertools.chain.from_iterable(iterables) выполняются аналогично.

Ключевое преимущество from_iterables заключается в способности обрабатывать большое (потенциально бесконечное) количество итераций, поскольку все они не обязательно должны быть доступны во время вызова.

person BiGYaN    schedule 10.07.2017
comment
Кто-нибудь знает, распаковывает ли оператор * iterables лениво? - person Rotareti; 25.03.2018
comment
@Rotareti, да, он распаковывается лениво (по одному), но в данном случае itertools.chain(*iterables) — это вызов функции. Все аргументы должны присутствовать во время вызова. - person BiGYaN; 27.03.2018
comment
Это правда? Судя по коду CPython, это похоже на stackoverflow.com/a/62513808/610569. - person alvas; 22.06.2020
comment
@alvas Попробуйте изменить количество элементов на очень большое; в диапазоне от 10_000 до 1_000_000, и вы увидите, что from_iterables становится быстрее. - person BiGYaN; 30.06.2020

Другой способ увидеть это:

chain(iterable1, iterable2, iterable3, ...) предназначен для тех случаев, когда вы уже знаете, какие у вас есть итерации, поэтому вы можете записать их в виде этих аргументов, разделенных запятыми.

chain.from_iterable(iterable) используется, когда ваши итерации (например, iterable1, iterable2, iterable3) получаются из другой итерации.

person R. Navega    schedule 20.08.2018

Расширение ответа @martijn-pieters

Хотя доступ к внутренним элементам в итерируемом объекте остается прежним и с точки зрения реализации,

  • itertools_chain_from_iterable (т.е. chain.from_iterable в Python) и
  • chain_new (т.е. chain в Python)

в реализации CPython оба являются утиными типами chain_new_internal


Есть ли какие-либо преимущества для оптимизации при использовании chain.from_iterable(x), где x – это итерация или итерация; и основная цель состоит в том, чтобы в конечном итоге использовать плоский список элементов?

Мы можем попробовать сравнить его с:

import random
from itertools import chain
from functools import wraps
from time import time

from tqdm import tqdm

def timing(f):
    @wraps(f)
    def wrap(*args, **kw):
        ts = time()
        result = f(*args, **kw)
        te = time()
        print('func:%r args:[%r, %r] took: %2.4f sec' % (f.__name__, args, kw, te-ts))
        return result
    return wrap

def generate_nm(m, n):
    # Creates m generators of m integers between range 0 to n.
    yield iter(random.sample(range(n), n) for _ in range(m))
    

def chain_star(x):
    # Stores an iterable that will unpack and flatten the list of list.
    chain_x = chain(*x)
    # Consumes the items in the flatten iterable.
    for i in chain_x:
        pass

def chain_from_iterable(x):
    # Stores an iterable that will unpack and flatten the list of list.
    chain_x = chain.from_iterable(x)
    # Consumes the items in the flatten iterable.
    for i in chain_x:
        pass


@timing
def versus(f, n, m):
  f(generate_nm(n, m))

P/S: Тест запущен... Ждем результатов.


Полученные результаты

цепочка_звезда, м = 1000, n = 1000

for _ in range(10):
    versus(chain_star, 1000, 1000)

[вне]:

func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 1000, 1000), {}] took: 0.6494 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 1000, 1000), {}] took: 0.6603 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 1000, 1000), {}] took: 0.6367 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 1000, 1000), {}] took: 0.6350 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 1000, 1000), {}] took: 0.6296 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 1000, 1000), {}] took: 0.6399 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 1000, 1000), {}] took: 0.6341 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 1000, 1000), {}] took: 0.6381 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 1000, 1000), {}] took: 0.6343 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 1000, 1000), {}] took: 0.6309 sec

chain_from_iterable, m=1000, n=1000

for _ in range(10):
    versus(chain_from_iterable, 1000, 1000)

[вне]:

func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 1000, 1000), {}] took: 0.6416 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 1000, 1000), {}] took: 0.6315 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 1000, 1000), {}] took: 0.6535 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 1000, 1000), {}] took: 0.6334 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 1000, 1000), {}] took: 0.6327 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 1000, 1000), {}] took: 0.6471 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 1000, 1000), {}] took: 0.6426 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 1000, 1000), {}] took: 0.6287 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 1000, 1000), {}] took: 0.6353 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 1000, 1000), {}] took: 0.6297 sec

chain_star, м=10000, n=1000

func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 10000, 1000), {}] took: 6.2659 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 10000, 1000), {}] took: 6.2966 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 10000, 1000), {}] took: 6.2953 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 10000, 1000), {}] took: 6.3141 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 10000, 1000), {}] took: 6.2802 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 10000, 1000), {}] took: 6.2799 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 10000, 1000), {}] took: 6.2848 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 10000, 1000), {}] took: 6.3299 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 10000, 1000), {}] took: 6.2730 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 10000, 1000), {}] took: 6.3052 sec

chain_from_iterable, m=10000, n=1000

func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 10000, 1000), {}] took: 6.3129 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 10000, 1000), {}] took: 6.3064 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 10000, 1000), {}] took: 6.3071 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 10000, 1000), {}] took: 6.2660 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 10000, 1000), {}] took: 6.2837 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 10000, 1000), {}] took: 6.2877 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 10000, 1000), {}] took: 6.2756 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 10000, 1000), {}] took: 6.2939 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 10000, 1000), {}] took: 6.2715 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 10000, 1000), {}] took: 6.2877 sec

chain_star, м=100000, n=1000

func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 100000, 1000), {}] took: 62.7874 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 100000, 1000), {}] took: 63.3744 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 100000, 1000), {}] took: 62.5584 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 100000, 1000), {}] took: 63.3745 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 100000, 1000), {}] took: 62.7982 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 100000, 1000), {}] took: 63.4054 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 100000, 1000), {}] took: 62.6769 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 100000, 1000), {}] took: 62.6476 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 100000, 1000), {}] took: 63.7397 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 100000, 1000), {}] took: 62.8980 sec

chain_from_iterable, m=100000, n=1000

for _ in range(10):
    versus(chain_from_iterable, 100000, 1000)

[вне]:

func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 100000, 1000), {}] took: 62.7227 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 100000, 1000), {}] took: 62.7717 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 100000, 1000), {}] took: 62.7159 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 100000, 1000), {}] took: 62.7569 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 100000, 1000), {}] took: 62.7906 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 100000, 1000), {}] took: 62.6211 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 100000, 1000), {}] took: 62.7294 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 100000, 1000), {}] took: 62.8260 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 100000, 1000), {}] took: 62.8356 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 100000, 1000), {}] took: 62.9738 sec

chain_star, м=500000, n=1000

for _ in range(3):
    versus(chain_from_iterable, 500000, 1000)

[вне]:

func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 500000, 1000), {}] took: 314.5671 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 500000, 1000), {}] took: 313.9270 sec
func:'versus' args:[(<function chain_star at 0x7f5c7188ef28>, 500000, 1000), {}] took: 313.8992 sec

chain_from_iterable, m=500000, n=1000

for _ in range(3):
    versus(chain_from_iterable, 500000, 1000)

[вне]:

func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 500000, 1000), {}] took: 313.8301 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 500000, 1000), {}] took: 313.8104 sec
func:'versus' args:[(<function chain_from_iterable at 0x7f5c7188eb70>, 500000, 1000), {}] took: 313.9440 sec
person alvas    schedule 22.06.2020

Другой способ взглянуть на это — использовать chain.from_iterable.

когда у вас есть итерация итераций, например вложенная итерация (или составная итерация), и вы используете цепочку для простых итераций

person Chyanit Singh    schedule 20.02.2020