В каких ситуациях вы должны использовать генераторы в Python? - PullRequest
0 голосов
/ 22 сентября 2018

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

Например, я видел пример о четных числах, который просто генерирует четные числа и распечатывает их, но что, если я хочу получить список четных чисел, как этот:

def even(k):
    for i in range(k):
        if (i%2):
           yield k

even_list = []
for i in even(100):
    even_list.append(i)

Пресекает ли это цель использования генератора, поскольку он создает его в четном списке.Этот метод все еще экономит память / время?

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

def even(k):
    evens_list = []
    for i in range(k):
        if (i%2):
           evens_list.append(i)
    return evens_list

В каких случаях в каких случаях могут быть полезны генераторы?

Ответы [ 4 ]

0 голосов
/ 22 сентября 2018

Пресекает ли это цель использования генератора, поскольку он создает его в четном списке.В этом случае, в каких именно случаях полезны генераторы?

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

Сохранение циклов ЦП (время)

Представьте, что у вас есть список четных чисел, а затем вы хотите взять сумму первых пяти чисел.В Python мы могли бы сделать это с помощью islice, например:

sumfirst5even = sum(islice(even(100), 5))

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

Используя генератор, мы можем ограничить это только теми элементами, которые нам действительно нужны.Таким образом, мы будем только yield первые пять элементов.Алгоритм будет никогда вычислять элементы больше 10. Да, здесь сомнительно, что это окажет какое-либо (значительное) влияние.Возможно даже, что « генератор протокола » потребует больше циклов ЦП по сравнению с генерацией списка, поэтому для небольших списков это не дает никаких преимуществ.Но теперь представьте, что мы использовали even(100000), тогда количество «бесполезных циклов ЦП», которые мы потратили на создание целого списка, может быть значительным.

Экономия памяти

Еще одно потенциальное преимущество - экономияпамяти, если нам не нужны все элементы генератора в памяти одновременно.

Возьмем, к примеру, следующий пример:

for x in even(1000):
    print(x)

Если even(..) создаетсписок элементов 1000, то это означает, что все эти числа должны быть объектами в памяти одновременно.В зависимости от интерпретатора Python объекты могут занимать значительное количество памяти.Например, int занимает в CPython 28 байтов памяти.Это означает, что список, содержащий 500 таких int с, может занимать примерно 14 КБ памяти (некоторая дополнительная память для списка).Да, большинство интерпретаторов Python поддерживают шаблон «flyweight», чтобы уменьшить нагрузку на небольшие целые числа (они являются общими, и поэтому мы не создаем отдельный объект для каждого int, который мы создаем в процессе), новсе же это может легко сложить.Для even(1000000) нам потребуется 14 МБ памяти.

Если мы используем генератор, то в зависимости от того, как мы используем генератор, мы можем сохранить память.Зачем?Поскольку, если нам больше не нужен номер 123456 (поскольку цикл for переходит к следующему элементу), пространство, которое «занял» объект, может быть переработано и передано объекту int со значением 12348.Таким образом, это означает, что - учитывая то, как мы используем генератор, это позволяет - что использование памяти остается постоянным, тогда как для списка оно масштабируется линейно.Конечно, сам генератор также должен осуществлять надлежащее управление: если в коде генератора мы создадим коллекцию, то память, конечно, также увеличится.

В 32-битных системах это может даже привести кв некоторых проблемах, поскольку списки Python имеют максимальная длина .Список может содержать не более 536'870'912 элементов.Да, это огромное число, но что если вы, например, хотите сгенерировать все перестановки из данного списка?Если мы сохраняем перестановки в списке, то это означает, что для 32-разрядной системы, списка из 13 (или более элементов), мы никогда не сможем создать такой список.

«онлайн»программы

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

Практическим примером может служить веб-камера, которая каждую секунду создает изображение и отправляет его на веб-сервер Python.В данный момент мы не знаем, как будет выглядеть изображение, которое будет снято веб-камерой в течение 24 часов.Но мы могли бы быть заинтересованы в обнаружении грабителя, который хочет что-то украсть.В этом случае список кадров, таким образом, не будет содержать все изображения.Однако генератор может создать элегантный «протокол», в котором мы итеративно извлекаем изображение, обнаруживаем грабителя и подаем сигнал тревоги, например:

for frame in from_webcam():
    if contains_burglar(frame):
        send_alarm_email('Maurice Moss')

Бесконечные генераторы

Нам не нужны веб-камерыили другое оборудование, чтобы использовать элегантность генераторов.Генераторы могут выдавать «бесконечную» последовательность.Или генератор even может выглядеть, например:

def even():
    i = 0
    while True:
        yield i
        i += 2

Это генератор, который будет в конечном итоге генерировать все четные числа.Если мы продолжим итерацию по нему, в конечном итоге мы получим число 123'456'789'012'345'678 (хотя это может занять очень много времени).

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

for i in even():
    if is_palindrome(i):
        print(i)

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

«обогащенные» генераторы: range(..) и друзья

В Python многие классы не создают списки при их итерации по ним, например, объект range(1000) не сначала создает список (в , но не в ).Объект range(..) просто представляет диапазон.Объект range(..) является , а не генератором, но это класс, который может генерировать объект итератора, который работает как генератор.

Помимо итерации, мы можем делать все виды вещейс объектом range(..), что возможно со списками, но не эффективным способом.

Если, например, мы хотим узнать, является ли 1000000000 элементом range(400, 10000000000, 2)тогда мы можем написать 1000000000 in range(400, 10000000000, 2).Теперь существует алгоритм, который будет проверять этот без генерации диапазона или построения списка: он видит, является ли элемент int, находится ли он в диапазоне объекта range(..) (такбольше или равно 400 и меньше 10000000000), и независимо от того, получен ли он (с учетом шага), не требует итерации по нему.В результате проверка членства может быть выполнена мгновенно.

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

Мы также можем «нарезать» объект диапазона, что приводит к другому объекту range(..), например:

>>> range(123, 456, 7)[1::4]
range(130, 459, 28)

с помощью алгоритма мы можем мгновенно нарезать объект range(..) на новый объект range.Нарезка списка занимает линейное время.Это может снова (для огромных списков) занять значительное время и память.

0 голосов
/ 22 сентября 2018

Генераторы короче и более читабельны:

В вашем примере вы должны создать пустой список, использовать append и вернуть получившийся список:

def even(k):
    evens_list = []
    for i in range(k):
        if i % 2 != 0:
           evens_list.append(i)
    return evens_list

Генератор простонужно yield:

def even(k):
    for i in range(k):
        if i % 2 != 0:
           yield i

И использование практически такое же, если вам нужен действительно список.Вместо

event_list = even(100)

строка становится

event_list = list(even(100))
0 голосов
/ 22 сентября 2018

Генератор, но в целом ленивая семантика предлагает некоторые преимущества:

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

Но также есть некоторые недостатки:

  • Накладные расходы
    • Вам необходимо хранить в памяти переменные функции генератора
    • также риск утечки памяти
  • Каждый раз, когда вы хотите повторно использоватьэлементы в списке должны быть восстановлены
0 голосов
/ 22 сентября 2018

Вы можете легко и эффективно преобразовать выходные данные генератора в список с помощью конструктора list():

even_list = list(even(100))
...