Frage

Ich fange Python zu lernen, und ich habe über Generator-Funktionen kommen, diejenigen, die eine yield-Anweisung in ihnen haben. Ich möchte wissen, welche Arten von Problemen, die diese Funktionen sind wirklich gut zu lösen.

War es hilfreich?

Lösung

Generatoren geben Sie lazy evaluation. Sie verwenden sie über sie eine Näherung durch Iteration, entweder explizit mit ‚für‘ oder implizit, indem sie es auf jede Funktion übergeben oder dass Iterierten konstruieren. Sie können mehrere Elemente von Generatoren denken als Rückkehr, als ob sie eine Liste zurück, sondern sie alle auf die Rückkehr nach ihrer Rückkehr sie one-by-one, und die Generatorfunktion wird angehalten, bis der nächste Punkt angefordert wird.

Generatoren sind gut für große Gruppen von Ergebnissen der Berechnung (insbesondere Berechnungen mit Schleifen selbst), wo man weiß nicht, ob Sie alle Ergebnisse gehen zu müssen, oder wo Sie wollen für alle Ergebnisse des Speichers nicht zuweisen an die selbe Zeit. Oder für Situationen, in denen der Generator verwendet andere Generator oder eine andere Ressource verbraucht, und es ist bequemer, wenn das so spät wie möglich geschehen ist.

Eine andere Verwendung für Generatoren (das ist wirklich das gleiche) ist Rückrufe mit Iteration zu ersetzen. In einigen Situationen mögen Sie eine Funktion eine Menge Arbeit zu tun, und gelegentlich an den Anrufer zurückmelden. Traditionell würden Sie eine Rückruffunktion für diese. Sie übergeben diesen Rückruf an den Work-Funktion und es würde diesen Rückruf in regelmäßigen Abständen rufen. Der Generator Ansatz ist, dass die Arbeit-Funktion (jetzt ein Generator) weiß nichts über den Rückruf, und nur liefert, wann immer es will etwas berichten. Der Anrufer, statt einen separaten Rückruf schreiben und nebenbei, dass die Arbeit-Funktion, macht die ganze Berichterstattung Arbeit in einem kleinen ‚für‘ Schleife um den Generator.

Zum Beispiel, sagen Sie ein ‚Dateisystem suchen‘ Programm geschrieben haben. Man könnte die Suche in seiner Gesamtheit, sammelt die Ergebnisse durchführt und sie dann zu einem Zeitpunkt einer Anzeige. Alle Ergebnisse müssen gesammelt werden würde, bevor Sie die erste zeigten, und alle Ergebnisse im Speicher zur gleichen Zeit sein würde. Oder Sie könnten die Ergebnisse angezeigt werden, während Sie sie finden, die mehr Speicher effizienter und viel freundlicher gegenüber dem Nutzer sein würde. Letzteres könnte, indem man die Ergebnis-Druckfunktion auf die Dateisystem-Suchfunktion durchgeführt werden, oder es könnte nur durch die Herstellung der Suchfunktion einen Generators und Iteration über das Ergebnis durchgeführt werden.

Wenn Sie ein Beispiel für die letzteren beiden Ansätze zu sehen, siehe os.path.walk () (die alte Dateisystem-Walking-Funktion mit Rückruf) und os.walk () (den neuen Dateisystem-Walking-Generator.) Von natürlich, wenn Sie wirklich alle Ergebnisse in einer Liste sammeln wollten, der Generator Ansatz ist trivial zu dem big-Liste Ansatz zu konvertieren:

big_list = list(the_generator)

Andere Tipps

Einer der Gründe Generator zu verwenden ist die Lösung klarer für eine Art von Lösungen zu machen.

Die andere ist, Ergebnisse einer nach dem anderen zu behandeln, bauen riesige Listen der Ergebnisse zu vermeiden, die Sie ohnehin getrennt würden verarbeiten.

Wenn Sie eine Fibonacci-up-to-n-Funktion wie folgt aus:

# function version
def fibon(n):
    a = b = 1
    result = []
    for i in xrange(n):
        result.append(a)
        a, b = b, a + b
    return result

Sie können leichter die Funktion wie folgt schreiben:

# generator version
def fibon(n):
    a = b = 1
    for i in xrange(n):
        yield a
        a, b = b, a + b

Die Funktion ist klarer. Und wenn Sie die Funktion wie folgt aus:

for x in fibon(1000000):
    print x,

