Advertisement
Dmitry_Dronov

Untitled

Dec 3rd, 2016
401
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.63 KB | None | 0 0
  1. Этот трюк поможет тебе понять идею позади функции, а также наглядно разложит логику её воплощения. С одной лишь разницей: то что происходит когда используется yield сильно отличается от способа с использованием списка. Зачастую подход с использованием генератора (yield делает из обычной функции генератор) будет менее прожорливым: использование памяти будет меньше, а скорость работы выше. А иногда трюк может ввести исполнение функции в бесконечный цикл, несмотря на то, что оригинальная функция работает как следует. Читай дальше, чтобы узнать больше
  2. Различай итерируемые объекты, итераторы и генераторы
  3.  
  4. Для начала разберемся с интерфейсом итератора. Когда ты пишешь:
  5.  
  6. >>> for x in mylist:
  7. >>> .... тело цикла ....
  8.  
  9. Python совершает два следующих шага:
  10.  
  11. Получает объект итератора для списка mylist.
  12.  
  13. Вызов iter(mylist) возвращает объект с методом next()(или __next__() для Py3).
  14.  
  15. >>> iter([1,2,3])
  16. >>> <listiterator object at 0xb72b0e0c>
  17.  
  18.  
  19. Использует полученный итератор для того, чтобы пройти в цикле по элементам списка. Затем for x in mylist: ... последовательно вызывает метод .next(), полученного на первом шаге итератора, и значение, которое возвращает .next() присваивается x. Если список закончился, то .next() генерирует исключение StopIteration.
  20.  
  21. На самом деле Python проделывает описанные шаги всякий раз, когда ему требуется пройтись последовательно по содержимому объекта: как с помощью цикла for, так и в коде вида therlist.extend(mylist)
  22. Итерируемые объекты
  23.  
  24. Наш mylist — это итерируемый объект, который реализует протокол итератора. Если тебе потребуется в каком-либо твоем классе сделать его экземпляры итерируемыми, то для этого потребуется всего лишь реализовать в нём метод __iter__(). Этот метод должен возвращать итератор. Итератор — это объект с методом .next().
  25.  
  26. Наряду с __iter__() ты можешь определить для своего класса и метод .next(), тогда __iter__() будет возвращать self. Правда такой вариант подойдет только для простых случаев. Для более сложных, где требуется нескольким итераторам проходиться по членам одного и того же объекта, уже не сработает.
  27.  
  28. В целом, это и есть протокол итератора. Многие объекты Python его реализуют:
  29.  
  30. Встроенные списки, словари, кортежи, множества, файлы
  31. Пользовательские классы, реализующие метод __iter__()
  32. Генераторы
  33.  
  34. Цикл for не знает с каким объектом взаимодействует. Цикл просто следует протоколу итератора и счастливо берёт элемент за элементом с каждым новым вызовом .next(). Списки возвращают последовательно элемент за элементом, словари отдают последовательно ключи, файлы — строка за строкой. А генераторы... что ж это то место, где вступает в игру yield.
  35.  
  36. >>> # определим функцию
  37. >>> def f123():
  38. >>> yield 1
  39. >>> yield 2
  40. >>> yield 3
  41. >>> for item in f123():
  42. >>> print item
  43. >>> 1
  44. >>> 2
  45. >>> 3
  46.  
  47. Если бы я написал вместо yield три раза return, то тогда при вызове f123() сработал бы первый встреченный return и функция завершила бы свою работу. Однако, так как мы использовали yield, то функция становится неординарной и когда интерпретатор достигает yield, то возвращается объект генератор (а не вычисляемое значение) — затем функция переходит в приостановленное состояние. Когда, к примеру, когда в цикл for передаётся объект генератора, то f123() возобновляет работу, отрабатывает до следующего встреченного yield и возвращает следующее значение. Это происходит до тех пор, пока цикл не завершает свою работу в следствии того, что генератор выдает исключение StopIteration.
  48.  
  49. Таким образом объект генератора выглядит как некоторый адаптер: с одной стороны он реализует протокол итератора, определяя методы __iter__() и next(), с другой стороны он исполняет функцию ровно на столько сколько требуется для получения очередного значения, а затем снова приостанавливает её.
  50. Зачем использовать генераторы
  51.  
  52. Ты можешь писать код, который не использует генераторы, но реализует ту же самую логику. К примеру, используя описанный в начале статьи трюк с временным списком. Этот трюк не будет работать в случаях с бесконечными циклами, слишком большими списками (что влечет чрезмерное потребление памяти)
  53.  
  54. Другой подход заключается в написании какого-нибудь пользовательского класса, который хранит состояние между вызовами в своих экземплярах и реализует в них метод .next() (или __next__() в Py3 ). В зависимости от алгоритма, код внутри метода .next() может быть очень комплексным, что влечет за собой скрытые ошибки и побочные результаты. Вот здесь и засияют генераторы, предоставляя простое решение.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement