Il s'agit d'un mémo écrit du livre efficace python d'O'Reilly Japan. https://www.oreilly.co.jp/books/9784873117560/ P38~42
Dans une fonction qui traite plusieurs fois, lorsqu'un itérateur est utilisé comme argument, il peut se comporter de manière inattendue.
** Considérez une fonction qui calcule le ratio de visiteurs dans chaque ville par rapport au nombre total de visiteurs **
def normalize(numbers):
total = sum(numbers)
result = []
for value in numbers:
persent = 100 * value / total
result.append(persent)
return result
visits = [15, 35, 80]
percentages = normalize(visits)
print(percentages)
>>>
[11.538461538461538, 26.923076923076923, 61.53846153846154]
C'est bien, mais essayez d'utiliser le générateur au cas où la quantité de données deviendrait importante (la quantité de données qui provoque une panne de mémoire).
def read_visits(data_path):
with open(data_path) as f:
for line in f:
yield int(line)
it = read_visits("visits.txt") #En supposant un fichier avec un grand nombre de nombres
percentages = normalize(it)
print(percentages[:3])
>>>
[]
Nous nous attendons à obtenir des résultats similaires au code ci-dessus, mais dans ce cas, une liste vide est renvoyée. La raison en est que le résultat de l'itérateur n'est généré qu'une seule fois.
Si vous exprimez le flux comme un flux
Dans ce cas, ce qui est particulièrement déroutant, c'est qu'aucune exception n'est renvoyée lorsque l'itérateur est épuisé. Dans le traitement itératif python, il n'est pas possible de déterminer s'il n'y a pas de sortie d'itérateur ou s'il est déjà attaché et StopIteration, donc aucune exception n'est retournée.
Pour résoudre ce problème, copiez l'itérateur dans la liste afin de pouvoir l'appeler encore et encore.
def normalize_copy(numbers):
numbers = list(numbers) #Faites une liste de copies de l'itérateur ici
total = sum(numbers)
result = []
for value in numbers:
persent = 100 * value / total
result.append(persent)
return result
it = read_visits("visits.txt")
persentage = normalize_copy(it)
print(persentage)
>>>
[11.538461538461538, 26.923076923076923, 61.53846153846154]
Le résultat est comme prévu, mais en générant une liste de nombres dans la fonction normalize_copy, une nouvelle zone mémoire sera utilisée. Cela élimine les avantages des itérateurs. Au lieu de créer une liste, envisagez d'utiliser une fonction qui renvoie un nouvel itérateur.
Définissez maintenant une fonction pour passer un nouvel itérateur
def normalize_func(get_iter):
total = sum(get_iter()) #Nouvel itérateur
result = []
for value in get_iter(): #Nouvel itérateur
persent = 100 * value / total
result.append(persent)
return result
persentages = normalize_func(lambda: read_visits("visits.txt"))
print(list(persentages))
>>>
[11.538461538461538, 26.923076923076923, 61.53846153846154]
Cela fonctionne comme prévu, mais l'utilisation de lambda est un problème. Mettez en œuvre le ** protocole Iterator ** d'origine pour obtenir le même résultat.
Le protocole itérateur est en charge de traiter les appels répétés dans le conteneur dans une boucle telle qu'une instruction for. (Appelez next () jusqu'à ce que StopIteration se produise) Créons notre propre classe de conteneur pour ce processus
class ReadVisits(object):
def __init__(self, data_path):
self.data_path = data_path
def __iter__(self):
with open(self.data_path) as f:
for line in f:
yield int(line)
visits = ReadVisits("visits.txt")
percentages = normalize(visits)
print(percentages)
>>>
[11.538461538461538, 26.923076923076923, 61.53846153846154]
La différence par rapport aux read_visits d'origine est qu'en raison de la classe de conteneur ReadVisit nouvellement implémentée, elle est traitée deux fois dans la fonction de normalisation. En effet, chaque nouvel objet itérateur est créé. Cela permet aux visites d'être appelées un nombre illimité de fois. (Cependant, cela a aussi son inconvénient: cela implique de lire plusieurs fois les données d'entrée.)
Fournir une fonction pour vérifier s'il s'agit d'un type de conteneur pour assurer un traitement correct
def normalize_defensive(numbers):
if iter(numbers) is iter(numbers):
raise TypeError('Must supply a container')
total = sum(numbers)
result = []
for value in numbers:
percent = 100 * value / total
result.append(percent)
return result
visits = [15, 35, 80]
normalize_defensive(visits)
>>>
[11.538461538461538, 26.923076923076923, 61.53846153846154]
Pas d'erreur
visits = ReadVisits("visits.txt")
normalize_defensive(visits)
>>>
[11.538461538461538, 26.923076923076923, 61.53846153846154]
Pas d'erreur
Une erreur se produit si l'entrée n'est pas de type conteneur
it = iter(visits)
normalize_defensive(it)
>>>
TypeError: Must supply a container
Recommended Posts