in diesem Beispiel, wenn die Generator-Version verwenden, die gesamte 1000000 Artikelliste wird gar nicht erstellt werden, nur einen Wert zu einem Zeitpunkt. Das wäre nicht der Fall sein, wenn die Liste Version, in dem eine Liste würde zuerst erstellt werden.

Sehen Sie die "Motivation" im PEP 255 .

Eine nicht-offensichtliche Verwendung von Generatoren ist unterbrechbare Erstellen von Funktionen, die Sie Dinge wie Update UI oder laufen mehrere Jobs „gleichzeitig“ (verschachtelte, tatsächlich), während nicht mit Threads tun kann.

Ich finde diese Erklärung, die meine Zweifel löscht. Denn es gibt eine Möglichkeit, dass Personen, die nicht wissen Generators auch nicht wissen, über yield

Zurück

Die return-Anweisung ist, wo alle lokalen Variablen zerstört werden und der resultierende Wert wird zurückgegeben (zurück) an den Anrufer. Sollte die gleiche Funktion einige Zeit später genannt wurde, wird die Funktion einen komplett neuen Satz von Variablen erhalten.

Yield

Was aber, wenn die lokalen Variablen nicht weggeworfen, wenn wir eine Funktion verlassen? Dies bedeutet, dass wir resume the function, wo wir aufgehört haben. Dies ist, wo das Konzept des generators eingeführt werden und die yield Anweisung fortgesetzt, wo die function aufhörte.

  def generate_integers(N):
    for i in xrange(N):
    yield i

    In [1]: gen = generate_integers(3)
    In [2]: gen
    <generator object at 0x8117f90>
    In [3]: gen.next()
    0
    In [4]: gen.next()
    1
    In [5]: gen.next()

Das ist also der Unterschied zwischen return und yield Aussagen in Python.

Yield-Anweisung ist, was eine Funktion einer Generatorfunktion macht.

So Generatoren sind ein einfaches und leistungsfähiges Werkzeug für Iteratoren zu schaffen. Sie werden wie normale Funktionen geschrieben, aber sie nutzen die yield Anweisung, wenn sie Daten zurückgeben möchten. Jedes Mal, next () aufgerufen wird, wird der Generator wieder, wo er aufgehört hat (es erinnert alle Datenwerte und welche Aussage wurde zuletzt ausgeführt).

Real World Beispiel

Angenommen, Sie haben 100 Millionen Domains in Ihrer MySQL-Tabelle, und Sie möchten, dass für jede Domain Alexa Rank aktualisieren.

Das erste, was Sie brauchen, ist Ihre Domain-Namen aus der Datenbank auswählen.

Lassen Sie uns sagen, dass Ihre Tabellenname ist domains und Spaltenname ist domain.

Wenn Sie SELECT domain FROM domains es geht um 100 Millionen Zeilen zurückgeben, die viel Speicher verbrauchen wird. Also Ihr Server abstürzen.

So Sie beschlossen, das Programm in den Reihen laufen. Sagen wir unsere Chargengröße ist 1000.

In unserer ersten Partie werden wir die ersten 1000 Zeilen abfragen, überprüfen Alexa Rank für jede Domäne und die Datenbankzeile aktualisieren.

In unserem zweiten Ansatz werden wir uns auf den nächsten 1000 Zeilen arbeiten. In unserem dritten Charge es von 2001 bis 3000 sein wird, und so weiter.

Jetzt brauchen wir eine Generatorfunktion, die unsere Chargen erzeugt.

Hier ist unsere Generatorfunktion:

def ResultGenerator(cursor, batchsize=1000):
    while True:
        results = cursor.fetchmany(batchsize)
        if not results:
            break
        for result in results:
            yield result

Wie Sie sehen können, unsere Funktion die Ergebnisse hält yielding. Wenn Sie das Schlüsselwort return statt yield verwendet wird, dann würde die ganze Funktion beendet werden, sobald es Rückkehr erreicht.

return - returns only once
yield - returns multiple times

Wenn eine Funktion verwendet das Schlüsselwort yield dann ist es ein Generator.

Jetzt können Sie wie folgt durchlaufen:

db = MySQLdb.connect(host="localhost", user="root", passwd="root", db="domains")
cursor = db.cursor()
cursor.execute("SELECT domain FROM domains")
for result in ResultGenerator(cursor):
    doSomethingWith(result)
db.close()

Buffering. Wenn es effizienter ist, Daten in großen Blöcken zu holen, aber es in kleinen Stücken verarbeiten, dann könnte ein Generator helfen:

