Was können Sie für Python-Generator-Funktionen verwenden?
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.
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 yield
ing. 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.
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.
- Besonders interessant ist, dass es nun möglich ist, PEP 342: Koroutinen über Verbesserte Generatoren für weitere Informationen .
Ich benutze Generatoren, wenn unsere Webserver als Proxy agiert:
- Der Client fordert eine URL- vom Server
- Der Server beginnt die Ziel-URL zu laden
- 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.)
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)