Fundamentos del Proceso Iterativo

PythonPythonBeginner
Practicar Ahora

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í

Introducción

Esta sección analiza el proceso subyacente de iteración.

Iteración en todos lados

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

Iteración: Protocolo

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

Soporte para iteración

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

Ejercicio 6.1: Iteración ilustrada

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.

Ejercicio 6.2: Soporte para iteración

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

Ejercicio 6.3: Creando un contenedor más adecuado

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.

Resumen

¡Felicitaciones! Has completado el laboratorio del Protocolo de Iteración. Puedes practicar más laboratorios en LabEx para mejorar tus habilidades.