def bufferedFetch():
  while True:
     buffer = getBigChunkOfData()
     # insert some code to break on 'end of data'
     for i in buffer:    
          yield i

Das oben können Sie ganz einfach trennen Pufferung von der Verarbeitung. Die Funktion Verbraucher können jetzt nur die Werte eins nach dem anderen, ohne sich Gedanken über die Pufferung.

Ich habe festgestellt, dass Generatoren sehr hilfreich sind der Code bei der Säuberung und von Ihnen eine sehr einzigartige Art und Weise Code zu kapseln und modularisieren. In einer Situation, wo Sie etwas brauchen, um ständig Werte ausspucken basierend auf seinen eigenen internen Verarbeitung und wenn das etwas von überall in Ihrem Code (und nicht nur innerhalb einer Schleife oder einem Block zum Beispiel) aufgerufen werden muss, Generatoren sind die Funktion nutzen zu können.

Ein abstraktes Beispiel wäre einen Fibonacci-Zahlengenerator sein, der in einer Schleife lebt nicht und wenn es von überall aufgerufen wird, wird immer die nächste Zahl in der Folge zurück:

def fib():
    first = 0
    second = 1
    yield first
    yield second

    while 1:
        next = first + second
        yield next
        first = second
        second = next

fibgen1 = fib()
fibgen2 = fib()

Jetzt haben Sie zwei Fibonacci-Zahlengenerator Objekte, die Sie überall in Ihrem Code aufrufen können, und sie werden immer immer größere Fibonacci-Zahlen in Folge zurück, wie folgt:

>>> fibgen1.next(); fibgen1.next(); fibgen1.next(); fibgen1.next()
0
1
1
2
>>> fibgen2.next(); fibgen2.next()
0
1
>>> fibgen1.next(); fibgen1.next()
3
5

Die schöne Sache über Generatoren ist, dass sie Zustand verkapseln, ohne durch die Reifen zu schaffen, Objekte zu gehen. Eine Möglichkeit, über sie zu denken, ist als „Funktionen“, die ihren inneren Zustand erinnern.

Ich habe das Fibonacci Beispiel von Python-Generatoren - Was sind sie? und mit einer wenig Phantasie, Sie mit vielen anderen Situationen kommen kann, wo Generatoren für eine große Alternative machen Loops und andere traditionelle Iteration Konstrukte for.

Die einfache Erklärung: Betrachten wir eine for Anweisung

for item in iterable:
   do_stuff()

Eine Menge der Zeit, dass alle Einzelteile in iterable muss nicht von Anfang an dabei sein, können aber im laufenden Betrieb erzeugt werden, wie sie erforderlich sind. Dies kann viel effizienter in beiden sein

  • Raum (Sie müssen nie gleichzeitig alle Elemente speichern) und
  • Zeit (die Iteration kann, bevor alle Elemente fertig sind erforderlich).

Andere Zeiten, Sie wissen nicht einmal, alle Einzelteile vor der Zeit. Zum Beispiel:

for command in user_input():
   do_stuff_with(command)

Sie haben keine Möglichkeit zu wissen, die alle Benutzer-Befehle vorher, aber man kann eine schöne Schleife wie diese verwenden, wenn Sie einen Generator haben Aushändigung Sie Befehle:

def user_input():
    while True:
        wait_for_command()
        cmd = get_command()
        yield cmd

Mit Generatoren können Sie auch haben Iteration über unendliche Folgen, was natürlich nicht möglich ist, wenn sie über Behälter laufen.

Meine Lieblings Anwendungen sind "Filter" und Operationen "reduzieren".

Lassen Sie uns sagen, dass wir eine Datei lesen, und nur die Linien wollen, die mit „##“ beginnen.

def filter2sharps( aSequence ):
    for l in aSequence:
        if l.startswith("##"):
            yield l

Wir können dann die Generatorfunktion in einer richtigen Schleife verwenden

source= file( ... )
for line in filter2sharps( source.readlines() ):
    print line
source.close()

Das reduziert Beispiel ist ähnlich. Lassen Sie uns sagen, dass wir eine Datei, wo wir Blöcke von <Location>...</Location> Linien müssen ausfindig zu machen. [Nicht-Tags HTML, sondern Linien, den Tag-wie zufällig aussehen.]

