Для чего вы можете использовать функции генератора Python?

Я начинаю изучать Python, и я столкнулся с функциями генератора, те, которые имеют оператор yield в них. Я хочу знать, какие проблемы эти функции действительно хорошо решают.

16 ответов


генераторы дают вам ленивую оценку. Вы используете их, повторяя их, явно с " for " или неявно, передавая его любой функции или конструкции, которая выполняет итерацию. Вы можете думать о генераторах как о возврате нескольких элементов, как если бы они возвращали список, но вместо того, чтобы возвращать их все сразу, они возвращают их один за другим, и функция генератора приостанавливается, пока не будет запрошен следующий элемент.

генераторы хороши для вычисления больших наборов результатов (в конкретные вычисления с участием самих циклов), где вы не знаете, понадобятся ли вам все результаты, или где вы не хотите выделять память для всех результатов одновременно. Или для ситуаций, когда генератор использует другое генератор, или потребляет какой-то другой ресурс, и это более удобно, если это произошло как можно позже.

другое использование для генераторов (это действительно то же самое) - заменить обратные вызовы итерацией. В некоторых ситуациях вы хотите, чтобы функция выполняла большую работу и иногда возвращалась к вызывающему объекту. Традиционно для этого используется функция обратного вызова. Вы передаете этот обратный вызов рабочей функции, и она периодически вызывает этот обратный вызов. Генераторный подход заключается в том, что рабочая функция (теперь генератор) ничего не знает о обратном вызове и просто дает, когда хочет что-то сообщить. Вызывающий абонент, вместо того, чтобы писать отдельный обратный вызов и передавать его рабочей функции, делает все отчеты работайте в небольшой " for " петле вокруг генератора.

например, скажем, вы написали программу "поиск в файловой системе". Вы можете выполнить поиск полностью, собрать результаты, а затем отобразить их по одному за раз. Все результаты должны быть собраны до того, как вы покажете первый, и все результаты будут в памяти одновременно. Или вы можете отобразить результаты, пока вы их найдете, что будет более эффективной памятью и намного дружелюбнее по отношению к пользователю. Этот последнее можно сделать, передав функцию печати результатов функции поиска файловой системы, или это можно сделать, просто сделав функцию поиска генератором и повторяя результат.

Если вы хотите увидеть пример двух последних подходов, см. раздел ОС.путь.walk () (старая файловая система-функция walking с обратным вызовом) и os.walk () (новая файловая система-шагающий генератор.) Конечно, если вы действительно хотите собрать все результаты в списке, подход генератора тривиален для преобразование в подход большого списка:

big_list = list(the_generator)

одна из причин использовать генератор сделать решение более ясным для некоторого вида решений.

другой-обрабатывать результаты по одному, избегая создания огромных списков результатов, которые вы бы обрабатывали в любом случае.

Если у вас есть функция Фибоначчи-до-n, как это:

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

вы можете более легко написать функцию следующим образом:

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

функция яснее. И если вы используете функцию как это:

for x in fibon(1000000):
    print x,

в этом примере, если используется версия генератора, весь список элементов 1000000 не будет создан вообще, только одно значение за раз. Это не будет иметь место при использовании версии списка, где список будет создан первым.


см. раздел "мотивация" в PEP 255.

неочевидное использование генераторов создает прерываемые функции, которые позволяют вам делать такие вещи, как обновление пользовательского интерфейса или запускать несколько заданий "одновременно" (на самом деле), не используя потоки.


Generators не знаю, о yield

возвращение

оператор return-это когда все локальные переменные уничтожаются, а результирующее значение возвращается (возвращается) вызывающему объекту. Если та же функция будет вызвана некоторое время спустя, функция получит новый набор переменная.

доходность

но что, если локальные переменные не выбрасываются при выходе из функции? Это означает, что мы можем resume the function на чем мы остановились. Вот где понятие generators вводятся и yield заявление о выходе, где function остановились.

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

так вот в чем разница между return и yield операторы в Python.

Yield заявление, что делает функцию функцией генератора.

таким образом, генераторы являются простым и мощным инструментом для создания итераторов. Они написаны как обычные функции, но они используют yield заявление всякий раз, когда они хотят вернуть данные. Каждый раз, когда вызывается next (), генератор возобновляется там, где он остановился (он запоминает все значения данных и какой оператор был выполнен в последний раз).


буферизация. Когда эффективно извлекать данные большими кусками, но обрабатывать их небольшими кусками, генератор может помочь:

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

вышеизложенное позволяет легко отделить буферизацию от обработки. Потребительская функция теперь может просто получать значения один за другим, не беспокоясь о буферизации.


Пример Реального Мира

предположим, у вас есть 100 миллионов доменов в вашей таблице MySQL, и вы хотели бы обновить Alexa rank для каждого домена.

Первое, что вам нужно, это выбрать свои доменные имена из базы данных.

допустим, ваше имя таблицы domains и имя столбца domain.

если вы используете SELECT domain FROM domains он собирается вернуть 100 миллионов строк, которые будут потреблять много памяти. Таким образом, ваш сервер может крушение.

Итак, вы решили запустить программу в пакетном режиме. Допустим, наш размер партии 1000.

в нашей первой партии мы запросим первые 1000 строк, проверим Alexa rank для каждого домена и обновим строку базы данных.

в нашей второй партии мы будем работать на следующие 1000 строк. В нашей третьей партии это будет от 2001 до 3000 и так далее.

теперь нам нужна функция генератора, которая генерирует наши партии.

вот наш генератор функция:

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

как вы можете видеть, наша функция сохраняет yielding результаты. Если вы использовали ключевое слово return вместо yield, тогда вся функция будет завершена, как только она достигнет возврата.

return - returns only once
yield - returns multiple times

если функция использует ключевое слово yield тогда это генератор.

теперь вы можете повторить такой:

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

я обнаружил, что генераторы очень полезны в очистке ваш код и дает вам уникальный способ для инкапсуляции и модульности кода. В ситуации, когда вам нужно что-то постоянно выплевывать значения на основе собственной внутренней обработки и когда это что-то нужно вызывать из любой точки вашего кода (а не только из цикла или блока, например), генераторы являются на функции для использования.

абстрактным примером будет число Фибоначчи генератор, который не живет в цикле и когда он вызывается из любого места, всегда будет возвращать следующий номер в последовательности:

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

Теперь у вас есть два объекта генератора чисел Фибоначчи, которые вы можете вызвать из любой точки вашего кода, и они всегда будут возвращать все большие числа Фибоначчи в последовательности следующим образом:

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

прекрасная вещь о генераторах заключается в том, что они инкапсулируют состояние без необходимости проходить через обручи создания объектов. Один путь думать о них - это как о "функциях", которые помнят свое внутреннее состояние.

я получил пример Фибоначчи из генераторы Python-что это такое? и с небольшим воображением вы можете придумать много других ситуаций, когда генераторы создают отличную альтернативу for петли и других традиционных циклов.


простое объяснение: Рассмотрим for сообщении

for item in iterable:
   do_stuff()

много времени, все элементы!--4--> не нужно быть там с самого начала, но может быть сгенерирован на лету, как они требуются. Это может быть намного более эффективным в как

  • пробел (вам никогда не нужно хранить все элементы одновременно) и
  • время (итерация может закончиться до того, как все элементы будут необходимы).

в других случаях вы не даже знать все пункты заранее. Например:

for command in user_input():
   do_stuff_with(command)

у вас нет способа узнать все команды пользователя заранее, но вы можете использовать хороший цикл, как это, если у вас есть генератор, передающий Вам команды:

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

с генераторами вы также можете иметь итерацию по бесконечным последовательностям, что, конечно, невозможно при итерации по контейнерам.


мои любимые использования - " фильтр "и" уменьшить " операции.

предположим, мы читаем файл и хотим только строки, начинающиеся с "##".

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

мы можем использовать функцию генератора в правильном цикле

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

пример уменьшения аналогичен. Предположим, у нас есть файл, где нам нужно найти блоки <Location>...</Location> строки. [Не теги HTML,а строки, которые выглядят как теги.]

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

опять же, мы можем использовать этот генератор в правильный для цикла.

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

идея заключается в том, что функция генератора позволяет фильтровать или уменьшать последовательность, производя другую последовательность по одному значению за раз.


практический пример, где вы можете использовать генератор, если у вас есть какая-то форма, и вы хотите перебирать ее углы, края или что-то еще. Для моего собственного проекта (исходный код здесь) у меня был прямоугольник:

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

теперь я могу создать прямоугольник и петлю по его углам:

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

вместо __iter__ у вас может быть метод iter_corners и звонок, что с for corner in myrect.iter_corners(). Это просто более элегантно использовать __iter__ С тех пор мы можем использовать имя экземпляра класса непосредственно в for выражение.


в основном избегая функций обратного вызова при итерации по состоянию поддержания ввода.

посмотреть здесь и здесь обзор того, что можно сделать с помощью генераторов.


некоторые хорошие ответы здесь, однако, я бы также рекомендовал полное чтение Python учебник функционального программирования, который помогает объяснить некоторые из более мощных случаев использования генераторов.


Я использую генераторы, когда наш веб-сервер работает как прокси:

  1. клиент запрашивает проксированный url-адрес с сервера
  2. сервер начинает загружать целевой url
  3. сервер дает, чтобы вернуть результаты клиенту, как только он их получит

поскольку метод send генератора не был упомянут, вот пример:

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

он показывает возможность отправки значения работающему генератору. Более продвинутый курс по генераторам в видео ниже (включая yield от explination, генераторов для параллельной обработки, минуя ограничение рекурсии и т. д.)

Дэвид Бизли на генераторах на PyCon 2014


кучи вещей. Каждый раз, когда вы хотите создать последовательность элементов, но не хотите "материализовать" их все в список сразу. Например, у вас может быть простой генератор, который возвращает простые числа:

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

затем вы можете использовать это для генерации продуктов последующих простых чисел:

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

эти довольно тривиальные примеры, но вы можете увидеть, как это может быть полезно для обработки большого (потенциально бесконечного!) наборы данных без их генерации заранее, что является лишь одним из наиболее очевидных применений.


также подходит для печати простых чисел до 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)