Do czego można używać funkcji generatora Pythona?

Zaczynam się uczyć Pythona i natknąłem się na funkcje generatora, które mają w sobie instrukcję yield. Chcę wiedzieć, jakie rodzaje problemów, które te funkcje są naprawdę dobre w rozwiązywaniu.

Author: Uli Köhler, 2008-09-19

16 answers

Generatory dają leniwe oceny. Używasz ich przez iterację nad nimi, jawnie za pomocą ' for ' lub pośrednio, przekazując je do dowolnej funkcji lub konstrukcji, która jest iteracją. Możesz myśleć o generatorach jako o zwracaniu wielu elementów, tak jakby zwracały listę, ale zamiast zwracać je wszystkie naraz, zwracają je jeden po drugim, a funkcja generatora jest wstrzymywana, aż do zażądania następnego elementu.

Generatory są dobre do obliczania dużych zbiorów wyników (w szczególności obliczenia z użyciem samych pętli), gdzie nie wiadomo, czy będą potrzebne wszystkie wyniki, czy też nie chcemy przydzielać pamięci dla wszystkich wyników w tym samym czasie. Lub w sytuacjach, w których generator używa innego generatora lub zużywa inny surowiec, a wygodniej jest, jeśli stało się to tak późno, jak to możliwe.

Innym zastosowaniem generatorów (to naprawdę to samo) jest zastąpienie wywołań zwrotnych iteracją. W niektórych sytuacjach chcesz, aby funkcja dużo pracy i od czasu do czasu zgłaszać się do rozmówcy. Tradycyjnie używasz do tego funkcji zwrotnej. Przekazujesz to wywołanie zwrotne do funkcji pracy i będzie okresowo wywoływać to wywołanie zwrotne. Podejście generatora polega na tym, że funkcja pracy (obecnie generator) nie wie nic o wywołaniu zwrotnym, a jedynie daje, gdy chce coś zgłosić. Rozmówca, zamiast pisać oddzielny callback i przekazując to do funkcji pracy, robi wszystkie prace raportowania w trochę "dla" pętla wokół generatora.

Na przykład, powiedzmy, że napisałeś program do wyszukiwania systemów plików. Możesz przeprowadzić wyszukiwanie w całości, zebrać wyniki, a następnie wyświetlić je pojedynczo. Wszystkie wyniki muszą być zebrane przed pokazaniem pierwszego, a wszystkie wyniki będą w pamięci w tym samym czasie. Możesz też wyświetlać wyniki, gdy je znajdziesz, co byłoby bardziej wydajne w pamięci i bardziej przyjazne dla użytkownika. Tego ostatniego można dokonać przechodząc funkcja drukowania wyników do funkcji wyszukiwania systemu plików, lub może to być wykonane przez po prostu czyniąc funkcję wyszukiwania generatorem i iterację nad wynikiem.

Jeśli chcesz zobaczyć przykład dwóch ostatnich podejść, zobacz os./ align = "left" / walk () (stary system plików-funkcja chodzenia z callback) i os.walk () (nowy generator systemu plików-walking.) Oczywiście, jeśli naprawdę chcesz zebrać wszystkie wyniki na liście, podejście generatora jest trywialne, aby przekonwertować na dużą listę "podejście": {]}

big_list = list(the_generator)
 222
Author: Thomas Wouters,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-08 22:00:24

Jednym z powodów, dla których warto użyć generatora, jest wyjaśnienie rozwiązania dla pewnego rodzaju rozwiązań.

Drugi polega na traktowaniu wyników po kolei, unikając budowania ogromnych list wyników, które i tak przetwarzałbyś osobno.

Jeśli masz funkcję Fibonacciego-up-to-N, taką jak Ta:

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

Możesz łatwiej napisać funkcję tak:

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

Funkcja jest jaśniejsza. A jeśli używasz funkcji w ten sposób:

for x in fibon(1000000):
    print x,

W tym przykładzie, jeśli za pomocą wersja generatora, cała lista 1000000 pozycji nie zostanie utworzona w ogóle, tylko jedna wartość na raz. Nie byłoby tak w przypadku użycia wersji listy, w której lista byłaby utworzona jako pierwsza.

 85
Author: nosklo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-19 15:09:28

Zobacz sekcję "motywacja" w PEP 255 .

Nieoczywistym zastosowaniem generatorów jest tworzenie funkcji przerywalnych, które pozwalają wykonywać takie rzeczy jak aktualizacja interfejsu użytkownika lub uruchamianie kilku zadań "jednocześnie" (w rzeczywistości przeplatanych), nie używając wątków.

 37
Author: Nickolay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-19 15:07:13

Znajduję to wyjaśnienie, które rozwiewa moje wątpliwości. Ponieważ istnieje możliwość, że osoba, która nie wie Generators również nie wie o yield

