Effektiver Python-Hinweis Punkt 17 Respektieren Sie die Sicherheit, wenn Sie Iteratoren für Argumente verwenden

Es ist ein Memo-Schreiben des Buches effektive Python von O'Reilly Japan. https://www.oreilly.co.jp/books/9784873117560/ P38~42

Beachten Sie, dass der Iteratoraufruf statusbehaftet ist

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

  1. Zuerst erstellt read_visits () es, was ein Generator ist.
  2. normalize () nimmt den Generator als Argument und sum () wird zuerst berechnet.
  3. ** Zu diesem Zeitpunkt ist der zu zählende Generator bereits erschöpft! ** ** **
  4. Danach wird die for-Schleifenverarbeitung in normalize ausgeführt, aber ** for-Anweisung funktioniert nicht, da die Anzahl der Generatoren bereits abgelaufen ist **
  5. Ergebnis bleibt leer, wie es deklariert wurde

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

Zusammenfassung

Recommended Posts

Effektiver Python-Hinweis Punkt 17 Respektieren Sie die Sicherheit, wenn Sie Iteratoren für Argumente verwenden
Effektiver Python-Hinweis Punkt 12 Vermeiden Sie die Verwendung von else-Blöcken nach for- und while-Schleifen
Effektives Python-Hinweiselement 20 Verwenden Sie None und die Dokumentationszeichenfolge, wenn Sie dynamische Standardargumente angeben
Dinge, auf die Sie achten müssen, wenn Sie Standardargumente in Python verwenden
[Python, Multiprocessing] Verhalten für Ausnahmen bei Verwendung von Multiprocessing
Ein nützlicher Hinweis, wenn Sie Python nach langer Zeit verwenden
Mit dem Evernote SDK für Python 3 erhalten Sie Informationen zu Notizen
Effektives Python-Memo Punkt 3
Effektives Python-Memo-Element 9 Betrachten Sie einen Generatorausdruck für die Notation großer Einschlüsse
Effektives Python-Memo-Element 19 Geben Sie Schlüsselwortargumenten optionales Verhalten
Beachten Sie Links, die bei der Verwendung von Python, Selenium2 hilfreich sein können
Effektiver Python-Hinweis Punkt 16 Erwägen Sie, einen Generator zurückzugeben, ohne eine Liste zurückzugeben
Notizen für 3 Monate, seit ich Python gestartet habe
Schlüsselwortargumente für Python-Funktionen
Effektives Python-Memo Element 11 Verwenden Sie zip, um Iteratoren parallel zu verarbeiten
Effektiver Python-Hinweis Punkt 15 Wissen, wie sich Schließungen auf den Funktionsumfang beziehen
Atom: Hinweis zum Einrückungsfehler beim Kopieren des Python-Skripts in die Shell
Hinweise zur Verwendung der Tab-Vervollständigung beim interaktiven Ausführen von Python unter Windows
Python Hinweis: Über den Vergleich mit is
Vorsichtsmaßnahmen bei der Verwendung von Pit mit Python
boto3 (AWS SDK für Python) Hinweis
[TouchDesigner] Tipps für die Anweisung von Python
[Python] Seien Sie vorsichtig, wenn Sie Druck verwenden
[Python] Grund für das Überschreiben mit super ()
[Python] Neunundneunzig Tabellen, die for-Anweisungen verwenden
Vorsichtsmaßnahmen bei der Verwendung von Phantomjs aus Python
Bei Verwendung von MeCab mit virtualenv python
Vorsichtsmaßnahmen bei Verwendung von sechs mit Python 2.5
Bei Verwendung regulärer Ausdrücke in Python
Hinweise zum Einrichten eines Docker-Containers für die Verwendung von JUMAN ++, KNP, Python