Applications pratiques des itérateurs
Maintenant que vous savez créer et utiliser des itérateurs, explorons quelques applications pratiques du monde réel où les itérateurs peuvent améliorer votre code.
Évaluation paresseuse avec les générateurs
Les générateurs sont un type spécial d'itérateur créés avec des fonctions utilisant l'instruction yield
. Ils vous permettent de générer des valeurs à la volée, ce qui peut être plus économiquement en mémoire que de créer une liste complète.
Créez un fichier nommé generator_example.py
:
def squared_numbers(n):
"""Generate squares of numbers from 1 to n."""
for i in range(1, n + 1):
yield i * i
## Create a generator for squares of numbers 1 to 10
squares = squared_numbers(10)
## squares is a generator object (a type of iterator)
print(f"Type of squares: {type(squares)}")
## Use next() to get values from the generator
print("\nGetting values with next():")
print(next(squares)) ## 1
print(next(squares)) ## 4
print(next(squares)) ## 9
## Use a for loop to get the remaining values
print("\nGetting remaining values with a for loop:")
for square in squares:
print(square)
## The generator is now exhausted, so this won't print anything
print("\nTrying to get more values (generator is exhausted):")
for square in squares:
print(square)
## Create a new generator and convert all values to a list at once
all_squares = list(squared_numbers(10))
print(f"\nAll squares as a list: {all_squares}")
Exécutez le code :
python3 ~/project/generator_example.py
Vous verrez :
Type of squares: <class 'generator'>
Getting values with next():
1
4
9
Getting remaining values with a for loop:
16
25
36
49
64
81
100
Trying to get more values (generator is exhausted):
All squares as a list: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
Les générateurs sont un moyen plus concis de créer des itérateurs pour les cas simples. Ils implémentent automatiquement le protocole d'itération pour vous.
Traitement de grands ensembles de données
Les itérateurs sont parfaits pour traiter de grands ensembles de données car ils vous permettent de travailler avec un élément à la fois. Créons un exemple qui simule le traitement d'un grand ensemble de données de températures :
Créez un fichier nommé data_processing.py
:
import random
import time
def temperature_data_generator(days, start_temp=15.0, max_variation=5.0):
"""Generate simulated hourly temperature data for a number of days."""
hours_per_day = 24
total_hours = days * hours_per_day
current_temp = start_temp
for hour in range(total_hours):
## Simulate temperature variations
day_progress = (hour % hours_per_day) / hours_per_day ## 0.0 to 1.0 through the day
## Temperature is generally cooler at night, warmer during day
time_factor = -max_variation/2 * (
-2 * day_progress + 1 if day_progress < 0.5
else 2 * day_progress - 1
)
## Add some randomness
random_factor = random.uniform(-1.0, 1.0)
current_temp += time_factor + random_factor
current_temp = max(0, min(40, current_temp)) ## Keep between 0-40°C
yield (hour // hours_per_day, hour % hours_per_day, round(current_temp, 1))
def process_temperature_data():
"""Process a large set of temperature data using an iterator."""
print("Processing hourly temperature data for 30 days...")
print("-" * 50)
## Create our data generator
data_iterator = temperature_data_generator(days=30)
## Track some statistics
total_readings = 0
temp_sum = 0
min_temp = float('inf')
max_temp = float('-inf')
## Process the data one reading at a time
start_time = time.time()
for day, hour, temp in data_iterator:
## Update statistics
total_readings += 1
temp_sum += temp
min_temp = min(min_temp, temp)
max_temp = max(max_temp, temp)
## Just for demonstration, print a reading every 24 hours
if hour == 12: ## Noon each day
print(f"Day {day+1}, 12:00 PM: {temp}°C")
processing_time = time.time() - start_time
## Calculate final statistics
avg_temp = temp_sum / total_readings if total_readings > 0 else 0
print("-" * 50)
print(f"Processed {total_readings} temperature readings in {processing_time:.3f} seconds")
print(f"Average temperature: {avg_temp:.1f}°C")
print(f"Temperature range: {min_temp:.1f}°C to {max_temp:.1f}°C")
## Run the temperature data processing
process_temperature_data()
Exécutez le code :
python3 ~/project/data_processing.py
Vous verrez une sortie similaire à (les températures exactes varieront en raison de la génération aléatoire) :
Processing hourly temperature data for 30 days...
--------------------------------------------------
Day 1, 12:00 PM: 17.5°C
Day 2, 12:00 PM: 18.1°C
Day 3, 12:00 PM: 17.3°C
...
Day 30, 12:00 PM: 19.7°C
--------------------------------------------------
Processed 720 temperature readings in 0.012 seconds
Average temperature: 18.2°C
Temperature range: 12.3°C to 24.7°C
Dans cet exemple, nous utilisons un itérateur pour traiter un ensemble de données simulé de 720 mesures de température (24 heures × 30 jours) sans avoir à stocker toutes les données en mémoire à la fois. L'itérateur génère chaque mesure à la demande, rendant le code plus économiquement en mémoire.
Construction d'un pipeline de données avec des itérateurs
Les itérateurs peuvent être enchaînés pour créer des pipelines de traitement de données. Construisons un simple pipeline qui :
- Génère des nombres
- Filtre les nombres impairs
- Eleve au carré les nombres pairs restants
- Limite la sortie à un nombre spécifique de résultats
Créez un fichier nommé data_pipeline.py
:
def generate_numbers(start, end):
"""Generate numbers in the given range."""
print(f"Starting generator from {start} to {end}")
for i in range(start, end + 1):
print(f"Generating: {i}")
yield i
def filter_even(numbers):
"""Filter for even numbers only."""
for num in numbers:
if num % 2 == 0:
print(f"Filtering: {num} is even")
yield num
else:
print(f"Filtering: {num} is odd (skipped)")
def square_numbers(numbers):
"""Square each number."""
for num in numbers:
squared = num ** 2
print(f"Squaring: {num} → {squared}")
yield squared
def limit_results(iterable, max_results):
"""Limit the number of results."""
count = 0
for item in iterable:
if count < max_results:
print(f"Limiting: keeping item #{count+1}")
yield item
count += 1
else:
print(f"Limiting: reached maximum of {max_results} items")
break
## Create our data pipeline
print("Creating data pipeline...\n")
pipeline = (
limit_results(
square_numbers(
filter_even(
generate_numbers(1, 10)
)
),
3 ## Limit to 3 results
)
)
## Execute the pipeline by iterating through it
print("\nExecuting pipeline and collecting results:")
print("-" * 50)
results = list(pipeline)
print("-" * 50)
print(f"\nFinal results: {results}")
Exécutez le code :
python3 ~/project/data_pipeline.py
Vous verrez :
Creating data pipeline...
Executing pipeline and collecting results:
--------------------------------------------------
Starting generator from 1 to 10
Generating: 1
Filtering: 1 is odd (skipped)
Generating: 2
Filtering: 2 is even
Squaring: 2 → 4
Limiting: keeping item #1
Generating: 3
Filtering: 3 is odd (skipped)
Generating: 4
Filtering: 4 is even
Squaring: 4 → 16
Limiting: keeping item #2
Generating: 5
Filtering: 5 is odd (skipped)
Generating: 6
Filtering: 6 is even
Squaring: 6 → 36
Limiting: keeping item #3
Generating: 7
Filtering: 7 is odd (skipped)
Generating: 8
Filtering: 8 is even
Squaring: 8 → 64
Limiting: reached maximum of 3 items
--------------------------------------------------
Final results: [4, 16, 36]
Cet exemple de pipeline montre comment les itérateurs peuvent être connectés pour former un flux de traitement de données. Chaque étape du pipeline traite un élément à la fois, le passant à l'étape suivante. Le pipeline ne traite aucune donnée tant que nous n'utilisons pas réellement les résultats (dans ce cas, en les convertissant en une liste).
Le principal avantage est qu'aucune liste intermédiaire n'est créée entre les étapes du pipeline, ce qui rend cette approche économiquement en mémoire même pour de grands ensembles de données.