- en
- Language: ru
- Documentation version: latest
Протокол итерации
Итерируемый объект (iterable) - это объект, который способен возвращать элементы по одному.
Для Python это любой объект у которого есть метод __iter__
или метод __getitem__
.
Если у объекта есть метод __iter__, итерируемый объект превращается в итератор вызовом iter(name)
,
где name - имя итерируемого объекта. Если метода __iter__ нет, Python перебирает элементы используя __getitem__.
class Items:
def __init__(self, items):
self.items = items
def __getitem__(self, index):
print('Вызываю __getitem__')
return self.items[index]
In [2]: iterable_1 = Items([1, 2, 3, 4])
In [3]: iterable_1[0]
Вызываю __getitem__
Out[3]: 1
In [4]: for i in iterable_1:
...: print('>>>>', i)
...:
Вызываю __getitem__
>>>> 1
Вызываю __getitem__
>>>> 2
Вызываю __getitem__
>>>> 3
Вызываю __getitem__
>>>> 4
Вызываю __getitem__
In [5]: list(map(str, iterable_1))
Вызываю __getitem__
Вызываю __getitem__
Вызываю __getitem__
Вызываю __getitem__
Вызываю __getitem__
Out[5]: ['1', '2', '3', '4']
Если у объекта есть метод __iter__ (который обязан возвращать итератор), при переборе значений используется он:
class Items:
def __init__(self, items):
self.items = items
def __getitem__(self, index):
print('Вызываю __getitem__')
return self.items[index]
def __iter__(self):
print('Вызываю __iter__')
return iter(self.items)
In [12]: iterable_1 = Items([1, 2, 3, 4])
In [13]: for i in iterable_1:
...: print('>>>>', i)
...:
Вызываю __iter__
>>>> 1
>>>> 2
>>>> 3
>>>> 4
In [14]: list(map(str, iterable_1))
Вызываю __iter__
Out[14]: ['1', '2', '3', '4']
В Python за получение итератора отвечает функция iter():
In [1]: lista = [1, 2, 3]
In [2]: iter(lista)
Out[2]: <list_iterator at 0xb4ede28c>
Функция iter
отработает на любом объекте, у которого есть метод __iter__ или метод __getitem__.
Метод __iter__ возвращает итератор. Если этого метода нет, функция iter() проверяет, нет ли метода __getitem__ - метода, который позволяет получать элементы по индексу.
Если метод __getitem__ есть, элементы будут перебираться по индексу (начиная с 0).
Итератор (iterator) - это объект, который возвращает свои элементы по одному за раз. С точки зрения Python - это любой объект, у которого есть метод __next__. Этот метод возвращает следующий элемент, если он есть, или возвращает исключение StopIteration, когда элементы закончились. Кроме того, итератор запоминает, на каком объекте он остановился в последнюю итерацию. Также у каждого итератора присутствует метод __iter__ - то есть, любой итератор является итерируемым объектом. Этот метод возвращает сам итератор.
Пример создания итератора из списка:
In [3]: lista = [1, 2, 3]
In [4]: i = iter(lista)
Теперь можно использовать функцию next(), которая вызывает метод __next__, чтобы взять следующий элемент:
In [5]: next(i)
Out[5]: 1
In [6]: next(i)
Out[6]: 2
In [7]: next(i)
Out[7]: 3
In [8]: next(i)
------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-8-bed2471d02c1> in <module>()
----> 1 next(i)
StopIteration:
После того, как элементы закончились, возвращается исключение StopIteration. Для того, чтобы итератор снова начал возвращать элементы, его надо заново создать. Аналогичные действия выполяются, когда цикл for проходится по списку:
In [9]: for item in lista:
...: print(item)
...:
1
2
3
Когда мы перебираем элементы списка, к списку сначала применяется функция iter(), чтобы создать итератор, а затем вызывается его метод __next__ до тех пор, пока не возникнет исключение StopIteration.
Пример функции my_for, которая работает с любым итерируемым объектом и имитирует работу встроенной функции for:
def my_for(iterable):
if getattr(iterable, "__iter__", None):
print('Есть __iter__')
iterator = iter(iterable)
while True:
try:
print(next(iterator))
except StopIteration:
break
elif getattr(iterable, "__getitem__", None):
print('Нет __iter__, но есть __getitem__')
index = 0
while True:
try:
print(iterable[index])
index += 1
except IndexError:
break
Проверка работы функции на объекте у которого есть метод __iter__:
In [18]: my_for([1,2,3,4])
Есть __iter__
1
2
3
4
Проверка работы функции на объекте у которого нет метода __iter__, но есть __getitem__:
class Items:
def __init__(self, items):
self.items = items
def __getitem__(self, index):
print('Вызываю __getitem__')
return self.items[index]
In [20]: iterable_1 = Items([1,2,3,4,5])
In [21]: my_for(iterable_1)
Нет __iter__, но есть __getitem__
Вызываю __getitem__
1
Вызываю __getitem__
2
Вызываю __getitem__
3
Вызываю __getitem__
4
Вызываю __getitem__
5
Вызываю __getitem__
Создание итератора
Пример класса Network:
In [10]: import ipaddress
...:
...: class Network:
...: def __init__(self, network):
...: self.network = network
...: subnet = ipaddress.ip_network(self.network)
...: self.addresses = [str(ip) for ip in subnet.hosts()]
Пример создания экземпляра класса Network:
In [14]: net1 = Network('10.1.1.192/30')
In [15]: net1
Out[15]: <__main__.Network at 0xb3124a6c>
In [16]: net1.addresses
Out[16]: ['10.1.1.193', '10.1.1.194']
In [17]: net1.network
Out[17]: '10.1.1.192/30'
Создаем итератор из класса Network:
In [12]: class Network:
...: def __init__(self, network):
...: self.network = network
...: subnet = ipaddress.ip_network(self.network)
...: self.addresses = [str(ip) for ip in subnet.hosts()]
...: self._index = 0
...:
...: def __iter__(self):
...: print('Вызываю __iter__')
...: return self
...:
...: def __next__(self):
...: print('Вызываю __next__')
...: if self._index < len(self.addresses):
...: current_address = self.addresses[self._index]
...: self._index += 1
...: return current_address
...: else:
...: raise StopIteration
...:
Метод __iter__ в итераторе должен возвращать сам объект, поэтому в методе
указано return self
, а метод __next__ возвращает элементы по одному и генерирует
исключение StopIteration, когда элементы закончились.
In [14]: net1 = Network('10.1.1.192/30')
In [15]: for ip in net1:
...: print(ip)
...:
Вызываю __iter__
Вызываю __next__
10.1.1.193
Вызываю __next__
10.1.1.194
Вызываю __next__
Чаще всего, итератор это одноразовый объект и перебрав элементы, мы уже не можем это сделать второй раз:
In [16]: for ip in net1:
...: print(ip)
...:
Вызываю __iter__
Вызываю __next__
Создание итерируемого объекта
Очень часто классу достаточно быть итерируемым объектом и не обязательно быть итератором. Если объект будет итерируемым, его можно использовать в цикле for, функциях map, filter, sorted, enumerate и других. Также, как правило, объект проще сделать итерируемым, чем итератором.
Для того чтобы класс Network создавал итерируемые объекты, надо чтобы в классе был метод
__iter__ (__next__ не нужен) и чтобы метод возвращал итератор.
Так как в данном случае, Network перебирает адреса, которые находятся в списке self.addresses,
самый просто вариант возвращать итератор, это вернуть iter(self.addresses)
:
In [17]: class Network:
...: def __init__(self, network):
...: self.network = network
...: subnet = ipaddress.ip_network(self.network)
...: self.addresses = [str(ip) for ip in subnet.hosts()]
...:
...: def __iter__(self):
...: return iter(self.addresses)
...:
Теперь все экземпляры класса Network будут итерируемыми объектами:
In [18]: net1 = Network('10.1.1.192/30')
In [19]: for ip in net1:
...: print(ip)
...:
10.1.1.193
10.1.1.194