Waarmee kunt u generatorfuncties gebruiken voor?

Ik begin met het leren van Python en ik ben generatorfuncties tegengekomen, die met een verklaring in hen hebben. Ik wil weten welke soorten problemen dat deze functies echt goed zijn in het oplossen.


1, Autoriteit 100%

Generatoren geven je luie evaluatie. Je gebruikt ze door ze over hen, hetzij expliciet met ‘voor’ of impliciet door het door te geven aan een functie of construeer dat itereert. Je kunt denken aan generatoren als het terugsturen van meerdere items, alsof ze een lijst retourneren, maar in plaats van ze allemaal tegelijk terug te sturen, retourneren ze ze een voor-een, en de generatorfunctie wordt gepauzeerd totdat het volgende item wordt aangevraagd.

Generatoren zijn goed voor het berekenen van grote sets van resultaten (in het bijzonder berekeningen met lussen zelf) waar u niet weet of u alle resultaten nodig hebt, of waar u het geheugen niet wilt toewijzen voor alle resultaten dezelfde tijd. Of voor situaties waarin de generator een andere generator gebruikt, of een andere hulpbron verbruikt, en het is handiger als dat zo laat mogelijk is gebeurd.

Een ander gebruik voor generatoren (dat is echt hetzelfde) is om callbacks met iteratie te vervangen. In sommige situaties wilt u een functie om veel werk te doen en af ​​en toe terug te melden bij de beller. Traditioneel zou je hiervoor een callback-functie gebruiken. U passeert deze callback aan de werkfunctie en het zou deze callback periodiek noemen. De generatorbenadering is dat de werkfunctie (nu een generator) niets weet over de callback, en slechts oplevert wanneer het iets wil melden. De beller, in plaats van een aparte callback te schrijven en die naar de werkfunctie passeert, werkt al het rapportage in een klein beetje ‘voor’ loop rond de generator.

Zeg bijvoorbeeld dat u een ‘FileSystem Search’-programma hebt geschreven. U kunt de zoektocht in zijn geheel uitvoeren, de resultaten verzamelen en deze vervolgens één tegelijk weergeven. Alle resultaten zouden moeten worden verzameld voordat u de eerste liet zien, en alle resultaten zouden tegelijkertijd in het geheugen zijn. Of u kunt de resultaten weergeven terwijl u ze vindt, wat meer geheugen efficiënt en veel vriendelijker is voor de gebruiker. Dit laatste zou kunnen worden gedaan door de resultaat-afdrukfunctie door te geven aan de FileSystem-Search-functie, of kan worden gedaan door de zoekfunctie een generator en iteratie over het resultaat te laten functioneren.

Als u een voorbeeld van de laatste twee benaderingen wilt zien, zie OS.Path.Walk () (de oude FileSystem-Walking-functie met callback) en OS.WALK () (de nieuwe bestandssysteem-loopgenerator.) Van CURSUS, als u echt alle resultaten in een lijst wilde verzamelen, is de generatorbenadering triviaal om te converteren naar de BIG-lijstbenadering:

big_list = list(the_generator)

2, Autoriteit 37%

Een van de redenen om generator te gebruiken, is om de oplossing duidelijker te maken voor een soort oplossingen.

De andere is om de resultaten één voor een tijdje te behandelen, en het vermijden van enorme lijsten van resultaten die u hoe dan ook gescheiden zou verwerken.

Als u een fibonacci-up-n-n-functie als volgt hebt:

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

U kunt de functie gemakkelijker schrijven als dit:

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

De functie is duidelijker. En als u de functie als volgt gebruikt:

for x in fibon(1000000):
    print x,

In dit voorbeeld, bij gebruik van de generatorversie, wordt de hele 1000000-itemlijst helemaal niet gemaakt, slechts één waarde per keer. Dat zou niet het geval zijn bij gebruik van de lijstversie, waar eerst een lijst zou worden gemaakt.


3, Autoriteit 18%

Ik vind deze uitleg die mijn twijfel wegneemt. Omdat er een mogelijkheid is dat iemand die Generatorsniet kent, ook niets weet van yield

Retour

