Введение
В этом разделе рассматривается основательный процесс итерации.
This tutorial is from open-source community. Access the source code
В этом разделе рассматривается основательный процесс итерации.
Многие разные объекты поддерживают итерацию.
a = 'hello'
for c in a: ## Перебираем символы в a
...
b = { 'name': 'Dave', 'password': 'foo'}
for k in b: ## Перебираем ключи в словаре
...
c = [1,2,3,4]
for i in c: ## Перебираем элементы в списке/кортеже
...
f = open('foo.txt')
for x in f: ## Перебираем строки в файле
...
Рассмотрим оператор for.
for x in obj:
## statements
Что происходит "под капотом"?
_iter = obj.__iter__() ## Получаем объект-итератор
while True:
try:
x = _iter.__next__() ## Получаем следующий элемент
## statements...
except StopIteration: ## Больше элементов нет
break
Все объекты, которые работают с циклом for, реализуют этот низкоуровневый протокол итерации.
Пример: Ручная итерация по списку.
>>> x = [1,2,3]
>>> it = x.__iter__()
>>> it
<listiterator object at 0x590b0>
>>> it.__next__()
1
>>> it.__next__()
2
>>> it.__next__()
3
>>> it.__next__()
Traceback (most recent call last):
File "<stdin>", line 1, in? StopIteration
>>>
Знание о итерации полезно, если вы хотите добавить его в свои собственные объекты. Например, создать пользовательский контейнер.
class Portfolio:
def __init__(self):
self.holdings = []
def __iter__(self):
return self.holdings.__iter__()
...
port = Portfolio()
for s in port:
...
Создайте следующий список:
a = [1,9,4,25,16]
Вручную итерируйтесь по этому списку. Вызовите __iter__(), чтобы получить итератор, и вызовите метод __next__(), чтобы получить последовательные элементы.
>>> i = a.__iter__()
>>> i
<listiterator object at 0x64c10>
>>> i.__next__()
1
>>> i.__next__()
9
>>> i.__next__()
4
>>> i.__next__()
25
>>> i.__next__()
16
>>> i.__next__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>>
Встроенная функция next() - это сокращение для вызова метода __next__() итератора. Попробуйте использовать ее для файла:
>>> f = open('portfolio.csv')
>>> f.__iter__() ## Примечание: Возвращает сам файл
<_io.TextIOWrapper name='portfolio.csv' mode='r' encoding='UTF-8'>
>>> next(f)
'name,shares,price\n'
>>> next(f)
'"AA",100,32.20\n'
>>> next(f)
'"IBM",50,91.10\n'
>>>
Продолжайте вызывать next(f), пока не дойдете до конца файла. Посмотрите, что произойдет.
Иногда вы можете захотеть, чтобы один из своих собственных объектов поддерживал итерацию - особенно если ваш объект оборачивает существующий список или другой итерируемый объект. В новом файле portfolio.py определите следующий класс:
## portfolio.py
class Portfolio:
def __init__(self, holdings):
self._holdings = holdings
@property
def total_cost(self):
return sum([s.cost for s in self._holdings])
def tabulate_shares(self):
from collections import Counter
total_shares = Counter()
for s in self._holdings:
total_shares[s.name] += s.shares
return total_shares
Этот класс предназначен для обертки вокруг списка, но с некоторыми дополнительными методами, такими как свойство total_cost. Измените функцию read_portfolio() в report.py, чтобы она создавала экземпляр Portfolio так:
## report.py
...
import fileparse
from stock import Stock
from portfolio import Portfolio
def read_portfolio(filename):
'''
Считывает файл портфеля акций в список словарей с ключами
name, shares и price.
'''
with open(filename) as file:
portdicts = fileparse.parse_csv(file,
select=['name','shares','price'],
types=[str,int,float])
portfolio = [ Stock(d['name'], d['shares'], d['price']) for d in portdicts ]
return Portfolio(portfolio)
...
Попробуйте запустить программу report.py. Вы обнаружите, что она spectacularly завершается из-за того, что экземпляры Portfolio не итерируемы.
>>> import report
>>> report.portfolio_report('portfolio.csv', 'prices.csv')
... crashes...
Исправьте это, изменив класс Portfolio для поддержки итерации:
class Portfolio:
def __init__(self, holdings):
self._holdings = holdings
def __iter__(self):
return self._holdings.__iter__()
@property
def total_cost(self):
return sum([s.shares*s.price for s in self._holdings])
def tabulate_shares(self):
from collections import Counter
total_shares = Counter()
for s in self._holdings:
total_shares[s.name] += s.shares
return total_shares
После внесения этих изменений программа report.py должна снова работать. В то же время исправьте программу pcost.py, чтобы она использовала новый объект Portfolio. Так:
## pcost.py
import report
def portfolio_cost(filename):
'''
Вычисляет общую стоимость (количество акций * цена) файла портфеля
'''
portfolio = report.read_portfolio(filename)
return portfolio.total_cost
...
Протестируйте ее, чтобы убедиться, что все работает:
>>> import pcost
>>> pcost.portfolio_cost('portfolio.csv')
44671.15
>>>
Если вы создаете класс-контейнер, вы часто хотите сделать больше, чем просто итерацию. Измените класс Portfolio так, чтобы он имел некоторые другие специальные методы, как это:
class Portfolio:
def __init__(self, holdings):
self._holdings = holdings
def __iter__(self):
return self._holdings.__iter__()
def __len__(self):
return len(self._holdings)
def __getitem__(self, index):
return self._holdings[index]
def __contains__(self, name):
return any([s.name == name for s in self._holdings])
@property
def total_cost(self):
return sum([s.shares*s.price for s in self._holdings])
def tabulate_shares(self):
from collections import Counter
total_shares = Counter()
for s in self._holdings:
total_shares[s.name] += s.shares
return total_shares
Теперь попробуйте провести некоторые эксперименты с использованием этого нового класса:
>>> import report
>>> portfolio = report.read_portfolio('portfolio.csv')
>>> len(portfolio)
7
>>> portfolio[0]
Stock('AA', 100, 32.2)
>>> portfolio[1]
Stock('IBM', 50, 91.1)
>>> portfolio[0:3]
[Stock('AA', 100, 32.2), Stock('IBM', 50, 91.1), Stock('CAT', 150, 83.44)]
>>> 'IBM' in portfolio
True
>>> 'AAPL' in portfolio
False
>>>
Одно важное наблюдение по этому поводу - в общем, код считается "питонистским", если он использует общий язык, которым другие части Python обычно работают. Для контейнерных объектов поддержка итерации, индексирования, вхождения и других типов операторов является важной частью этого.
Поздравляем! Вы завершили лабораторную работу по Протоколу итерации. Вы можете практиковаться в других лабораторных работах в LabEx, чтобы улучшить свои навыки.