Язык программирования

Лекция № 6

Владимир Владимирович Руцкий rutsky.vladimir@gmail.com

_images/cgsg.png _images/pml30.png
1

План занятия

2

Классы в Python. Повторение

3

Итераторы

4

Итераторы в Python

5

Итераторы в Python. Пример

>>> a = ['A', 'B', 'C']
>>> a.__iter__  
<method-wrapper '__iter__' of list object at 0x...>
>>> it = a.__iter__()
>>> it.__next__()
'A'
>>> it.__next__()
'B'
>>> it.__next__()
'C'
>>> it.__next__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>
6

Цикл for

7

Написание итераторов

>>> class MyRange:
...     def __init__(self, stop):
...         self._stop = stop
...         self._next = 0
...     def __iter__(self):
...         return self
...     def __next__(self):
...         if self._next >= self._stop:
...             raise StopIteration
...         else:
...             result, self._next = self._next, self._next + 1
...             return result
...
>>> for i in MyRange(10):
...     print(i, end=" ")
...
0 1 2 3 4 5 6 7 8 9 
>>>
8

Передача последовательностей

9

__iter__() у итераторов

10

Функция iter()

11

Функция next()

12

Пример

>>> r = range(3) # возвращает объект, поддерживающий интерфейс итератора
>>> it = iter(r)
>>> next(it)
0
>>> next(it)
1
>>> next(it)
2
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> it = iter("ABC")
>>> next(it, "Done!")
'A'
>>> next(it, "Done!")
'B'
>>> next(it, "Done!")
'C'
>>> next(it, "Done!")
'Done!'
>>> next(it, "Done!")
'Done!'
>>>
13

Возможности итераторов

Реализация собственных итераторов через реализацию __iter__(), __next__() довольно трудоёмкий процесс, можно прощё!

14

Генераторы

>>> def my_generator(n):
...     for i, ch in zip(range(n), "ABCDEFGHIK"):
...         # Если в функции встречается команда yield, то функция — генератор
...         yield ch + str(i)
...
>>> # Генераторы возвращают итератор
... it = my_generator(3)
>>> it  
<generator object my_generator at 0x...>
>>> # При вызове генератора код тела функции не выполняется.
... # При попытке получить следующее значение итератора функция выполнится до
... # первого yield
... next(it)
'A0'
>>> # При запросе следующего значения, выполнение функции продолжится
... # с места, где она остановилась на yield в последний раз.  Состояние
... # переменных сохраняется, как будто функция и не прерывалась.
... next(it)
'B1'
>>> next(it)
'C2'
>>> next(it) # Когда тело функции заканчивается, итератор считается исчерпанным
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>
15

Пример

>>> # Генераторы очень удобны для создания последовательностей
... # "на лету"
... def my_range(start, stop=None, step=1):
...     if stop is None:
...         start, stop = 0, start
...     while start < stop:
...         yield start
...         start += step
...
>>> list(my_range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list(my_range(3, 5))
[3, 4]
>>> list(my_range(3, 10, 2))
[3, 5, 7, 9]
>>>
16

yield from

>>> # itertools.chain позволяет объединять последовательности
... import itertools
>>> list(itertools.chain(range(5), "ABC", [4, 3, 2, 1, 0]))
[0, 1, 2, 3, 4, 'A', 'B', 'C', 4, 3, 2, 1, 0]
>>> def my_chain(*iterables):
...     for iterable in iterables:
...         for elem in iterable:
...             yield elem
...
>>> list(my_chain("abc", range(4)))
['a', 'b', 'c', 0, 1, 2, 3]
>>> def my_chain2(*iterables):
...     for iterable in iterables:
...         # В "yield from" генератор будет возвращать значения напрямую из
...         # итератора iterable, пока в нём не закончатся элементы.
...         yield from iterable
...
>>> list(my_chain2("abc", range(4)))
['a', 'b', 'c', 0, 1, 2, 3]
>>>
17

Передача значений в yield

>>> def echo(value=None):
...     try:
...         while True:
...             try:
...                 # Команда yield возвращает значение, которое может быть
...                 # передано через iterator.send()
...                 value = (yield value)
...             except Exception as e:
...                 value = e
...     except GeneratorExit:
...         print("У итератора вызвали close() — запрос на отмену итерирования")
...
>>> it = echo(1)  # Передаём начальный value=1
>>> print(next(it))
1
>>> print(next(it))  # По умолчанию результат yield — None
None
>>> # Результат yield можно указать, использовав send() вместо __next__()
... print(it.send(2))
2
>>> # Можно вызвать исключение внутри генератора (в месте yield):
... it.throw(TypeError, "spam")
TypeError('spam',)
>>> # Можно запросить остановку итератора (вызывается при уничтожении объекта, 
... # который не закончил итерирование):
... print(it.close())
У итератора вызвали close() — запрос на отмену итерирования
None
>>>
18

Выражения генераторы

>>> def f(arg, dummy_arg=None):
...     print(type(arg))
...     for i in arg:
...         print(i, end=" ")
...
>>> # Comprehensions, используемые сами по себе, создают контейнеры
... f([x ** 2 for x in range(5)], "test") # будет создан список и передан в f()
<class 'list'>
0 1 4 9 16 
>>> # Можно написать генерацию последовательности без скобок контейнера,
... # тогда будет создан генератор:
... f(x ** 2 for x in range(5))
<class 'generator'>
0 1 4 9 16 
>>> # Или можно написать круглые скобки:
... f((x ** 2 for x in range(5)), "test")
<class 'generator'>
0 1 4 9 16 
>>>
19

Возможности генераторов

20

Декораторы

21

Пример

>>> import datetime
>>> import time
>>> def print_time(func):
...     def wrapped_func(*args, **kwargs):
...         # Обёртка засекает время и вызывает оборачиваемую функцию.
...         start_time = datetime.datetime.now()
...         try:
...             return func(*args, **kwargs)
...         finally:
...             # По завершению работы функции обёртка выводит время работы
...             # функции.
...             end_time = datetime.datetime.now()
...             num_secs = (end_time - start_time).total_seconds()
...             print("Took {} seconds".format(num_secs))
...     # Оборачиваемая функция будет заменена возвращаемой декоратором функцией
...     return wrapped_func
...
>>> @print_time
... def long_running_func():
...     for i in range(10000):
...         pass
...     return 123
>>> long_running_func()  
Took 0.000243 seconds
123
>>>
22

Комбинирование декораторов

23

Практика

24