De return-instructie is waar alle lokale variabelen worden vernietigd en de resulterende waarde wordt teruggegeven (geretourneerd) aan de aanroeper. Mocht dezelfde functie enige tijd later worden aangeroepen, dan krijgt de functie een nieuwe set variabelen.

Opbrengst

Maar wat als de lokale variabelen niet worden weggegooid wanneer we een functie afsluiten? Dit houdt in dat we resume the functionwaar we gebleven waren. Hier wordt het concept van generatorsgeïntroduceerd en gaat de yield-instructie verder waar de functionophield.

 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()

Dus dat is het verschil tussen returnen yieldstatements in Python.

Opbrengstverklaring maakt een functie tot een generatorfunctie.

Generatoren zijn dus een eenvoudig en krachtig hulpmiddel voor het maken van iterators. Ze zijn geschreven als gewone functies, maar ze gebruiken de instructie yieldwanneer ze gegevens willen retourneren. Elke keer dat next() wordt aangeroepen, gaat de generator verder waar hij was gebleven (hij onthoudt alle gegevenswaarden en welke instructie het laatst is uitgevoerd).


Antwoord 4, autoriteit 18%

Zie het gedeelte ‘Motivatie’ in PEP 255.

Een niet voor de hand liggend gebruik van generatoren is het creëren van onderbreekbare functies, waarmee je dingen kunt doen zoals het bijwerken van de gebruikersinterface of het “gelijktijdig” uitvoeren van verschillende taken (interleaved, eigenlijk) terwijl je geen threads gebruikt.


Antwoord 5, autoriteit 16%

Voorbeeld uit de echte wereld

Stel dat u 100 miljoen domeinen in uw MySQL-tabel heeft en dat u de Alexa-rangschikking voor elk domein wilt bijwerken.

Het eerste wat u moet doen, is uw domeinnamen uit de database selecteren.

Stel dat uw tabelnaam domainsis en de kolomnaam domain.

Als je SELECT domain FROM domainsgebruikt, zal het 100 miljoen rijen retourneren, wat veel geheugen zal verbruiken. Uw server kan dus crashen.

Dus je hebt besloten om het programma in batches uit te voeren. Laten we zeggen dat onze batchgrootte 1000 is.

In onze eerste batch zullen we de eerste 1000 rijen doorzoeken, de Alexa-rang voor elk domein controleren en de databaserij bijwerken.

In onze tweede batch werken we aan de volgende 1000 rijen. In onze derde batch zal het zijn van 2001 tot 3000 enzovoort.

Nu hebben we een generatorfunctie nodig die onze batches genereert.

Hier is onze generatorfunctie:

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

Zoals je kunt zien, blijft onze functie yield. Als u het sleutelwoord returngebruikte in plaats van yield, dan zou de hele functie worden beëindigd zodra return werd bereikt.

return - returns only once
yield - returns multiple times

Als een functie het trefwoord yieldgebruikt, is het een generator.

Je kunt nu als volgt herhalen:

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()

Antwoord 6, autoriteit 11%

buffering. Wanneer het efficiënt is om gegevens in grote brokken te halen, maar het in kleine brokken verwerken, kan een generator helpen:

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

Hiermee kunt u gemakkelijk buffering van verwerking scheiden. De consumentenfunctie kan nu slechts één voor één krijgen zonder zich zorgen te maken over buffering.


7, Autoriteit 9%

Ik heb ontdekt dat generatoren erg behulpzaam zijn bij het opruimen van uw code en door u een zeer unieke manier te geven om code te inkapselen en te modulariseren. In een situatie waarin u iets nodig hebt om de waarden voortdurend uit te spugen op basis van zijn eigen interne verwerking en wanneer dat iets van overal in uw code (en niet alleen binnen een lus of een blok bijvoorbeeld) wordt genoemd), zijn generatoren de -functie om te gebruiken.

Een abstract voorbeeld zou een FIBONACCI-nummergenerator zijn die niet binnen een lus leeft en wanneer het van overal wordt genoemd, zal altijd het volgende nummer in de reeks retourneren:

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()

Nu u twee FIBONACCI-nummergeneratorobjecten hebt waarvan u overal in uw code kunt bellen en ze zullen altijd als volgt een steeds grotere fibonacci-nummers terugkeren als volgt:

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

