Расширенные темы: Создание пользовательских итерируемых объектов
На этом этапе мы рассмотрим, как создавать собственные итерируемые объекты в Python. Это важный навык, который позволяет вам разрабатывать пользовательские структуры данных, которые беспрепятственно работают с механизмами итерации Python.
Понимание протокола итератора
Чтобы создать пользовательский итерируемый объект, вам необходимо реализовать протокол итератора. Это включает в себя:
- Реализацию метода
__iter__(), который возвращает объект итератора
- Объект итератора должен реализовать метод
__next__(), который возвращает следующее значение в последовательности
Давайте создадим простой пользовательский итерируемый класс, чтобы продемонстрировать это:
- Создайте новый файл с именем
custom_iterable.py
- Добавьте следующий код:
## 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))
- Сохраните файл и запустите его:
python3 custom_iterable.py
Вы должны увидеть последовательность обратного отсчета от 5 до 1, а затем снова, когда мы выполним итерацию во второй раз. Это демонстрирует, что наш пользовательский класс действительно является итерируемым.
Создание более сложного итерируемого объекта: последовательность Фибоначчи
Давайте создадим более интересный итерируемый объект, который генерирует последовательность Фибоначчи до указанного предела:
- Создайте новый файл с именем
fibonacci_iterable.py
- Добавьте следующий код:
## 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))
- Сохраните файл и запустите его:
python3 fibonacci_iterable.py
Этот пример демонстрирует более сложный итерируемый класс, который отделяет итерируемый объект (класс Fibonacci) от итератора (класс FibonacciIterator). Это распространенный шаблон в более сложных итерируемых объектах.
Практический пример использования: конвейер обработки данных
Наконец, давайте создадим простой конвейер обработки данных, используя наши знания об итерируемых объектах:
- Создайте новый файл с именем
data_pipeline.py
- Добавьте следующий код:
## 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)
- Сохраните файл и запустите его:
python3 data_pipeline.py
Этот пример демонстрирует практическое применение итерируемых объектов при создании конвейера обработки данных. Каждый компонент в конвейере (источник, процессор, приемник) предназначен для работы с механизмами итерации Python, что делает код чистым и эффективным.