Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- Этот трюк поможет тебе понять идею позади функции, а также наглядно разложит логику её воплощения. С одной лишь разницей: то что происходит когда используется yield сильно отличается от способа с использованием списка. Зачастую подход с использованием генератора (yield делает из обычной функции генератор) будет менее прожорливым: использование памяти будет меньше, а скорость работы выше. А иногда трюк может ввести исполнение функции в бесконечный цикл, несмотря на то, что оригинальная функция работает как следует. Читай дальше, чтобы узнать больше
- Различай итерируемые объекты, итераторы и генераторы
- Для начала разберемся с интерфейсом итератора. Когда ты пишешь:
- >>> for x in mylist:
- >>> .... тело цикла ....
- Python совершает два следующих шага:
- Получает объект итератора для списка mylist.
- Вызов iter(mylist) возвращает объект с методом next()(или __next__() для Py3).
- >>> iter([1,2,3])
- >>> <listiterator object at 0xb72b0e0c>
- Использует полученный итератор для того, чтобы пройти в цикле по элементам списка. Затем for x in mylist: ... последовательно вызывает метод .next(), полученного на первом шаге итератора, и значение, которое возвращает .next() присваивается x. Если список закончился, то .next() генерирует исключение StopIteration.
- На самом деле Python проделывает описанные шаги всякий раз, когда ему требуется пройтись последовательно по содержимому объекта: как с помощью цикла for, так и в коде вида therlist.extend(mylist)
- Итерируемые объекты
- Наш mylist — это итерируемый объект, который реализует протокол итератора. Если тебе потребуется в каком-либо твоем классе сделать его экземпляры итерируемыми, то для этого потребуется всего лишь реализовать в нём метод __iter__(). Этот метод должен возвращать итератор. Итератор — это объект с методом .next().
- Наряду с __iter__() ты можешь определить для своего класса и метод .next(), тогда __iter__() будет возвращать self. Правда такой вариант подойдет только для простых случаев. Для более сложных, где требуется нескольким итераторам проходиться по членам одного и того же объекта, уже не сработает.
- В целом, это и есть протокол итератора. Многие объекты Python его реализуют:
- Встроенные списки, словари, кортежи, множества, файлы
- Пользовательские классы, реализующие метод __iter__()
- Генераторы
- Цикл for не знает с каким объектом взаимодействует. Цикл просто следует протоколу итератора и счастливо берёт элемент за элементом с каждым новым вызовом .next(). Списки возвращают последовательно элемент за элементом, словари отдают последовательно ключи, файлы — строка за строкой. А генераторы... что ж это то место, где вступает в игру yield.
- >>> # определим функцию
- >>> def f123():
- >>> yield 1
- >>> yield 2
- >>> yield 3
- >>> for item in f123():
- >>> print item
- >>> 1
- >>> 2
- >>> 3
- Если бы я написал вместо yield три раза return, то тогда при вызове f123() сработал бы первый встреченный return и функция завершила бы свою работу. Однако, так как мы использовали yield, то функция становится неординарной и когда интерпретатор достигает yield, то возвращается объект генератор (а не вычисляемое значение) — затем функция переходит в приостановленное состояние. Когда, к примеру, когда в цикл for передаётся объект генератора, то f123() возобновляет работу, отрабатывает до следующего встреченного yield и возвращает следующее значение. Это происходит до тех пор, пока цикл не завершает свою работу в следствии того, что генератор выдает исключение StopIteration.
- Таким образом объект генератора выглядит как некоторый адаптер: с одной стороны он реализует протокол итератора, определяя методы __iter__() и next(), с другой стороны он исполняет функцию ровно на столько сколько требуется для получения очередного значения, а затем снова приостанавливает её.
- Зачем использовать генераторы
- Ты можешь писать код, который не использует генераторы, но реализует ту же самую логику. К примеру, используя описанный в начале статьи трюк с временным списком. Этот трюк не будет работать в случаях с бесконечными циклами, слишком большими списками (что влечет чрезмерное потребление памяти)
- Другой подход заключается в написании какого-нибудь пользовательского класса, который хранит состояние между вызовами в своих экземплярах и реализует в них метод .next() (или __next__() в Py3 ). В зависимости от алгоритма, код внутри метода .next() может быть очень комплексным, что влечет за собой скрытые ошибки и побочные результаты. Вот здесь и засияют генераторы, предоставляя простое решение.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement