Cómo implementar la iteración en un objeto personalizado de Python

PythonPythonBeginner
Practicar Ahora

💡 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

Los mecanismos de iteración integrados de Python, como los bucles for y las comprensiones de listas, son herramientas poderosas para trabajar con colecciones de datos. Sin embargo, ¿y si quieres crear tus propios objetos personalizados que se puedan iterar? En este tutorial, exploraremos cómo implementar la iteración en un objeto personalizado de Python, lo que te permitirá aprovechar al máximo las capacidades iterativas de Python.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/AdvancedTopicsGroup(["Advanced Topics"]) python/AdvancedTopicsGroup -.-> python/iterators("Iterators") python/AdvancedTopicsGroup -.-> python/generators("Generators") python/AdvancedTopicsGroup -.-> python/context_managers("Context Managers") subgraph Lab Skills python/iterators -.-> lab-397736{{"Cómo implementar la iteración en un objeto personalizado de Python"}} python/generators -.-> lab-397736{{"Cómo implementar la iteración en un objeto personalizado de Python"}} python/context_managers -.-> lab-397736{{"Cómo implementar la iteración en un objeto personalizado de Python"}} end

Comprendiendo la iteración en Python

¿Qué es la iteración?

La iteración en la programación se refiere al proceso de ejecutar repetidamente un conjunto de instrucciones o un bloque de código. En Python, la iteración es un concepto fundamental que te permite trabajar con secuencias, como listas, tuplas y cadenas, así como con otros objetos iterables.

Objetos iterables

Un objeto iterable es un objeto que se puede iterar, es decir, se puede recorrer en un bucle y se pueden acceder a sus elementos uno por uno. En Python, los objetos iterables comunes incluyen:

  • Listas
  • Tuplas
  • Cadenas
  • Diccionarios
  • Conjuntos
  • Archivos
  • Objetos personalizados que implementen el protocolo de iterador

El bucle for

El bucle for es la forma más común de iterar sobre un objeto iterable en Python. El bucle for te permite ejecutar un bloque de código para cada elemento en el objeto iterable.

fruits = ['apple', 'banana', 'cherry']
for fruit in fruits:
    print(fruit)

Salida:

apple
banana
cherry

El bucle while

El bucle while es otra forma de implementar la iteración en Python. El bucle while continúa ejecutando un bloque de código mientras una condición especificada sea verdadera.

count = 0
while count < 5:
    print(count)
    count += 1

Salida:

0
1
2
3
4

Iteradores y el protocolo de iterador

Detrás de escena, el bucle for y otros mecanismos de iteración en Python utilizan el protocolo de iterador. Un iterador es un objeto que implementa el protocolo de iterador, que define dos métodos: __iter__() y __next__(). El método __iter__() devuelve el objeto iterador en sí mismo, y el método __next__() devuelve el siguiente elemento en la secuencia.

class MyIterator:
    def __init__(self, data):
        self.data = data
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.index < len(self.data):
            result = self.data[self.index]
            self.index += 1
            return result
        else:
            raise StopIteration()

my_iterator = MyIterator([1, 2, 3, 4, 5])
for item in my_iterator:
    print(item)

Salida:

1
2
3
4
5

Implementando iteradores personalizados

El protocolo de iterador

Como se mencionó anteriormente, el protocolo de iterador en Python define dos métodos: __iter__() y __next__(). Para crear un iterador personalizado, debes implementar estos dos métodos en tu propia clase.

Implementando el método __iter__()

El método __iter__() debe devolver el objeto iterador en sí mismo. Este método se llama cuando se utiliza la función iter() o cuando se utiliza el objeto en un bucle for.

class MyIterator:
    def __init__(self, data):
        self.data = data
        self.index = 0

    def __iter__(self):
        return self

Implementando el método __next__()

El método __next__() debe devolver el siguiente elemento en la secuencia. Si no hay más elementos, debe levantar una excepción StopIteration.

