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

StackOverflow https://stackoverflow.com/questions/102535

  •  01-07-2019
  •  | 
  •  

Вопрос

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

Это было полезно?

Решение

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

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

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

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

Если вы хотите увидеть пример последних двух подходов, см. os.path.walk() (старая функция обхода файловой системы с обратным вызовом) и 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 элементов вообще не будет создан, а будет создано только одно значение за раз.Этого не произойдет при использовании версии списка, где список будет создан первым.

См. раздел «Мотивация». ПЭП 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(), генератор возобновляет работу с того места, где он остановился (он запоминает все значения данных и то, какой оператор был выполнен последним).

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

Допустим, у вас есть 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()

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

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

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

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

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

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

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

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

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

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

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

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. Клиент запрашивает прокси-адрес с сервера
  2. Сервер начинает загружать целевой URL
  3. Сервер уступает место возврату результатов клиенту, как только он их получит.

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

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

Дэвид Бизли о генераторах на 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)
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top