Python Yield

Yield — ключевое слово, которое используется для выхода из функции-генератора значений списка. Подобные функции-генераторы используются, когда вам нужно создавать какую-то более сложную последовательность, чем возвращаемая range(10).

Для понимания, что делает yield, необходимо понимать, что такое генераторы. Генераторам же предшествуют итераторы. Когда вы создаёте список, вы можете считывать его элементы один за другим — это называется итерацией:

>>> mylist = [1, 2, 3]
>>> for i in mylist :
...    print(i)
1
2
3

Mylist является итерируемым объектом. Когда вы создаёте список, используя генераторное выражение, вы создаёте также итератор:

>>> mylist = [x*x for x in range(3)]
>>> for i in mylist :
...    print(i)
0
1
4

Всё, к чему можно применить конструкцию «for… in…», является итерируемым объектом: списки, строки, файлы… Это удобно, потому что можно считывать из них значения сколько потребуется — однако все значения хранятся в памяти, а это не всегда желательно, если у вас много значений.

Генераторы Python

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

>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator :
...    print(i)
0
1
4

Всё то же самое, разве что используются круглые скобки вместо квадратных. НО: нельзя применить конструкцию for i in mygenerator второй раз, так как генератор может быть использован только единожды: он вычисляет 0, потом забывает про него и вычисляет 1, завершаяя вычислением 4 — одно за другим.

Yield

Yield это ключевое слово, которое используется как return для функции, которая вернёт генератор.

>>> def createGenerator() :
...    mylist = range(3)
...    for i in mylist :
...        yield i*i
...
>>> mygenerator = createGenerator() # создаём генератор
>>> print(mygenerator) # mygenerator является объектом!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
...     print(i)
0
1
4

В данном случае пример бесполезный, но это удобно, если вы знаете, что функция вернёт большой набор значений, который надо будет прочитать только один раз.

Чтобы освоить yield, вы должны понимать, что когда вы вызываете функцию, код внутри тела функции не исполняется. Функция только возвращает объект-генератор — немного мудрёно :-)

Ваш код будет вызываться каждый раз, когда for обращается к генератору.

Теперь трудная часть:

В первый запуск вашей функции, она будет исполняться от начала до того момента, когда она наткнётся на yield — тогда она вернёт первое значение из цикла. На каждый следующий вызов будет происходить ещё одна итерация написанного вами цикла, возвращаться будет следующее значение — и так пока значения не кончатся.

Генератор считается пустым, как только при исполнении кода функции не встречается yield. Это может случиться из-за конца цикла, или же если не выполняется какое-то из условий «if/else».

Метод extend() это метод объекта списка, который ожидает на вход что-нибудь итерируемое и добавляет его значения к списку.

Обычно мы передаём ему список:

>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]

Но в нашем коде он принимает генератор, что хорошо по следующим причинам:

  • Нет необходимости читать значения дважды.
  • Может случиться так, что потомков много и хранить их всех в памяти не хочется.

И это работает, потому что Python всё равно, является аргумент этого метода списком или нет. Python ожидает итерируемый объект, так что это сработает со строками, списками, кортежами и генераторами! Это называется утиной типизацией и является одной из причин, почему Python так крут.

Контроль за исчерпанием генератора

>>> class Bank(): # создаём банк, строящий торговые автоматы (ATM — Automatic Teller Machine)
...    crisis = False
...    def create_atm(self) :
...        while not self.crisis :
...            yield "$100"
>>> hsbc = Bank() # когда всё хорошо, можно получить сколько угодно денег с торгового автомата
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # пришёл кризис, денег больше нет!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # что верно даже для новых автоматов
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # проблема в том, что когда кризис прошёл, автоматы по-прежнему пустые...
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # но если построить ещё один, будешь снова в деле!
>>> for cash in brand_new_atm :
...    print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *