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

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

Ответы

Ответ 1

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

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

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

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

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

big_list = list(the_generator)

Ответ 2

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

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

Если у вас есть функция fibonacci-up-to-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 не будет создан вообще, всего одно значение за раз. Это не будет иметь место при использовании версии списка, в которой сначала будет создан список.

Ответ 3

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

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

Ответ 4

Я нахожу это объяснение, которое очищает мои сомнения. Потому что есть вероятность, что человек, который не знает Generators, также не знает о yield

Return

Оператор 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, когда они хотят вернуть данные. Каждый раз, когда вызывается next(), генератор возобновляет то, где он оставил (он запоминает все значения данных и какой оператор был выполнен в последний раз).

Ответ 5

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

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

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

Ответ 6

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

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

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

Предположим, что имя вашей таблицы domains и имя столбца domain

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

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

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

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

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

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

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

Как видите, наша функция сохраняет yield результаты. Если вы использовали ключевое слово 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()

Ответ 7

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

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

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

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

Я получил пример фибоначчи из http://www.neotitans.com/resources/python/python-generators-tutorial.html и с небольшим воображением вы можете найти множество других ситуаций, когда генераторы для отличной альтернативы for-loop и другим традиционным итерационным конструкциям.

Ответ 8

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

for item in iterable:
   do_stuff()

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

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

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

for command in user_input():
   do_stuff_with(command)

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

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

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

Ответ 9

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

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

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

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

Ответ 10

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

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.

Ответ 11

В основном избегать функций обратного вызова при повторном выполнении состояния сохранения ввода.

См. здесь и здесь для обзора того, что могут быть выполнены с использованием генераторов.

Ответ 12

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

Ответ 13

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

  • Клиент запрашивает прокси-сервер с сервера
  • Сервер начинает загружать целевой URL
  • Сервер позволяет возвращать результаты клиенту, как только он их получает.

Ответ 14

Так как метод отправки генератора не упоминался здесь, это пример:

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

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

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

Ответ 15

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

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

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

Ответ 16

Также полезно печатать простые числа до 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)