Was ist der Unterschied zwischen chain und chain.from_iterable in itertools?


71

Ich konnte im Internet kein gültiges Beispiel finden, in dem ich den Unterschied zwischen ihnen und dem Grund für die Auswahl erkennen kann.

Antworten:


86

Das erste nimmt 0 oder mehr Argumente an, jedes ist iterierbar, das zweite nimmt ein Argument, von dem erwartet wird, dass es die iterablen erzeugt:

from itertools import chain

chain(list1, list2, list3)

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

aber iterableskann jeder Iterator sein, die die Iterables ergibt:

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

itertools.chain.from_iterable(gen_iterables())

Die Verwendung der zweiten Form ist normalerweise ein Fall der Bequemlichkeit, aber da sie die Eingabe-Iterables träge durchläuft, ist dies auch die einzige Möglichkeit, eine unendliche Anzahl endlicher Iteratoren zu verketten:

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

chain.from_iterable(gen_iterables())

Das obige Beispiel gibt Ihnen eine Iterable, die ein zyklisches Muster von Zahlen ergibt, das niemals aufhört, aber niemals mehr Speicher verbraucht, als für einen einzelnen range()Anruf erforderlich ist.


4
Ich kann es immer noch nicht bekommen. Können Sie mir den Ausgabedifferenz- und Anwendungsfall in der praktischen Situation geben, wo was zu verwenden ist
user1994660

14
@ user1994660: Es gibt keinen Ausgabedifferenz. Es ist ein Eingabedifferenz . Dies erleichtert die Verwendung bestimmter Eingänge.
Martijn Pieters

@ user1994660: Ich verwende das zweite Formular in dieser Antwort .
Martijn Pieters

1
@ user1994660: Führen Sie diesen Code aus: # 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())) ) Der erste ist der beste; Der zweite kommt nicht zu den verschachtelten Iteratoren, sondern gibt Iteratoren anstelle der gewünschten Zahlen zurück. Der dritte erzeugt die korrekte Ausgabe, ist jedoch nicht vollständig faul: Das "*" hat die Erstellung aller Iteratoren erzwungen. Für diese dumme Eingabe spielt das keine Rolle.
ToolmakerSteve

1
Beachten Sie, dass, wenn die iterables nicht zu groß ist, Sie auch tun könnenitertools.chain(*iterables)
balki

9

Ich konnte kein gültiges Beispiel finden ... wo ich den Unterschied zwischen ihnen [ chainund chain.from_iterable] sehen kann und warum ich eines über das andere wählen sollte

Die akzeptierte Antwort ist gründlich. Wenn Sie eine schnelle Anwendung suchen, sollten Sie mehrere Listen reduzieren:

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

Möglicherweise möchten Sie diese Listen später wiederverwenden, damit Sie Listen iterieren können:

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

Versuch

Wenn Sie jedoch eine iterable to übergeben, erhalten Sie chainein nicht abgeflachtes Ergebnis:

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

Warum? Sie haben einen Artikel (ein Tupel) übergeben. chainbraucht jede Liste separat.


Lösungen

Wenn möglich, können Sie eine iterable entpacken:

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

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

Im Allgemeinen verwenden .from_iterable(da es auch mit unendlichen Iteratoren funktioniert):

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

g = itertools.chain.from_iterable(itertools.cycle(iterable))
next(g)
# "a"

7

Sie machen sehr ähnliche Dinge. Für eine kleine Anzahl von Iterables itertools.chain(*iterables)und itertools.chain.from_iterable(iterables)ähnliche Leistung.

Der Hauptvorteil von from_iterablesliegt in der Fähigkeit, eine große (möglicherweise unendliche) Anzahl von Iterables zu verarbeiten, da zum Zeitpunkt des Aufrufs nicht alle verfügbar sein müssen.


Weiß jemand, ob der *Bediener träge auspackt iterables?
Rotareti

1
@ Rotareti, ja, es wird faul (einzeln) entpackt, aber in diesem Fall itertools.chain(*iterables)handelt es sich um einen Funktionsaufruf. Alle Argumente müssen zum Zeitpunkt des Aufrufs vorhanden sein.
BiGYaN

Ist das wahr? Aus dem CPython-Code geht hervor, dass es sich um denselben stackoverflow.com/a/62513808/610569
alvas

@alvas Versuchen Sie, die Anzahl der Elemente auf sehr groß zu ändern. im Bereich von 10_000 bis 1_000_000 und Sie werden sehen, dass das from_iterablesschneller wird.
BiGYaN

3

Eine andere Art, es zu sehen:

chain(iterable1, iterable2, iterable3, ...) Dies ist der Fall, wenn Sie bereits wissen, welche iterablen Elemente Sie haben, damit Sie sie als durch Kommas getrennte Argumente schreiben können.

chain.from_iterable(iterable) ist für den Fall, dass Ihre iterables (wie iterable1, iterable2, iterable3) von einem anderen iterable erhalten werden.


2

Erweitern der Antwort von @ martijn-pieters

Obwohl der Zugriff auf die inneren Elemente in der Iterable derselbe bleibt und in Bezug auf die Implementierung

  • itertools_chain_from_iterable(dh chain.from_iterablein Python) und
  • chain_new(dh chainin Python)

In der CPython-Implementierung gibt es beide Ententypen von chain_new_internal


Gibt es Optimierungsvorteile bei der Verwendung chain.from_iterable(x), bei denen xes sich um eine Iterierbarkeit oder eine Iterierbarkeit handelt? und der Hauptzweck ist es, letztendlich die abgeflachte Liste der Elemente zu verbrauchen?

Wir können versuchen, es mit folgenden Kriterien zu vergleichen:

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: Benchmark läuft ... Warten auf die Ergebnisse.


Ergebnisse

chain_star, m = 1000, n = 1000

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

[aus]:

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)

[aus]:

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, m = 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, m = 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)

[aus]:

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, m = 500000, n = 1000

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

[aus]:

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)

[aus]:

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

0

Eine andere Möglichkeit, es zu betrachten, ist die Verwendung von chain.from_iterable

Wenn Sie eine Iterable von Iterables wie eine verschachtelte Iterable (oder eine zusammengesetzte Iterale) haben und die Kette für einfache Iterables verwenden

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.