Temas avanzados: Creación de objetos iterables personalizados
En este paso, exploraremos cómo crear sus propios objetos iterables en Python. Esta es una habilidad importante que le permite diseñar estructuras de datos personalizadas que funcionan a la perfección con los mecanismos de iteración de Python.
Comprender el protocolo del iterador
Para crear un objeto iterable personalizado, debe implementar el protocolo del iterador. Esto implica:
- Implementar el método
__iter__() que devuelve un objeto iterador
- El objeto iterador debe implementar un método
__next__() que devuelve el siguiente valor en la secuencia
Creemos una clase iterable personalizada simple para demostrar esto:
- Cree un nuevo archivo llamado
custom_iterable.py
- Agregue el siguiente código:
## custom_iterable.py
class CountDown:
"""
A custom iterable class that counts down from a specified number to 1.
"""
def __init__(self, start):
"""Initialize with the starting number."""
self.start = start
def __iter__(self):
"""Return an iterator object."""
## This is a simple case where the class is both the iterable and iterator
## In more complex cases, you might return a separate iterator class
self.current = self.start
return self
def __next__(self):
"""Return the next value in the sequence."""
if self.current <= 0:
## Signal the end of iteration
raise StopIteration
## Decrement the counter and return the previous value
self.current -= 1
return self.current + 1
## Test the custom iterable
countdown = CountDown(5)
print("Custom iterable countdown from 5:")
for number in countdown:
print(number, end=" ")
print()
## We can iterate through it again
print("Iterating again:")
for number in countdown:
print(number, end=" ")
print()
## We can also check if it's iterable using our utility
from iteration_utils import is_iterable, get_iterable_info
print("\nChecking if CountDown is iterable:")
print(f"Is CountDown(5) iterable? {is_iterable(countdown)}")
print("Detailed info:", get_iterable_info(countdown))
- Guarde el archivo y ejecútelo:
python3 custom_iterable.py
Debería ver la secuencia de cuenta regresiva de 5 a 1, y luego nuevamente cuando iteramos por segunda vez. Esto demuestra que nuestra clase personalizada es, de hecho, iterable.
Creación de un iterable más complejo: Secuencia de Fibonacci
Creemos un iterable más interesante que genere la secuencia de Fibonacci hasta un límite especificado:
- Cree un nuevo archivo llamado
fibonacci_iterable.py
- Agregue el siguiente código:
## fibonacci_iterable.py
class Fibonacci:
"""An iterable that generates Fibonacci numbers up to a specified limit."""
def __init__(self, limit):
"""
Initialize with a limit (the maximum Fibonacci number to generate).
Args:
limit: The maximum value in the sequence
"""
self.limit = limit
def __iter__(self):
"""Return a fresh iterator."""
return FibonacciIterator(self.limit)
class FibonacciIterator:
"""Iterator for the Fibonacci sequence."""
def __init__(self, limit):
self.limit = limit
self.previous = 0
self.current = 1
def __next__(self):
"""Return the next Fibonacci number."""
## Check if we've reached the limit
if self.previous > self.limit:
raise StopIteration
## Save the current value to return
result = self.previous
## Update for the next iteration
self.previous, self.current = self.current, self.previous + self.current
return result
## Test the Fibonacci iterable
print("Fibonacci sequence up to 100:")
for number in Fibonacci(100):
print(number, end=" ")
print()
## Converting to a list
fib_list = list(Fibonacci(50))
print("\nFibonacci sequence up to 50 as a list:")
print(fib_list)
## Using it in a list comprehension
fib_squared = [x**2 for x in Fibonacci(30)]
print("\nSquared Fibonacci numbers up to 30:")
print(fib_squared)
## Checking iterability
from iteration_utils import is_iterable, get_iterable_info
print("\nChecking if Fibonacci is iterable:")
fib = Fibonacci(100)
print(f"Is Fibonacci(100) iterable? {is_iterable(fib)}")
print("Detailed info:", get_iterable_info(fib))
- Guarde el archivo y ejecútelo:
python3 fibonacci_iterable.py
Este ejemplo demuestra una clase iterable más sofisticada que separa el iterable (la clase Fibonacci) del iterador (la clase FibonacciIterator). Este es un patrón común en iterables más complejos.
Caso de uso práctico: Tubería de procesamiento de datos
Finalmente, creemos una tubería de procesamiento de datos simple utilizando nuestro conocimiento de los iterables:
- Cree un nuevo archivo llamado
data_pipeline.py
- Agregue el siguiente código:
## data_pipeline.py
class DataSource:
"""
A data source that can yield data records.
This simulates reading from a file, database, or API.
"""
def __init__(self, data):
self.data = data
def __iter__(self):
return iter(self.data)
class DataProcessor:
"""
A data processor that transforms data records.
"""
def __init__(self, source, transform_func):
self.source = source
self.transform_func = transform_func
def __iter__(self):
## Iterate through the source and apply the transformation
for item in self.source:
yield self.transform_func(item)
class DataSink:
"""
A data sink that collects processed records.
"""
def __init__(self):
self.collected_data = []
def collect(self, processor):
"""Collect all data from the processor."""
if not isinstance(processor, DataProcessor):
raise TypeError("Expected a DataProcessor")
for item in processor:
self.collected_data.append(item)
return self.collected_data
## Sample data - a list of dictionaries representing people
sample_data = [
{"name": "Alice", "age": 25, "city": "New York"},
{"name": "Bob", "age": 30, "city": "Los Angeles"},
{"name": "Charlie", "age": 35, "city": "Chicago"},
{"name": "Diana", "age": 40, "city": "Houston"},
{"name": "Eve", "age": 45, "city": "Phoenix"}
]
## Create a data source
source = DataSource(sample_data)
## Define a transformation function
def transform_record(record):
## Create a new record with transformed data
return {
"full_name": record["name"].upper(),
"age_in_months": record["age"] * 12,
"location": record["city"]
}
## Create a data processor
processor = DataProcessor(source, transform_record)
## Create a data sink and collect the processed data
sink = DataSink()
processed_data = sink.collect(processor)
## Display the results
print("Original data:")
for record in sample_data:
print(record)
print("\nProcessed data:")
for record in processed_data:
print(record)
- Guarde el archivo y ejecútelo:
python3 data_pipeline.py
Este ejemplo demuestra una aplicación práctica de los iterables en la creación de una tubería de procesamiento de datos. Cada componente de la tubería (fuente, procesador, sumidero) está diseñado para funcionar con los mecanismos de iteración de Python, lo que hace que el código sea limpio y eficiente.