def reduceLocation( aSequence ):
    keep= False
    block= None
    for line in aSequence:
        if line.startswith("</Location"):
            block.append( line )
            yield block
            block= None
            keep= False
        elif line.startsWith("<Location"):
            block= [ line ]
            keep= True
        elif keep:
            block.append( line )
        else:
            pass
    if block is not None:
        yield block # A partial block, icky

Auch hier können wir diesen Generator in einem richtigen for-Schleife verwenden.

source = file( ... )
for b in reduceLocation( source.readlines() ):
    print b
source.close()

Die Idee ist, dass eine Generatorfunktion ermöglicht es uns, eine Sequenz zu filtern oder zu verringern, eine andere Sequenz ein Wert zu einem Zeitpunkt erzeugt wird.

Ein praktisches Beispiel, wo Sie den Einsatz eines Generators machen könnte, ist, wenn Sie irgendeine Art von Form haben, und Sie möchten, um ihre Ecken, Kanten iterieren oder was auch immer. Für mein eigenes Projekt (Quellcode hier ) hatte ich ein Rechteck:

class Rect():

    def __init__(self, x, y, width, height):
        self.l_top  = (x, y)
        self.r_top  = (x+width, y)
        self.r_bot  = (x+width, y+height)
        self.l_bot  = (x, y+height)

    def __iter__(self):
        yield self.l_top
        yield self.r_top
        yield self.r_bot
        yield self.l_bot

Jetzt kann ich ein Rechteck und eine Schleife über seine Ecken erstellen:

myrect=Rect(50, 50, 100, 100)
for corner in myrect:
    print(corner)

Statt __iter__ könnten Sie eine Methode iter_corners haben und dass rufen mit for corner in myrect.iter_corners(). Es ist nur elegantes __iter__ zu verwenden, da dann können wir die Klasse Instanznamen direkt im for Ausdruck verwenden.

Grundsätzlich vermeiden Funktionen Rückruf bei über Eingabe Aufrechterhaltung Zustand iterieren.

Siehe hier und hier für einen Überblick von dem, was kann mit Generatoren erfolgen.

Einige gute Antworten hier, aber ich würde auch eine vollständige Lesen des Python Functional Programming Tutorial , die einige der potentere anwendungs~~POS=TRUNC von Generatoren hilft erklären.

Ich benutze Generatoren, wenn unsere Webserver als Proxy agiert:

  1. Der Client fordert eine URL- vom Server
  2. Der Server beginnt die Ziel-URL
  3. zu laden
  4. Die Server-Erträge, die Ergebnisse an den Client zurück, sobald es wird ihnen

Da die Sendemethode eines Generators nicht erwähnt wurde, ist hier ein Beispiel:

def test():
    for i in xrange(5):
        val = yield
        print(val)

t = test()

# Proceed to 'yield' statement
next(t)

# Send value to yield
t.send(1)
t.send('2')
t.send([3])

Es zeigt die Möglichkeit, einen Wert an einen laufenden Generator zu senden. Ein fortgeschrittenere Kurs über Generatoren in dem Video unten (einschließlich yield von explination, Generatoren für die parallele Verarbeitung, die Rekursion Grenze entweichende, etc.)

David Beazley auf Generatoren bei PyCon 2014

Haufen von Sachen. Jedes Mal, wenn Sie wollen, eine Folge von Elementen zu erzeugen, wollen aber nicht alle in eine Liste zu ‚materialisieren‘, sie haben auf einmal. Zum Beispiel könnten Sie einen einfachen Generator haben, die Primzahlen zurückgibt:

def primes():
    primes_found = set()
    primes_found.add(2)
    yield 2
    for i in itertools.count(1):
        candidate = i * 2 + 1
        if not all(candidate % prime for prime in primes_found):
            primes_found.add(candidate)
            yield candidate

Sie können dann verwenden, die Produkte der nachfolgenden Primzahlen zu erzeugen:

def prime_products():
    primeiter = primes()
    prev = primeiter.next()
    for prime in primeiter:
        yield prime * prev
        prev = prime

Dies sind ziemlich trivial Beispiele, aber man kann sehen, wie es für die Verarbeitung großen (potentiell unendlichen!) Kann nützlich sein, Datensätze, ohne sie vorher zu erzeugen, die nur eine der offensichtlichsten Anwendungen ist.

Auch gut für den Druck der Primzahlen bis n:

def genprime(n=10):
    for num in range(3, n+1):
        for factor in range(2, num):
            if num%factor == 0:
                break
        else:
            yield(num)

for prime_num in genprime(100):
    print(prime_num)
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top