Python: трюки с генераторами. Часть 1 

3567

Введение

  • Генераторы это круто!
  • Но что они такое?
  • И для чего они хороши?
  • Вот о чем этот учебник
Python: трюки с генераторами. Часть 1 
Python: трюки с генераторами. Часть 1

Наша цель

  • Изучить практическое использование генераторов
  • Сфокусироваться на «системном программировании»
  • Что включает в себя файлы, файловые системы, синтаксический разбор, сеть, потоки и т.д.
  • Моя цель: предоставить несколько более убедительных примеров использования генераторов

Отречение

  • Это не полный учебник по генераторам и связанной с ними теории
  • Курс будем рассматривать на серии примеров
  • Я не знаю, является ли написанный мною код «лучшим» способом решения любой из этих проблем.
  • Давайте обсуждать

Часть I

Введение в итераторы и генераторы

Итерация

  • Как вы знаете, в Python есть оператор for
  • Вы используете его, чтобы перебрать коллекцию элементов
>>> for x in [1,4,5,10]:
... print(x, end=' ')
...
1 4 5 10
>>>
  • И, как вы, наверное, заметили, вы можете перебирать различные типы объектов (не только списки)

Итерация по словарю

  • Если вы перебираете словарь, вы получаете ключи
>>> prices = { 'GOOG' : 490.10,
... 'AAPL' : 145.23,
... 'YHOO' : 21.71 }
...
>>> for key in prices:
... print(key)
...
YHOO
GOOG
AAPL
>>>

Итерация по строке

  • Если вы перебираете строку, вы получаете символы
>>> s = "Yow!"
>>> for c in s:
... print(c)
...
Y
o
w
!
>>>

Итерация по файлу

  • Если вы итерируете по файлу, вы получаете строки
>>> for line in open("real.txt"):
... print(line, end='')
...
 Real Programmers write in FORTRAN
 Maybe they do now,
 in this decadent era of
 Lite beer, hand calculators, and "user-friendly" software
 but back in the Good Old Days,
 when the term "software" sounded funny
 and Real Computers were made out of drums and vacuum tubes,
 Real Programmers wrote in machine code.
 Not FORTRAN. Not RATFOR. Not, even, assembly language.
 Machine Code.
 Raw, unadorned, inscrutable hexadecimal numbers.
 Directly.

Потребление итерируемых объектов

  • Многие операции потребляют итерируемый объект
  • Редукции:
    sum(s)min(s)max(s)
  • Конструкторы
    list(s)tuple(s)set(s)dict(s)
  • Различные операторы
    item in s
  • Many others in the library

Протокол итерации

  • Причина, по которой вы можете перебирать различные объекты, заключается в том, что существует определенный протокол (интерфейс):
>>> items = [1, 4, 5]
>>> it = iter(items)
>>> it.__next__()
1
>>> it.__next__()
4
>>> it.__next__()
5
>>> it.__next__()
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
StopIteration
>>>
  • Взгляд изнутри на цикл for
for x in obj:
    # statements
  • Под капотом
_iter = iter(obj)            # Получить объект итератора
while 1:
    try:
        x = _iter.__next__() # Получить следующий элемент
    except StopIteration:    # Больше нет элементов
        break
 # statements
 ...
  • Любой объект, который поддерживает метод iter(), называется «итерируемым».

Поддержка итерации

  • Пользовательские объекты могут поддерживать итерацию
  • Пример: обратный отсчет …
>>> for x in countdown(10):
... print(x, end=' ')
...
10 9 8 7 6 5 4 3 2 1
>>>
  • Для этого вы должны реализовать для объекта методы __iter__() и __next__()
  • Пример реализации
class countdown(object):
    def __init__(self,start):
        self.start = start
    def __iter__(self):
        return countdown_iter(self.start)

class countdown_iter(object):
    def __init__(self, count):
        self.count = count
    def __next__(self):
        if self.count <= 0:
            raise StopIteration
        r = self.count
        self.count -= 1
        return r

Пример итерации

  • Пример использования:
>>> c = countdown(5)
>>> for i in c:
... print(i, end=' ')
...
5 4 3 2 1
>>>

Комментарии по итерации

  • Есть много тонких деталей, связанных с реализацией итераторов для различных объектов.
  • Тем не менее, мы не собираемся это рассматривать
  • Потому что это не учебник по итераторам
  • Мы говорим о генераторах …

Генераторы

  • Генератор — это функция, которая выдает последовательность результатов вместо одного значения
def countdown(n):
    while n > 0:
    yield n
    n -= 1
>>> for i in countdown(5):
... print(i, end=' ')
...
5 4 3 2 1
>>>
  • Вместо того, чтобы возвращать значение, вы генерируете серию значений (используя оператор yield)
  • Поведение генератора совершенно отличается от нормальной функции
  • Вызов функции-генератора создает объект генератора. Тем не менее, этот вызов не запускает функцию.
def countdown(n):
    print("Counting down from", n)
    while n > 0:
        yield n
        n -= 1
>>> x = countdown(10)
>>> x
<generator object at 0x58490>
>>>

Обратите внимание, что не было никакого вывода

Функции генератора

  • Функция выполняется только в __next __()
>>> x = countdown(10)
>>> x
<generator object at 0x58490>
>>> x.__next__()
Counting down from 10
10
>>>
  • yield возвращает значение, но приостанавливает функцию
  • Функция возобновляется при следующем вызове __next __()
>>> x.__next__()
9
>>> x.__next__()
8
>>>
  • Когда генератор делает return (в т.ч. неявный дойдя до конца функции), итерация останавливается
>>> x.__next__()
1
>>> x.__next__()
Traceback (most recent call last):
 File "<stdin>", line 1, in ?
StopIteration
>>>
  • Генераторная функция — гораздо более удобный способ написания итератора, чем класс.
  • Вам не нужно беспокоиться о протоколе итератора (__next____iter__ и StopIteration exception)
  • Это просто работает

Генераторы против итераторов

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

Генераторные выражения

  • Генераторная версия вычислимого списка (list comprehension)
>>> a = [1,2,3,4]
>>> b = (2*x for x in a)
>>> b
<generator object at 0x58760>
>>> for i in b: print(b, end=' ')
...
2 4 6 8
>>>
  • Этот цикл проходит последовательность элементов и применяет операцию к каждому элементу.
  • Тем не менее, результаты производятся по одному с использованием генератора
  • Важные отличия от вычислимого списка
    • Не создает список в памяти
    • Единственная полезная цель — итерация
    • После употребления не может быть повторно использован
  • Пример
>>> a = [1,2,3,4]
>>> b = [2*x for x in a] # list comprehension
>>> b
[2, 4, 6, 8]
>>> c = (2*x for x in a) # lazy generator
<generator object at 0x58760>
>>>
  • Общий синтаксис
    (expression for i in s if condition)
  • Что это значит
for i in s:
    if condition:
        yield expression

Примечание о синтаксисе

  • Круглые скобки в генераторном выражении могут быть отброшены, если оно используются в качестве единственного аргумента функции.
    sum(x*x for x in s)

Интерлюдия

  • Теперь у нас есть два базовых строительных блока
    • Функции-генераторы:
def countdown(n):
    while n > 0:
        yield n
        n -= 1
  • Генераторные выражения
squares = (x*x for x in s)
  • В обоих случаях мы получаем объект, который генерирует значения (который обычно используются в цикле for)

источник