Return

Instrukcja return jest miejscem, w którym wszystkie zmienne lokalne są niszczone, a wynikowa wartość jest zwracana (zwracana) do wywołującego. Jeśli ta sama funkcja zostanie wywołana jakiś czas później, funkcja otrzyma nowy zestaw zmiennych.

Wydajność

Ale co jeśli lokalny zmienne nie są wyrzucane po wyjściu z funkcji? Oznacza to, że możemy resume the function tam, gdzie skończyliśmy. W tym miejscu wprowadza się pojęcie generators, a twierdzenie yield wznawia się tam, gdzie function zostało przerwane.

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

Więc to jest różnica między return i yield w Pythonie.

Twierdzenie o wydajności jest tym, co sprawia, że funkcja jest funkcją generatora.

Więc generatory są prostym i potężnym narzędziem do tworzenia iteratorów. Są to napisane jak zwykłe funkcje, ale używają instrukcji yield, gdy chcą zwrócić dane. Za każdym razem, gdy wywołana jest funkcja next (), generator jest wznawiany tam, gdzie został przerwany (zapamiętuje wszystkie wartości danych i które polecenie zostało ostatnio wykonane).

 33
Author: Mirage,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-20 12:03:27

Buforowanie. Gdy wydajne jest pobieranie danych w dużych kawałkach, ale przetwarzanie ich w małych kawałkach, generator może pomóc:

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

Powyższe pozwala łatwo oddzielić buforowanie od przetwarzania. Funkcja consumer może teraz pobierać wartości jeden po drugim, nie martwiąc się o buforowanie.

 26
Author: Rafał Dowgird,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-19 15:14:10

Przykład Świata Rzeczywistego

Załóżmy, że masz 100 milionów domen w tabeli MySQL i chciałbyś zaktualizować rangę Alexa dla każdej domeny.

Pierwszą rzeczą, której potrzebujesz, jest wybranie nazw domen z bazy danych.

Załóżmy, że nazwa tabeli to domains, a nazwa kolumny to domain.

Jeśli użyjesz SELECT domain FROM domains zwróci 100 milionów wierszy, co pochłonie dużo pamięci. Więc twój serwer może się zawiesić.

Więc zdecydowałeś się uruchomić program partiami. Powiedzmy, że nasza partia to 1000.

W naszej pierwszej partii zapytamy o pierwsze 1000 wierszy, sprawdzimy Ranking Alexa dla każdej domeny i zaktualizujemy wiersz bazy danych.

W naszej drugiej partii będziemy pracować nad kolejnymi 1000 rzędami. W naszej trzeciej partii będzie to od 2001 do 3000 i tak dalej.

Teraz potrzebujemy funkcji generatora, która generuje nasze partie.

Oto nasza funkcja generatora:

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

Jak widzisz, nasza funkcja utrzymuje yield w wyniki. Jeśli użyjesz słowa kluczowego return zamiast yield, cała funkcja zostanie zakończona po osiągnięciu wartości return.

return - returns only once
yield - returns multiple times

Jeśli funkcja używa słowa kluczowego yield, to jest generatorem.

Teraz możesz iterować Tak:

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()
 26
Author: Giri,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-20 11:16:57

Odkryłem, że generatory są bardzo pomocne w czyszczeniu kodu i dając ci bardzo unikalny sposób na hermetyzację i modularyzację kodu. W sytuacji, gdy potrzebujesz czegoś do ciągłego wypluwania wartości na podstawie własnego wewnętrznego przetwarzania i gdy to coś musi być wywołane z dowolnego miejsca w kodzie (i nie tylko w pętli lub bloku na przykład), generatory są Funkcja do użycia.

Abstrakcyjnym przykładem może być generator liczb Fibonacciego, który nie żyje w pętli i gdy zostanie wywołana z dowolnego miejsca, zawsze zwróci następną liczbę w sekwencji:

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

Teraz masz dwa obiekty generatora liczb Fibonacciego, które możesz wywołać z dowolnego miejsca w kodzie i zawsze będą zwracać coraz większe liczby Fibonacciego w następującej kolejności:

>>> fibgen1.next(); fibgen1.next(); fibgen1.next(); fibgen1.next()
0
1
1
2
>>> fibgen2.next(); fibgen2.next()
0
1
>>> fibgen1.next(); fibgen1.next()
3
5
Piękne w generatorach jest to, że zamykają stan bez konieczności przechodzenia przez obręcze tworzenia obiektów. Jednym ze sposobów myślenia o nich jest "funkcje", które pamiętają swój stan wewnętrzny.

Otrzymałem przykład Fibonacciego z Generatory Pythona-czym są? przy odrobinie wyobraźni możesz wymyślić wiele innych sytuacji, w których Generatory stanowią doskonałą alternatywę dla pętli {2]} i innych tradycyjnych konstrukcji iteracyjnych.

 20
Author: Andz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-20 11:58:31

Proste wyjaśnienie: Rozważmy twierdzenie for

for item in iterable:
   do_stuff()

Przez większość czasu wszystkie elementy w iterable nie muszą być tam od początku, ale mogą być generowane w locie, ponieważ są wymagane. Może to być o wiele bardziej efektywne w obu

  • spacja (nigdy nie musisz przechowywać wszystkich przedmiotów jednocześnie) i
  • time (iteracja może zakończyć się zanim wszystkie elementy będą potrzebne).
/ Align = "left" / Na przykład:
for command in user_input():
   do_stuff_with(command)

Nie masz możliwości wcześniejszego poznania wszystkich poleceń użytkownika, ale możesz użyć ładnej pętli takiej jak ta, Jeśli masz generator przekazujący Ci polecenia:

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

Z generatorami można również wykonywać iterację nad nieskończonymi sekwencjami, co oczywiście nie jest możliwe w przypadku iteracji nad kontenerami.

 18
Author: dF.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-19 15:15:03

Moje ulubione zastosowania to operacje "filtrowania" i "zmniejszania".

Załóżmy, że czytamy plik i chcemy tylko linii zaczynających się od"##".

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

Możemy wtedy użyć funkcji generatora w odpowiedniej pętli

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

Przykład redukcji jest podobny. Załóżmy, że mamy plik, w którym musimy zlokalizować bloki <Location>...</Location> linii. [Nie znaczniki HTML, ale linie, które wyglądają jak znaczniki.]

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

Ponownie możemy użyć tego generatora w odpowiednim dla pętla.

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

Idea jest taka, że funkcja generatora pozwala nam filtrować lub zmniejszać sekwencję, tworząc inną sekwencję jedną wartość na raz.

 12
Author: S.Lott,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-19 15:13:16

Praktycznym przykładem, gdzie można użyć generatora jest, jeśli masz jakiś kształt i chcesz iterację na jego rogach, krawędziach lub cokolwiek innego. Dla mojego własnego projektu (kod źródłowy tutaj) miałem prostokąt:

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

Teraz mogę utworzyć prostokąt i pętlę na jego rogach:

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

Zamiast __iter__ możesz mieć metodę iter_corners i wywołać ją za pomocą for corner in myrect.iter_corners(). Bardziej eleganckie jest użycie __iter__, ponieważ wtedy możemy użyć nazwy instancji klasy bezpośrednio w for wyrażenie.

 8
Author: Pithikos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-27 23:13:38

Zasadniczo unikanie funkcji oddzwaniających podczas iteracji nad danymi wejściowymi, utrzymujących stan.

Zobacz TUTAJ i TUTAJ Aby uzyskać przegląd tego, co można zrobić za pomocą generatorów.

 6
Author: MvdD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-19 15:09:26

Kilka dobrych odpowiedzi tutaj, jednak polecam również pełną lekturę Python Functional Programming tutorial który pomaga wyjaśnić niektóre z silniejszych przypadków użycia generatorów.

 4
Author: shongololo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-20 11:54:11

Używam generatorów gdy nasz serwer WWW działa jako proxy:

    Klient żąda proxy url z serwera
  1. Serwer zaczyna wczytywać docelowy adres url
  2. serwer zwraca wyniki klientowi, gdy tylko je otrzyma
 2
Author: Brian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-19 15:17:51

Ponieważ metoda send generatora nie została wymieniona, oto przykład:

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

Pokazuje możliwość wysłania wartości do działającego generatora. Bardziej zaawansowany kurs na temat generatorów w poniższym filmie (w tym yield z explination, generatory do przetwarzania równoległego, ucieczki limitu rekursji itp.)

David Beazley o generatorach na PyCon 2014

 2
Author: John Damen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-20 12:06:32

Stosy rzeczy. Za każdym razem, gdy chcesz wygenerować sekwencję elementów, ale nie chcesz "zmaterializować" ich wszystkich w Listę na raz. Na przykład, możesz mieć prosty generator, który zwraca liczby pierwsze:

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

Można wtedy użyć tego do wygenerowania produktów kolejnych liczb pierwszych:

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

Są to dość trywialne przykłady, ale można zobaczyć, jak może być przydatne do przetwarzania dużych (potencjalnie nieskończonych!) zbiorów danych bez ich wcześniejszego generowania, co jest tylko jedno z bardziej oczywistych zastosowań.

 1
Author: Nick Johnson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-19 18:03:49

Nadaje się również do drukowania liczb pierwszych do 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)
 0
Author: tryptofan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-22 14:06:40