class MyIterator:
    def __init__(self, data):
        self.data = data
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.index < len(self.data):
            result = self.data[self.index]
            self.index += 1
            return result
        else:
            raise StopIteration()

Usando el iterador personalizado

Una vez que hayas implementado el protocolo de iterador, puedes usar tu iterador personalizado en un bucle for o con otros mecanismos de iteración.

my_iterator = MyIterator([1, 2, 3, 4, 5])
for item in my_iterator:
    print(item)

Salida:

1
2
3
4
5

Evaluación diferida con generadores

Los generadores son un tipo especial de función que se puede utilizar para crear iteradores personalizados. Los generadores utilizan la palabra clave yield para devolver valores uno por uno, en lugar de construir una lista completa en memoria.

def my_generator(n):
    i = 0
    while i < n:
        yield i
        i += 1

my_gen = my_generator(5)
for item in my_gen:
    print(item)

Salida:

0
1
2
3
4

Los generadores pueden ser más eficientes en términos de memoria que crear una lista completa, especialmente cuando se trabaja con conjuntos de datos grandes o infinitos.

Aplicando objetos personalizados iterativos

Casos de uso para iteradores personalizados

Los iteradores personalizados pueden ser útiles en una variedad de escenarios, como:

  • Iterar sobre conjuntos de datos grandes o infinitos sin consumir demasiada memoria
  • Implementar estructuras de datos personalizadas que se pueden iterar
  • Proporcionar una forma más intuitiva o específica del dominio de iterar sobre datos

Ejemplo: Iterando sobre un árbol de búsqueda binario

Consideremos un ejemplo de un árbol de búsqueda binario (BST) que se puede iterar utilizando un iterador personalizado.

class Node:
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None

class BinarySearchTree:
    def __init__(self):
        self.root = None

    def insert(self, value):
        ## Implementación del método insert omitida por brevedad

    def __iter__(self):
        return BSTIterator(self.root)

class BSTIterator:
    def __init__(self, root):
        self.stack = []
        self.push_left_children(root)

    def __next__(self):
        if not self.stack:
            raise StopIteration()
        node = self.stack.pop()
        self.push_left_children(node.right)
        return node.value

    def push_left_children(self, node):
        while node:
            self.stack.append(node)
            node = node.left

## Uso de ejemplo
bst = BinarySearchTree()
bst.insert(5)
bst.insert(3)
bst.insert(7)
bst.insert(1)
bst.insert(4)
bst.insert(6)
bst.insert(8)

for value in bst:
    print(value)

Salida:

1
3
4
5
6
7
8

En este ejemplo, hemos implementado un iterador personalizado para la clase BinarySearchTree. La clase BSTIterator utiliza una pila para realizar un recorrido en orden del árbol de búsqueda binario, lo que nos permite iterar sobre los elementos del árbol en orden ascendente.

Iterando sobre secuencias infinitas

Los iteradores personalizados también se pueden utilizar para trabajar con secuencias infinitas, como la secuencia de Fibonacci o la secuencia de números primos. Al utilizar generadores, podemos crear iteradores que pueden generar el siguiente elemento en el momento, sin almacenar toda la secuencia en memoria.

def fibonacci_generator():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

fib = fibonacci_generator()
for i in range(10):
    print(next(fib))

Salida:

0
1
1
2
3
5
8
13
21
34

Al utilizar una función generadora, podemos crear un iterador que puede generar la secuencia de Fibonacci indefinidamente, sin consumir una gran cantidad de memoria.

Resumen

Al final de este tutorial, tendrás una comprensión sólida de cómo implementar la iteración en tus propios objetos personalizados de Python. Aprenderás los fundamentos de los iteradores personalizados, cómo aplicarlos a tus objetos y explorar casos de uso prácticos para esta poderosa técnica. Dominar la iteración personalizada en Python te permitirá crear un código más flexible, eficiente e intuitivo que se integre sin problemas con las características principales del lenguaje.