Het mooie aan generatoren is dat ze de staat inkapselen zonder door de hoepels te hoeven gaan met het maken van objecten. Een manier van denken over hen is als “functies” die hun interne toestand onthouden.

Ik heb het Fibonacci-voorbeeld van python generatoren – wat Zijn ze? en met een beetje verbeeldingskracht, kunt u veel andere situaties bedenken waar generatoren voor een groot alternatief zijn voor forloops en andere traditionele iteratieconstructen.


8, Autoriteit 8%

De eenvoudige uitleg:
Overweeg een forverklaring

for item in iterable:
   do_stuff()

Veel van de tijd, alle items in iterablehoeven er niet vanaf het begin te zijn, maar kunnen op de vlucht worden gegenereerd zoals ze nodig zijn. Dit kan in beide

een stuk efficiënter zijn

  • ruimte (je hoeft nooit alle items tegelijkertijd op te slaan) en
  • Tijd (de iteratie kan eindigen voordat alle items nodig zijn).

Andere tijden, u weet niet eens alle items die van tevoren zijn. Bijvoorbeeld:

for command in user_input():
   do_stuff_with(command)

U hebt geen manier om alle opdrachten van de gebruiker van tevoren te kennen, maar u kunt een mooie lus als dit gebruiken als u een generator hebt die u opdringt:

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

Met generatoren kunt u ook anneratie over oneindige sequenties hebben, wat natuurlijk niet mogelijk is bij het herhalen van over containers.


9, Autoriteit 5%

Mijn favoriete gebruik is “filter” en “Verminder” -activiteiten.

Laten we zeggen dat we een bestand lezen en alleen willen de regels die beginnen met “##”.

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

We kunnen vervolgens de generatorfunctie gebruiken in een juiste lus

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

Het vermindering van het voorbeeld is vergelijkbaar. Laten we zeggen dat we een bestand hebben waar we moeten vinden om blokken van <Location>...</Location>LINES. [Not HTML-tags, maar lijnen die er nog tag-achtig uitzien.]

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

Nogmaals, we kunnen deze generator gebruiken in een juiste lus.

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

Het idee is dat een generatorfunctie ons in staat stelt om een ​​sequentie te filteren of te verminderen, waarbij een andere reeks één waarde per keer wordt geproduceerd.


10, Autoriteit 3%

In principe het vermijden van oproepfuncties wanneer herhoogt op de invoer onderhoudstoestand.

Zie Hier en hier voor een overzicht van wat kan worden gedaan met behulp van generatoren.


11, Autoriteit 2%

Aangezien de verzendmethode van een generator niet is vermeld, is hier een voorbeeld:

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])

Het toont de mogelijkheid om een ​​waarde naar een lopende generator te verzenden. Een geavanceerdere cursus op generatoren in de onderstaande video (inclusief yieldvan uitzondering, generatoren voor parallelle verwerking, ontsnappen aan de recursietlimiet, enz.)

David Bezley op generatoren bij pycon 2014


12, Autoriteit 2%

Een aantal goede antwoorden hier, maar ik zou ook een compleet lezen van de Python Functionele programmeerhandleiding die helpt een aantal van de krachtigere gebruiksgevallen van generatoren uit te leggen.


Antwoord 13

Ik gebruik generatoren wanneer onze webserver als proxy fungeert:

  1. De client vraagt ​​om een ​​proxy-URL van de server
  2. De server begint de doel-URL te laden
  3. De server geeft mee om de resultaten naar de client terug te sturen zodra hij ze ontvangt

Antwoord 14

Stapels met spullen. Elke keer dat u een reeks items wilt genereren, maar ze niet allemaal tegelijk in een lijst wilt ‘materialiseren’. U kunt bijvoorbeeld een eenvoudige generator hebben die priemgetallen retourneert:

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

Je zou dat dan kunnen gebruiken om de producten van volgende priemgetallen te genereren:

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

Dit zijn vrij triviale voorbeelden, maar je kunt zien hoe het nuttig kan zijn voor het verwerken van grote (potentieel oneindige!) datasets zonder ze vooraf te genereren, wat slechts een van de meer voor de hand liggende toepassingen is.


Antwoord 15

Ook goed voor het afdrukken van priemgetallen tot 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)

Other episodes