Introducción
Esta sección analiza el proceso subyacente de iteración.
This tutorial is from open-source community. Access the source code
💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí
Esta sección analiza el proceso subyacente de iteración.
Muchos objetos diferentes admiten iteración.
a = 'hello'
for c in a: ## Recorre los caracteres de a
...
b = { 'name': 'Dave', 'password':'foo'}
for k in b: ## Recorre las claves en el diccionario
...
c = [1,2,3,4]
for i in c: ## Recorre los elementos de una lista/tupla
...
f = open('foo.txt')
for x in f: ## Recorre las líneas de un archivo
...
Considera la instrucción for
.
for x in obj:
## instrucciones
¿Qué sucede por debajo de los paneles?
_iter = obj.__iter__() ## Obtener el objeto iterador
while True:
try:
x = _iter.__next__() ## Obtener el siguiente elemento
## instrucciones...
except StopIteration: ## No hay más elementos
break
Todos los objetos que funcionan con el for-loop
implementan este protocolo de iteración de bajo nivel.
Ejemplo: Iteración manual sobre una 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
>>>
Es útil conocer sobre la iteración si deseas agregarla a tus propios objetos. Por ejemplo, crear un contenedor personalizado.
class Portfolio:
def __init__(self):
self.holdings = []
def __iter__(self):
return self.holdings.__iter__()
...
port = Portfolio()
for s in port:
...
Crea la siguiente lista:
a = [1,9,4,25,16]
Itera manualmente sobre esta lista. Llama a __iter__()
para obtener un iterador y llama al método __next__()
para obtener elementos sucesivos.
>>> 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
>>>
La función integrada next()
es un atajo para llamar al método __next__()
de un iterador. Prueba a usarla en un archivo:
>>> f = open('portfolio.csv')
>>> f.__iter__() ## Nota: Esto devuelve el archivo mismo
<_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'
>>>
Sigue llamando a next(f)
hasta que llegues al final del archivo. Observa lo que sucede.
En ocasiones, es posible que desees hacer que uno de tus propios objetos soporte la iteración, especialmente si tu objeto envuelve una lista existente u otro iterable. En un nuevo archivo portfolio.py
, define la siguiente clase:
## 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 clase está destinada a ser una capa alrededor de una lista, pero con algunos métodos adicionales como la propiedad total_cost
. Modifica la función read_portfolio()
en report.py
de modo que cree una instancia de Portfolio
de la siguiente manera:
## report.py
...
import fileparse
from stock import Stock
from portfolio import Portfolio
def read_portfolio(filename):
'''
Lee un archivo de cartera de acciones en una lista de diccionarios con claves
name, shares y 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)
...
Intenta ejecutar el programa report.py
. Verás que fallará espectacularmente debido a que las instancias de Portfolio
no son iterables.
>>> import report
>>> report.portfolio_report('portfolio.csv', 'prices.csv')
... se detiene...
Corrige esto modificando la clase Portfolio
para que soporte la iteración:
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
Después de hacer este cambio, tu programa report.py
debería funcionar de nuevo. Mientras estás en ello, corrige tu programa pcost.py
para que utilice el nuevo objeto Portfolio
. De la siguiente manera:
## pcost.py
import report
def portfolio_cost(filename):
'''
Calcula el costo total (shares*price) de un archivo de cartera de acciones
'''
portfolio = report.read_portfolio(filename)
return portfolio.total_cost
...
Prueba para asegurarte de que funcione:
>>> import pcost
>>> pcost.portfolio_cost('portfolio.csv')
44671.15
>>>
Si estás creando una clase de contenedor, a menudo quieres hacer más que simplemente iterar. Modifica la clase Portfolio
de modo que tenga algunos otros métodos especiales 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
Ahora, intenta algunos experimentos con esta nueva clase:
>>> 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
>>>
Una observación importante al respecto: generalmente, el código se considera "pythonista" si habla el vocabulario común de cómo funcionan normalmente otras partes de Python. Para los objetos de contenedor, el soporte para la iteración, la indexación, la contención y otros tipos de operadores es una parte importante de esto.
¡Felicitaciones! Has completado el laboratorio del Protocolo de Iteración. Puedes practicar más laboratorios en LabEx para mejorar tus habilidades.