Fundamentos do Processo Iterativo

Beginner

This tutorial is from open-source community. Access the source code

Introdução

Esta seção aborda o processo subjacente de iteração.

Iteração em Todo Lugar (Iteration Everywhere)

Muitos objetos diferentes suportam iteração.

a = 'hello'
for c in a: ## Loop over characters in a
    ...

b = { 'name': 'Dave', 'password':'foo'}
for k in b: ## Loop over keys in dictionary
    ...

c = [1,2,3,4]
for i in c: ## Loop over items in a list/tuple
    ...

f = open('foo.txt')
for x in f: ## Loop over lines in a file
    ...

Iteração: Protocolo (Iteration: Protocol)

Considere a instrução for.

for x in obj:
    ## statements

O que acontece por baixo dos panos?

_iter = obj.__iter__()        ## Get iterator object
while True:
    try:
        x = _iter.__next__()  ## Get next item
        ## statements ...
    except StopIteration:     ## No more items
        break

Todos os objetos que funcionam com o for-loop implementam este protocolo de iteração de baixo nível.

Exemplo: Iteração manual sobre uma lista.

>>> 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
>>>

Suportando Iteração (Supporting Iteration)

Conhecer a iteração é útil se você deseja adicioná-la aos seus próprios objetos. Por exemplo, criando um container personalizado.

class Portfolio:
    def __init__(self):
        self.holdings = []

    def __iter__(self):
        return self.holdings.__iter__()
    ...

port = Portfolio()
for s in port:
    ...

Exercício 6.1: Iteração Ilustrada (Iteration Illustrated)

Crie a seguinte lista:

a = [1,9,4,25,16]

Itere manualmente sobre esta lista. Chame __iter__() para obter um iterador e chame o método __next__() para obter elementos sucessivos.

>>> 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
>>>

A função embutida next() é um atalho para chamar o método __next__() de um iterador. Tente usá-la em um arquivo:

>>> f = open('portfolio.csv')
>>> f.__iter__()    ## Note: This returns the file itself
<_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'
>>>

Continue chamando next(f) até chegar ao final do arquivo. Observe o que acontece.

Exercício 6.2: Suportando Iteração (Supporting Iteration)

Em algumas ocasiões, você pode querer que um de seus próprios objetos suporte iteração – especialmente se seu objeto encapsula uma lista existente ou outro iterável. Em um novo arquivo portfolio.py, defina a seguinte classe:

## 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

Esta classe foi projetada para ser uma camada em torno de uma lista, mas com alguns métodos extras, como a propriedade total_cost. Modifique a função read_portfolio() em report.py para que ela crie uma instância de Portfolio assim:

## report.py
...

import fileparse
from stock import Stock
from portfolio import Portfolio

def read_portfolio(filename):
    '''
    Leia um arquivo de portfólio de ações em uma lista de dicionários com as chaves
    name, shares e 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)
...

Tente executar o programa report.py. Você descobrirá que ele falha espetacularmente devido ao fato de que as instâncias de Portfolio não são iteráveis.

>>> import report
>>> report.portfolio_report('portfolio.csv', 'prices.csv')
... crashes ...

Corrija isso modificando a classe Portfolio para suportar iteração:

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

Depois de fazer essa alteração, seu programa report.py deve funcionar novamente. Enquanto estiver nisso, corrija seu programa pcost.py para usar o novo objeto Portfolio. Assim:

## pcost.py

import report

def portfolio_cost(filename):
    '''
    Computa o custo total (shares*price) de um arquivo de portfólio
    '''
    portfolio = report.read_portfolio(filename)
    return portfolio.total_cost
...

Teste-o para ter certeza de que funciona:

>>> import pcost
>>> pcost.portfolio_cost('portfolio.csv')
44671.15
>>>

Exercício 6.3: Criando um Container Mais Adequado (Making a more proper container)

Ao criar uma classe container, você frequentemente deseja fazer mais do que apenas iteração. Modifique a classe Portfolio para que ela tenha alguns outros métodos especiais como este:

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

Agora, tente alguns experimentos usando esta nova classe:

>>> 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
>>>

Uma observação importante sobre isso – geralmente, o código é considerado "Pythonic" se ele fala o vocabulário comum de como outras partes do Python normalmente funcionam. Para objetos container, suportar iteração, indexação, contenção e outros tipos de operadores é uma parte importante disso.

Resumo

Parabéns! Você concluiu o laboratório do Protocolo de Iteração (Iteration Protocol). Você pode praticar mais laboratórios no LabEx para aprimorar suas habilidades.