14.6 Iterable

Além de conter objetos e saber retornar a quantidade de seus elementos, queremos que nosso container seja iterável, ou seja, que consigamos iterar sobre seus elementos em um laço for, por exemplo. O módulo collection.abc também provê uma classe abstrata para este comportamento, é a classe Iterable . Iterable suporta iteração com o método iter :

from collections.abc import Container

class Funcionarios(Container, Sized, Iterable):

_dados = []

def contains (self, item):
 
return self._dados. contains (self, item)
 
def len (self):
 
return len(self._dados)
 
def iter (self):
 
return self._dados. iter (self)
 
java
if name == ' main ': funcionarios = Funcionarios()

Toda coleção deve herdar dessas classes ABCs: Container , Iterable e Sized . Ou implementar seus protocolos: contains , iter e len .

Além dessas classes, existem outras que facilitam esse trabalho e implementam outros protocolos.

Veja a hierarquia de classe do módulo collections.abc:

Além do que já foi implementado, a ideia é que nossa classe Funcionario funcione como uma lista contando apenas objetos do tipo Funcionario . Como aprendemos no capítulo 4, uma lista é uma sequência. Além de uma sequência, é uma sequência mutável - podemos adicionar elementos em uma lista. Nossa classe Funcionario também deve possuir essa funcionalidade.

Segundo o diagrama de classes do módulo collections.abc , a classe que representa essa estrutura é a MutableSequence . Note que MutableSequence herda de Sequence que representa uma sequência; que por sua vez herda de Container , Iterable e Sized .

Portanto, devemos implementar 5 métodos abstratos (em itálico na imagem) segundo a

documentação de MutableSequence : len , getitem , setitem , delitem e

insert . O método getitem garante que a classe é um Container e Iterable . Segundo a

PEP 234 (https://www.python.org/dev/peps/pep-0234/) um objeto pode ser iterável com um laço for se implementa iter ou getitem .

Então, basta nossa classe Funcionario herdar de MutableSequence e implementar seus métodos abstratos:

from collections.abc import MutableSequence class Funcionarios(MutableSequence):

_dados = []

def len (self):
 
return len(self._dados)
 
def getitem (self, posicao): return self._dados[posicao]
 
def setitem (self, posicao, valor): self._dados[posicao] = valor
 
def delitem (self, posicao): del self._dados[posicao]
 
python
def insert(self, posicao, valor):

return self._dados.insert(posicao, valor)

E podemos voltar ao nosso código para acrescentar os dados de um arquivo em nosso container

Funcionarios :

import csv

arquivo = open(‘funcionarios.txt’, ‘r’) leitor = csv.reader(arquivo)

funcionarios = Funcionarios() for linha in leitor:

funcionario = Funcionario(linha[0], linha[1], float(linha[2])) funcionarios.append(funcionario)

arquivo.close()

O método insert() garante o funcionamento do método append() . E podemos imprimir os valores dos salários de cada funcionário:

for f in funcionarios: print(f.salario)

Mas até aqui não há nada de diferente de uma lista comum. Ainda não há nada que impeça de inserir qualquer outro objeto em nossa lista. Nossa classe Funcionarios se comporta como uma lista comum. A ideia de implementarmos as interfaces de collections.abc era exatamente modificar alguns comportamentos.

Queremos que nossa lista de funcionários apenas aceite objetos da classe Funcionario . Vamos

sobrescrever os métodos setitem () que atribuiu um valor em determinada posição na lista. Este método pode apenas atribuir a uma determinada posição um objeto Funcionario .

Para isso, vamos usar o método isinstance() que vai verificar se o objeto a ser atribuído é uma instância de Funcionario . Caso contrário, vamos lançar uma exceção TypeError com uma mensagem de erro:

def setitem (self, posicao, valor): if (isinstance(valor, Funcionario)):
 
self._dados[posicao] = valor else:
 
raise TypeError('Valor atribuído não é um Funcionario')
 

Agora, ao tentar atribuir uma valor a determinada posição de nossa lista, recebemos um

TypeError :

funcionarios[0] = ‘Python’

Saída:

Traceback (most recent call last):
 
File <stdin>, line 18, in setitem
 
raise TypeError('Valor atribuído não é um Funcionario') TypeError: Valor atribuído não é um Funcionario
 
Faremos o mesmo com o método insert() :
 
python
def insert(self, posicao, valor): if(isinstance(valor, Funcionario)):

return self._dados.insert(posicao, valor) else:

raise TypeError('Valor inserido não é um Funcionario')
 

E podemos testar nossa classe imprimindo não apenas o salário mas o valor da bonificação de cada

Funcionario através do método get_bonificacao() que definimos nos capítulos passados:

if name == ' main ': import csv

arquivo = open(‘funcionarios.txt’, ‘r’) leitor = csv.reader(arquivo)

funcionarios = Funcionarios() for linha in leitor:

funcionario = Funcionario(linha[0], linha[1], float(linha[2])) funcionarios.append(funcionario)

print('salário - bonificação') for f in funcionarios:
python
print('{} - {}'.formar(f.salario, f.get_bonificacao())) arquivo.close()

As classes ABCs foram criadas para encapsular conceitos genéricos e abstrações como aprendemos no capítulo de classes abstratas. São comumente utilizadas em grandes aplicações e frameworks para

garantir a consistência do sistema através dos métodosisinstance() e issubclass() . No dia a dia é raramente usado e basta o uso correto das estruturas já fornecidas pela biblioteca padrão do Python para a maior parte das tarefas.‌


⬅️ Capítulo Anterior | Próximo Capítulo ➡️