Я начинаю изучать Python, и я сталкивался с функциями генератора, которые имеют в них инструкцию yield. Я хочу знать, какие типы проблем действительно эффективны при выполнении этих функций.
Для чего вы можете использовать функции генератора Python?
Ответ 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 Руководство по функциональному программированию, которое поможет объяснить некоторые из более мощные варианты использования генераторов.
- Особенно интересно, что теперь можно обновить переменную доходности вне функции генератора, что позволяет создавать динамические и переплетенные сопрограммы с относительно небольшим усилием.
- Также см. PEP 342: Coroutines через Enhanced Generators для получения дополнительной информации.
Ответ 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])
Показывает возможность отправки значения работающему генератору Более продвинутый курс по генераторам в приведенном ниже видео (включая доходность от исследования, генераторы для параллельной обработки, преодоление рекурсивного ограничения и т.д.)
Ответ 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)