Es ist ein Memo-Schreiben des Buches effektive Python von O'Reilly Japan. https://www.oreilly.co.jp/books/9784873117560/ P38~42
Wenn in einer Funktion, die mehrmals verarbeitet wird, ein Iterator als Argument verwendet wird, kann er sich unerwartet verhalten.
** Betrachten Sie eine Funktion, die das Verhältnis der Besucher in jeder Stadt zur Gesamtzahl der Besucher berechnet. **
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]
Dies ist in Ordnung, aber versuchen Sie, den Generator zu verwenden, falls die Datenmenge groß wird (die Datenmenge, die einen Speicherabsturz verursacht).
def read_visits(data_path):
with open(data_path) as f:
for line in f:
yield int(line)
it = read_visits("visits.txt") #Angenommen, eine Datei mit einer großen Anzahl von Zahlen
percentages = normalize(it)
print(percentages[:3])
>>>
[]
Wir erwarten ähnliche Ergebnisse wie im obigen Code, aber in diesem Fall wird eine leere Liste zurückgegeben. Der Grund ist, dass das Iterator-Ergebnis nur einmal generiert wird.
Wenn Sie den Fluss als Fluss ausdrücken
In diesem Fall ist es besonders verwirrend, dass keine Ausnahmen zurückgegeben werden, wenn der Iterator ausgeht. Es ist ein Mechanismus, bei dem keine Ausnahme zurückgegeben wird, da nicht festgestellt werden kann, ob der iterative Prozess von Python keine Ausgabe des Iterators hat oder bereits angehängt ist und StopIteration ist.
Um dies zu lösen, kopieren Sie den Iterator in die Liste, damit Sie ihn immer wieder aufrufen können.
def normalize_copy(numbers):
numbers = list(numbers) #Erstellen Sie hier eine Liste der Kopien des Iterators
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]
Das Ergebnis ist wie erwartet, aber durch Generieren einer Liste von Zahlen in der Funktion normalize_copy wird ein neuer Speicherbereich verwendet. Dies eliminiert die Vorteile von Iteratoren. Anstatt eine Liste zu erstellen, sollten Sie eine Funktion verwenden, die einen neuen Iterator zurückgibt.
Definieren Sie nun eine Funktion zum Übergeben eines neuen Iterators
def normalize_func(get_iter):
total = sum(get_iter()) #Neuer Iterator
result = []
for value in get_iter(): #Neuer Iterator
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]
Es funktioniert wie erwartet, aber die Verwendung von Lambda ist ein Ärger. Implementieren Sie das ursprüngliche ** Iterator-Protokoll **, um das gleiche Ergebnis zu erzielen.
Das Iteratorprotokoll ist für die Verarbeitung wiederholter Aufrufe im Container in einer Schleife wie einer for-Anweisung verantwortlich. (Rufen Sie next () auf, bis StopIteration auftritt.) Erstellen wir für diesen Prozess eine eigene Containerklasse
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]
Der Unterschied zu den ursprünglichen read_visits besteht darin, dass sie aufgrund der neu implementierten ReadVisit-Containerklasse in der Normalisierungsfunktion zweimal verarbeitet werden. Dies liegt daran, dass jedes neue Iteratorobjekt erstellt wird. Auf diese Weise können Besuche beliebig oft aufgerufen werden. (Dies hat jedoch auch seinen Nachteil: Es beinhaltet das mehrmalige Lesen der Eingabedaten.)
Stellen Sie eine Funktion bereit, mit der überprüft werden kann, ob es sich um einen Containertyp handelt, um eine korrekte Verarbeitung sicherzustellen
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]
Kein Fehler
visits = ReadVisits("visits.txt")
normalize_defensive(visits)
>>>
[11.538461538461538, 26.923076923076923, 61.53846153846154]
Kein Fehler
Ein Fehler tritt auf, wenn die Eingabe kein Containertyp ist
it = iter(visits)
normalize_defensive(it)
>>>
TypeError: Must supply a container
Recommended Posts