Die Python-Funktion „ reduce() ” kommt aus der Welt der funktionalen Programmierung. Funktionale Programmierung (FP) ist ein Programmieransatz, bei dem Programme Ergebnisse durch die Anwendung von Funktionen auf unveränderliche Daten erzeugen.
Ein gängiges Muster in diesem Stil ist die „Faltung“, die eine Sequenz zu einem einzigen Ergebnis zusammenfasst. Zum Beispiel, wenn du die Zahlenliste [2, 4, 5, 3] durch Addition 14 in aufeinanderfolgenden Schritten zusammenfasst: [2, 4, 5, 3] → [6, 5, 3] → [11, 3] → 14.
reduce() verallgemeinert diese Idee. Es wendet eine binäre Operation auf ein iterierbares Objekt an, bis nur noch das Ergebnis übrig ist.
In diesem Artikel schaue ich mir die wichtigsten Teile von Python „ reduce() ” an und gebe ein paar praktische Beispiele. Wenn du eine Auffrischung der Grundlagen von Python brauchst, schau dir doch mal diese Ressourcen an:
Python hat noch mehr funktionale Programmierfunktionen, wie zum Beispiel map() und filter().
Funktionale Konstrukte wie „ map() “ und „ filter() “ waren in Python 1.0 enthalten. Guido van Rossum mochte sie nicht und meinte, dass „ reduce() “ schwer zu analysieren sei und eine Schleife „ for “ fast immer besser zu lesen sei. In Python 3.0 haben die Entwickler nach PEP 3100 die Funktion ` reduce() ` als integrierte Funktion rausgenommen und sie ins Modul ` functools ` verschoben. Durch die Umstellung auf „functools “ wurde es quasi zu einem Nischenwerkzeug.
Warum reduce() verwenden?
Meistens finde ich es wahrscheinlich besser, eine integrierte Funktion oder eine Schleife zu verwenden. In manchen Fällen ist „reduce() “ aber immer noch eine gute Wahl.
Funktionspipelines. Kette eine (möglicherweise dynamische) Reihe von Transformationen auf saubere Weise aneinander.
Algebraische Falten. Verwende das für Operationen mit natürlichen Identitätswerten, wie zum Beispiel die Vereinigungsmenge mit der leeren Menge oder Bitmaskenoperationen mit Null.
Individuelle Falzung ohne Einbau. Definiere deine eigene Zusammenführung auf domänenspezifische Weise, wenn keine integrierte Funktion vorhanden ist.
Strukturierte Akkumulatoren. Lerne mehrere Zustände gleichzeitig in einer benutzerdefinierten Akkumulatorfunktion.
Wie Python reduce() funktioniert
Schauen wir uns mal an, wie „reduce() “ funktioniert.
Die grundlegende Funktionssignatur von „ reduce() “ lautet
Die Funktion reduce() Funktion braucht zwei Argumente und kann noch ein drittes mitbringen.
functionEine binäre Funktion, die sagt, wie man zwei Elemente zusammenfügt.
iterableDie zu reduzierende Sequenz oder Iterable, wie zum Beispiel eine Liste oder ein Tupel. initializer(optional): ein Startwert, um die Funktion zu initialisieren.
Schritt-für-Schritt-Beispiel:
[1, 3, 2, 7] → [4, 2, 7].
[4, 2, 7] → [6, 7].
[6, 7] → [13].
Das Endergebnis ist 13.
Einfache Beispiele für reduce()
Die folgenden einfachen Beispiele zeigen, wie man „ reduce() “ benutzt.
from functools importreduce
numbers =[2,4,6]
product =reduce(lambda x, y: x * y, numbers)# ((2 x 4) x 6) = 8 x 6 = 48
min_value =reduce(lambda x, y: x if x < y else y, numbers)# 2
words =['dog','cat','tree','pony']
str_concat =reduce(lambda x, y: x + y, words)# "dogcattreepony"Bereitgestellt von
Initialisierer
Ohne Initialisierer nimmt „ reduce() “ das erste Element des iterierbaren Objekts als Startwert. Wenn das Iterable leer ist, löst „ reduce() “ einen „ TypeError “ aus. Um den Code stabil zu machen, gib einen Initialisierer an, der das Verhalten bei leeren Eingaben festlegt.
from functools importreduce
words =[]# Error: reduce() of empty iterable with no initial value
str_concat =reduce(lambda x, y: x + y, words)# Correct: use empty string initializer
str_concat =reduce(lambda x, y: x + y, words,"")Bereitgestellt von
Initialisierer füllen ein Ergebnis auch mit einem leeren Container. Du kannst zum Beispiel Wörter zu einer flachen Liste von Zeichen zusammenfügen.
from functools importreduce
words =['reduce','is','fun']
chars_list =reduce(lambda acc, word: acc +list(word), words,[])print(chars_list)# ['r', 'e', 'd', 'u', 'c', 'e', 'i', 's', 'f', 'u', 'n']Bereitgestellt von
Um Python und die Datenbearbeitung weiter zu erkunden, habe ich hier ein paar Optionen, die ich empfehlen kann.
Bisher haben wir Lambda-Funktionen benutzt, um den binären Operator zu definieren.
Du kannst auch Operatoren aus dem Modul „ operator “ verwenden. Das Modul „operator “ ( ) hat Funktionsversionen von gängigen Operatoren und Methodenaufrufen. Anstelle von x + y könnte man zum Beispiel auch operator.add(x, y) schreiben. Damit kannst du vordefinierte (und effiziente) Operatoren an ` reduce(` übergeben, ohne dass du ein Lambda schreiben musst.
from functools importreduceimport operator as op
numbers =[2,4,6]
total =reduce(op.add, numbers,0)# instead of reduce(lambda x,y: x + y, numbers)Bereitgestellt von
Eine dritte Möglichkeit ist, eine eigene Funktion zu schreiben. Das ist eine gute Option, wenn es keinen vordefinierten Operator gibt oder die Funktion für ein Lambda zu kompliziert ist.
Angenommen, du willst Duplikate aus einer Liste entfernen, aber die Reihenfolge der ersten Nennung beibehalten. Du könntest einen Reducer definieren, der ein Element nur dann an eine Liste anhängt, wenn es vorher noch nicht aufgetaucht ist.
from functools importreduce
items =['the','wild','wild','world','is','the','wide','world','is','the','world']defdedup(acc, x):if x notin acc:# O(n) membership test
acc.append(x)return acc
unique =reduce(dedup, items,[])# ['the', 'wild', 'world', 'is', 'wide']Bereitgestellt von
Weitere Ideen zum Entfernen von Duplikaten findest du in diesem Tutorial.
Python- reduce() ruft unsere Funktion einmal für jedes Element auf. Bei einer Liste mit einer Million Elementen bedeutet das eine Million Funktionsaufrufe, von denen jeder einen Frame erstellt, Argumente verarbeitet und Referenzzahlen aktualisiert. Das macht den Aufwand echt groß.
Im Gegensatz dazu macht ein eingebautes Element wie „ sum “ einen einzigen Aufruf in einer C-Funktion und führt die Millionen Additionen innerhalb der C-Schleife durch. Dieser Unterschied kann die integrierten Befehle um ein Vielfaches schneller machen.
Probleme mit Cache-Lokalität und CPU-Effizienz
Reduce hat auch Probleme mit Cache-Lokalität und CPU-Effizienz.
Eine moderne CPU kann mehrere Milliarden Befehle pro Sekunde ausführen, aber der Zugriff auf den RAM ist um ein Vielfaches langsamer. Um das auszugleichen, haben moderne CPUsCaches mit mehreren Ebenen(, L1, L2, L3), die Daten für einen schnellen Zugriff speichern.
Diese Caches nutzen zwei Muster aus.
Zeitliche Lokalität Daten, die kürzlich benutzt wurden, werden wahrscheinlich wieder benutzt werden.
Raumbezogene Nähe Daten in der Nähe einer kürzlich verwendeten Adresse werden wahrscheinlich bald wieder gebraucht.
Im Gegensatz dazu muss man bei jedem Schritt von „ reduce() “ nach dem nächsten Element suchen und Python-Funktionen aufrufen, was die Lokalität stört und die CPU ausbremst. Eingebaute und vektorisierte Funktionen umgehen dieses Problem, indem sie über enge C-Schleifen laufen.
Wenn du eine Auffrischung zum Schreiben von idiomatischem und effizientem Python-Code brauchst, schau dir Folgendes an:
Optimierter C-Code. Built-ins laufen in optimiertem C-Code, nicht in Python. Dadurch wird der Aufwand vermieden, der bei „ reduce() “ anfällt. Dieser Geschwindigkeitsvorteil macht sich bei großen Eingaben besonders bemerkbar.
Lesbarkeit. Eingebaute Funktionen haben beschreibende Namen (sum, min), sodass ihr Zweck klar ist. Ein Aufruf von „reduce() “ lässt dich die Funktion analysieren, die gefaltet wird.
Loops:
Leistung. Schleifen laufen normalerweise langsamer als integrierte Funktionen, aber schneller als „reduce() “.
Lesbarkeit. Genau wie Built-ins sind Schleifen meistens besser zu lesen als „ reduce() “. Ein Aufruf von „reduce() “ zwingt dich dazu, eine funktionale Anweisung zu analysieren, während eine Schleife eher Python-typisch ist.
itertools.accumulate()
Die Python-Bibliothek „ itertools ” hat eine Reihe von leistungsstarken Iteratoren wie „ count() ”, „ product() ” und „ combinations() ”. Eine nützliche Funktion von „ itertools “ ist „ itertools.accumulate() “. Genau wie „ reduce() “ wendet es eine Funktion auf ein iterierbares Objekt an. accumulate() speichert aber nicht nur das Endergebnis, sondern auch Zwischenwerte der Berechnung.
Zum Beispiel:
import itertools, operator
from functools importreducelist(itertools.accumulate([1,2,3,4], initial=0))# [0, 1, 3, 6, 10]reduce(operator.add,[1,2,3,4],0)# 10Bereitgestellt von
Die Funktion „Akkumulieren“ ist praktisch, wenn du laufende Summen oder Minimal-/Maximalwerte brauchst. Du möchtest vielleicht wissen, wie hoch die höchste Temperatur im Monat war.
Häufige Probleme bei der Verwendung von reduce()
Wenn du reduce() benutzt, denk an die folgenden Fallstricke.
Nimm lieber einfachere Alternativen wie Einbauten oder Schleifen. Heb dir „reduce() “ für den Fall auf, dass du es wirklich brauchst.
Leere iterierbare Objekte verarbeiten. Benutze immer einen passenden Initialisierer, um Fehler bei leeren Eingaben zu vermeiden.
Pass auf Speicherprobleme auf. Versuche nicht, „reduce() ” in Situationen zu zwängen, in denen ein Generator oder ein Streaming-Ansatz effizienter wäre.
Vermeide knifflige Lambdas. Benutz die Funktionen aus dem Modul „ operator “, wenn du kannst. Lambdas können, vor allem bei nicht-assoziativen Operationen, die Übersichtlichkeit beeinträchtigen.
Sei lieber klar als clever.
Python reduce() – Best Practices und Richtlinien
Wie bei jedem Tool gibt es auch bei „ reduce() “ bewährte Vorgehensweisen. Wenn du dich fürreduce() entschieden hast, findest du hier ein paar Tipps zur Nutzung.
Entwerfe zuerst den Reduzierer.
Beschreib den Vertrag in einem Satz. „Füge Wörterbuchschlüssel zusammen, indem du die Anzahl pro Schlüssel addierst.“
Halt es möglichst assoziativ. So kannst du leichter parallelisieren und testen.
Finde dein Identitätselement und benutze es als Initialisierer. Zum Beispiel ist die Identität für die Summe 0. Für min ist es math.inf und für set union ist es set(). So bleibt der Code stabil und frei von „TypeErrors “.
Halte es einfach und verständlich
Eine klare Sache. Keine Nebenwirkungen.
Gib dem Reduzierer einen aussagekräftigen Namen.
Dokument
Schreib im Docstring das Identitätselement und das Verhalten bei leeren Eingaben, die Assoziativitätsannahme und die Fehlerbehandlung auf.
Test
Unit-Tests für Randfälle: leere Iterable, gemischte Typen, Extremwerte.
Testet die Assoziativität.
Leistung überwachen
Vergleich kleine und große Eingaben. Vergleich Benchmarks mit integrierten Funktionen und Schleifen.
Wenn es auf Geschwindigkeit ankommt, solltest du Vorverarbeitung, Batch-Verarbeitung und die Verlagerung rechenintensiver Aufgaben auf NumPy oder pandas in Betracht ziehen.
Fortgeschrittene Anwendungen und praktische Anwendungsfälle für Python reduce()
Angesichts der Nachteile könnte man meinen, dass „ reduce() “ keinen echten Wert hat. Im Gegenteil, „reduce() “ hat viele coole Anwendungsfälle.
Verarbeitung verschachtelter Strukturen
Datenbankähnliche Vorgänge
Datenverarbeitungs-Pipelines
MapReduce-Anwendungen
Verarbeitung verschachtelter Strukturen
Reduce bietet eine saubere Möglichkeit, verschachtelte Datenstrukturen wie JSON-Objekte zu durchlaufen, indem es eine Folge von Schlüsseln zu aufeinanderfolgenden Suchvorgängen zusammenfasst.
import json
from functools importreduceimport operator
data = json.loads('''
{
"user": {
"id": "ABC123",
"name": "Alice",
"email": "[email protected]",
"profile": {
"address": {
"city": "San Francisco",
"zip": "94103"
},
"age": 34,
"skills": ["Python", "Data Science", "Machine Learning"]
}
}
}
''')# Example lookups with reduce + operator.getitem
city =reduce(operator.getitem,["user","profile","address","city"], data)print(city)# "San Francisco"
user_id =reduce(operator.getitem,["user","id"], data)print(user_id)# "ABC123"
age =reduce(operator.getitem,["user","profile","age"], data)print(age)# 34Bereitgestellt von
Hier macht es Sinn, „ reduce() “ zu verwenden. Das JSON ist ziemlich verschachtelt: user → profile → address → city. Anstatt Lookups manuell zu verketten, stell den Pfad als Liste von Schlüsseln dar. Dann benutz „ reduce(operator.getitem, path, data) “, um es zu durchlaufen. Dadurch bleibt der Code allgemein, verständlich und wiederverwendbar.
Datenverarbeitungs-Pipelines
Reduce kann Datenverarbeitungs-Pipelines steuern, indem es Daten durch eine Reihe von Transformationen schickt. Jede Funktion macht einen Schritt, und die Pipeline kommt, wenn man sie der Reihe nach anwendet. Hier ist eine Spielzeug-Pipeline, die eine Textzeichenfolge vorverarbeitet, bevor sie in ein NLP-Modell eingespeist wird.
from functools importreduceimport re
# Define preprocessing stepsdefstrip_punctuation(s):return re.sub(r"[^\w\s]","", s)defto_lower(s):return s.lower()defremove_stopwords(s):
stops ={"the","is","a","of"}return" ".join(word for word in s.split()if word notin stops)defstem_words(s):# trivial "stemmer": cut off 'ing'return" ".join(word[:-3]if word.endswith("ing")else word for word in s.split())
pipeline =[
strip_punctuation,
to_lower,
remove_stopwords,
stem_words,]# Input data
text ="The quick brown fox is Jumping over a log."# Apply pipeline with reduce
processed =reduce(lambda acc, f: f(acc), pipeline, text)print(processed)# quick brown fox jump over logBereitgestellt von
Fehlerbehandlung in komplizierten Anwendungen
Komm, wir schauen uns nochmal das Beispiel mit dem verschachtelten JSON an. Im Moment löst der direkte Aufruf von reduce(operator.getitem, …) einen Fehler vom Typ „ KeyError “ oder „ TypeError “ aus, wenn ein Schlüssel fehlt oder wenn ein Nicht-Dict gefunden wird. Um den Code sicherer zu machen, definier eine Hilfsfunktion, die operator.getitem in einen try/except-Block einbindet und bei einem Fehler einen Standardwert zurückgibt.
Hier ist eine Idee für die Hilfsfunktion.
defdeep_get(data, keys, default=None):"""Traverse nested dicts/lists safely with reduce."""try:returnreduce(operator.getitem, keys, data)except(KeyError, IndexError, TypeError):return defaultBereitgestellt von
Jetzt änderst du die Beispiel-Lookups, damit sie unsere neue Funktion anstelle einer unwrapped verwenden. reduce()
# Example lookups
city = deep_get(data,["user","profile","address","city"], default="Unknown City")print(city)# "San Francisco"
user_id = deep_get(data,["user","id"], default="N/A")print(user_id)# "ABC123"
age = deep_get(data,["user","profile","age"], default="N/A")print(age)# 34# Example with missing key
phone = deep_get(data,["user","profile","phone"], default="No phone")print(phone)# "No phone"Bereitgestellt von
Mehrstufige Datenumwandlungen mit map() und filter()
Du kannst „ reduce() “ mit anderen praktischen Tools wie „ map() “ und „ filter() “ kombinieren, um mehrstufige Datenumwandlungen zu erstellen. Hier ist unsere frühere NLP-Vorverarbeitungs-Pipeline, die funktional geschrieben wurde.
from functools importreduceimport re
# Define preprocessing stepsdefstrip_punctuation(s):return re.sub(r"[^\w\s]","", s)defto_lower(s):return" ".join(map(str.lower, s.split()))defremove_stopwords(s):
stops ={"the","is","a","of"}return" ".join(filter(lambda w: w notin stops, s.split()))defstem_words(s):# trivial "stemmer": cut off 'ing'return" ".join(map(lambda w: w[:-3]if w.endswith("ing")else w, s.split()))# Pipeline of transformations
pipeline =[
strip_punctuation,
to_lower,
remove_stopwords,
stem_words,]# Input data
text ="The quick brown fox is Jumping over a log."# Apply pipeline with reduce
processed =reduce(lambda acc, f: f(acc), pipeline, text)print(processed)# quick brown fox jump over logBereitgestellt von
Die Transformationen sind:
Entferne die Satzzeichen. „Der schnelle braune Fuchs springt über einen Baumstamm.“ → „Der schnelle braune Fuchs springt über einen Baumstamm.“
Kleinbuchstaben
„Der schnelle braune Fuchs springt über einen Baumstamm“ → „Der schnelle braune Fuchs springt über einen Baumstamm“
Stoppwörter rausnehmen. „Der schnelle braune Fuchs springt über einen Baumstamm“ → „schneller brauner Fuchs springt über Baumstamm“
Stammwörter
„Schneller brauner Fuchs springt über Baumstamm“ → „Schneller brauner Fuchs springt über Baumstamm“
Um mehr über funktionale Programmierung und Vektorisierung zu erfahren, check mal diese DataCamp-Artikel aus.
Um „ reduce() “ gut zu nutzen, ist es hilfreich zu verstehen, wie es in das moderne Python-Ökosystem passt. Schauen wir mal genauer, wie es mit NumPy und pandas zusammenpasst, wie es parallele und verteilte Systeme unterstützt und wie es mit modernen Tools wie statischen Analysatoren zusammenarbeitet.
numpy und pandas
NumPy und pandas laufen mit optimiertem C-Code, also versuch nicht, ihre Funktionen mit reduce() zu duplizieren. reduce() ist aber eine gute Wahl für Pipelines mit dynamischen Schritten. Du kannst zum Beispiel mehrere NumPy-Transformationen auf ein Array anwenden.
from functools importreduceimport numpy as np
defstandardize(x):return(x - x.mean())/(x.std()+1e-9)defclip(x):return np.clip(x,0,1)deflog(x):return np.log1p(x)
x = np.array([1,500,40.5,100,250.45])
funcs =[standardize, clip, log]
y =reduce(lambda a, f: f(a), funcs, x)# x is ndarrayBereitgestellt von
Parallele und verteilte Rechenumgebungen
Reduzieren ist echt wichtig bei parallelen und verteilten Rechenumgebungen. Diese Systeme teilen einen Datensatz in Teile auf, bearbeiten diese Teile gleichzeitig und setzen dann die Teilergebnisse zu einer Antwort zusammen. Der Schritt „kombinieren“ ist die Reduktion.
Damit die Reduzierung in einem Cluster richtig funktioniert, musst du sicherstellen, dass die folgenden Bedingungen erfüllt sind.
Assoziativität. Die Operation muss unabhängig von der Gruppierung immer das gleiche Ergebnis liefern. Dadurch können Teilergebnisse in beliebiger Reihenfolge im Netzwerk zusammengeführt werden.
Identitätselement. Die Operation muss einen Initialisierer haben, der das Endergebnis nicht beeinflusst. Das sorgt dafür, dass alles richtig läuft, wenn Partitionen leer sind oder unterschiedliche Größen haben.
Wenn diese Eigenschaften nicht zutreffen, werden Reduktionen langsam (weil sie nicht sicher parallelisiert werden können) oder fehlerhaft (weil die Ergebnisse von der Auswertungsreihenfolge abhängen).
Dieser Prozess braucht eine assoziative Operation und eine klare Identität. Sonst kriegst du langsamen Code oder falsche Ergebnisse.
Statische Analyse-Tools
Ein statischer Analysator (wie mypy, Pyright, ruff oder bandit) ist ein Tool, das Code checkt, ohne ihn auszuführen. Diese Tools finden Fehler, sorgen dafür, dass Stilregeln eingehalten werden, und checken, ob alles richtig geschrieben ist.
Statische Analysatoren haben Probleme mit „ reduce() “. Beim Faltvorgang kann der Akkumulatortyp vom Elementtyp abweichen, und die Typinferenz wird kompliziert.
Schau dir mal diesen Code an.
from functools importreducedefadd_chars(acc, word):
acc.extend(word)# extend adds each character of the stringreturn acc
chars =reduce(add_chars,["hi","ok"],[])print(chars)# ['h', 'i', 'o', 'k']Bereitgestellt von
Auch wenn dieser Code gut läuft, könnte ein statischer Analysator ein paar Probleme melden.
Der Elementtyp des Initialisierers [] ist nicht eindeutig.
Analysatoren könnten davon ausgehen, dass die Akkumulator- und Elementtypen übereinstimmen.
Um den Code für Leute und Analysatoren verständlicher zu machen, füge Typ-Hinweise hinzu.
from functools importreducefrom typing import List
defadd_chars(acc: List[str], word:str)-> List[str]:
acc.extend(word)# extend adds each character of the stringreturn acc
chars: List[str]=reduce(add_chars,["hi","ok"],[])print(chars)# ['h', 'i', 'o', 'k']Bereitgestellt von
Jetzt zeigt der Reduzierer ganz klar
Der Akkumulator ist ein List[str].
Jedes Element ist ein „str “.
Der Rückgabetyp ist ein „list[str] “.
Mit diesen Tipps können statische Analysatoren den Code gründlich checken.
Fazit
Reduce kommt aus der funktionalen Programmierung, wo das Zusammenfassen von Sammlungen zu einem einzigen Ergebnis eine Kernidee ist. Auch wenn es in Python nicht mehr das beste Tool ist, hat es trotzdem seinen Platz. Wenn du flexible Pipelines, benutzerdefinierte Falzungen oder Vorgänge brauchst, die sich nicht sauber auf vorhandene Funktionen abbilden lassen, ist „ reduce() “ ein echt starkes Tool. Wenn man es richtig einsetzt, passt es super ins Python-Ökosystem und ist in den richtigen Situationen echt praktisch.
Ein paar nützliche Links zu Python, die du vielleicht hilfreich findest.
Lerne mehr über Python-Lambda-Funktionen, wozu sie gut sind und wann man sie benutzt. Enthält praktische Beispiele und bewährte Methoden für eine effektive Umsetzung.
Lerne, wie du Python-Listen mit Techniken wie Slicing, List Comprehensions und itertools aufteilen kannst. Finde heraus, wann du welche Methode für die beste Datenverarbeitung nutzen solltest.
Lerne die Funktionen und Methoden von Python-Listen kennen. Schau dir jetzt die Code-Beispiele für list() und andere Python-Funktionen und -Methoden an!
Erweitere deine Datenkenntnisse, entdecke, wie du Daten manipulieren und visualisieren kannst, und wende fortgeschrittene Analysen an, um datengestützte Entscheidungen zu treffen.
Die Python-Funktion „
reduce()” kommt aus der Welt der funktionalen Programmierung. Funktionale Programmierung (FP) ist ein Programmieransatz, bei dem Programme Ergebnisse durch die Anwendung von Funktionen auf unveränderliche Daten erzeugen.Ein gängiges Muster in diesem Stil ist die „Faltung“, die eine Sequenz zu einem einzigen Ergebnis zusammenfasst. Zum Beispiel, wenn du die Zahlenliste [2, 4, 5, 3] durch Addition 14 in aufeinanderfolgenden Schritten zusammenfasst: [2, 4, 5, 3] → [6, 5, 3] → [11, 3] → 14.
reduce()verallgemeinert diese Idee. Es wendet eine binäre Operation auf ein iterierbares Objekt an, bis nur noch das Ergebnis übrig ist.In diesem Artikel schaue ich mir die wichtigsten Teile von Python „
reduce()” an und gebe ein paar praktische Beispiele. Wenn du eine Auffrischung der Grundlagen von Python brauchst, schau dir doch mal diese Ressourcen an:Geschichte
Python hat noch mehr funktionale Programmierfunktionen, wie zum Beispiel
map()undfilter().Funktionale Konstrukte wie „
map()“ und „filter()“ waren in Python 1.0 enthalten. Guido van Rossum mochte sie nicht und meinte, dass „reduce()“ schwer zu analysieren sei und eine Schleife „for“ fast immer besser zu lesen sei. In Python 3.0 haben die Entwickler nach PEP 3100 die Funktion `reduce()` als integrierte Funktion rausgenommen und sie ins Modul `functools` verschoben. Durch die Umstellung auf „functools“ wurde es quasi zu einem Nischenwerkzeug.Warum reduce() verwenden?
Meistens finde ich es wahrscheinlich besser, eine integrierte Funktion oder eine Schleife zu verwenden. In manchen Fällen ist „
reduce()“ aber immer noch eine gute Wahl.Wie Python reduce() funktioniert
Schauen wir uns mal an, wie „
reduce()“ funktioniert.Die grundlegende Funktionssignatur von „
reduce()“ lautetDie Funktion
reduce()Funktion braucht zwei Argumente und kann noch ein drittes mitbringen.functionEine binäre Funktion, die sagt, wie man zwei Elemente zusammenfügt.iterableDie zu reduzierende Sequenz oder Iterable, wie zum Beispiel eine Liste oder ein Tupel.initializer(optional): ein Startwert, um die Funktion zu initialisieren.Schritt-für-Schritt-Beispiel:
Das Endergebnis ist 13.
Einfache Beispiele für reduce()
Die folgenden einfachen Beispiele zeigen, wie man „
reduce()“ benutzt.Initialisierer
Ohne Initialisierer nimmt „
reduce()“ das erste Element des iterierbaren Objekts als Startwert. Wenn das Iterable leer ist, löst „reduce()“ einen „TypeError“ aus. Um den Code stabil zu machen, gib einen Initialisierer an, der das Verhalten bei leeren Eingaben festlegt.Initialisierer füllen ein Ergebnis auch mit einem leeren Container. Du kannst zum Beispiel Wörter zu einer flachen Liste von Zeichen zusammenfügen.
Um Python und die Datenbearbeitung weiter zu erkunden, habe ich hier ein paar Optionen, die ich empfehlen kann.
Den Reducer definieren
Bisher haben wir Lambda-Funktionen benutzt, um den binären Operator zu definieren.
Du kannst auch Operatoren aus dem Modul „
operator“ verwenden. Das Modul „operator“ ( ) hat Funktionsversionen von gängigen Operatoren und Methodenaufrufen. Anstelle von x + y könnte man zum Beispiel auchoperator.add(x, y)schreiben. Damit kannst du vordefinierte (und effiziente) Operatoren an `reduce(` übergeben, ohne dass du ein Lambda schreiben musst.Eine dritte Möglichkeit ist, eine eigene Funktion zu schreiben. Das ist eine gute Option, wenn es keinen vordefinierten Operator gibt oder die Funktion für ein Lambda zu kompliziert ist.
Angenommen, du willst Duplikate aus einer Liste entfernen, aber die Reihenfolge der ersten Nennung beibehalten. Du könntest einen Reducer definieren, der ein Element nur dann an eine Liste anhängt, wenn es vorher noch nicht aufgetaucht ist.
Weitere Ideen zum Entfernen von Duplikaten findest du in diesem Tutorial.
Leistung von Python reduce()
reduce()hat mit Leistungsproblemen zu tun.Funktionsaufruf-Overhead
Python-
reduce()ruft unsere Funktion einmal für jedes Element auf. Bei einer Liste mit einer Million Elementen bedeutet das eine Million Funktionsaufrufe, von denen jeder einen Frame erstellt, Argumente verarbeitet und Referenzzahlen aktualisiert. Das macht den Aufwand echt groß.Im Gegensatz dazu macht ein eingebautes Element wie „
sum“ einen einzigen Aufruf in einer C-Funktion und führt die Millionen Additionen innerhalb der C-Schleife durch. Dieser Unterschied kann die integrierten Befehle um ein Vielfaches schneller machen.Probleme mit Cache-Lokalität und CPU-Effizienz
Reduce hat auch Probleme mit Cache-Lokalität und CPU-Effizienz.
Eine moderne CPU kann mehrere Milliarden Befehle pro Sekunde ausführen, aber der Zugriff auf den RAM ist um ein Vielfaches langsamer. Um das auszugleichen, haben moderne CPUsCaches mit mehreren Ebenen( , L1, L2, L3), die Daten für einen schnellen Zugriff speichern.
Diese Caches nutzen zwei Muster aus.
Im Gegensatz dazu muss man bei jedem Schritt von „
reduce()“ nach dem nächsten Element suchen und Python-Funktionen aufrufen, was die Lokalität stört und die CPU ausbremst. Eingebaute und vektorisierte Funktionen umgehen dieses Problem, indem sie über enge C-Schleifen laufen.Wenn du eine Auffrischung zum Schreiben von idiomatischem und effizientem Python-Code brauchst, schau dir Folgendes an:
Alternativen zu Python reduce()
Einbauten:
reduce()“ anfällt. Dieser Geschwindigkeitsvorteil macht sich bei großen Eingaben besonders bemerkbar.sum,min), sodass ihr Zweck klar ist. Ein Aufruf von „reduce()“ lässt dich die Funktion analysieren, die gefaltet wird.Loops:
reduce()“.reduce()“. Ein Aufruf von „reduce()“ zwingt dich dazu, eine funktionale Anweisung zu analysieren, während eine Schleife eher Python-typisch ist.itertools.accumulate()Die Python-Bibliothek „
itertools” hat eine Reihe von leistungsstarken Iteratoren wie „count()”, „product()” und „combinations()”. Eine nützliche Funktion von „itertools“ ist „itertools.accumulate()“. Genau wie „reduce()“ wendet es eine Funktion auf ein iterierbares Objekt an.accumulate() speichert aber nicht nur das Endergebnis, sondern auch Zwischenwerte der Berechnung.Zum Beispiel:
Die Funktion „Akkumulieren“ ist praktisch, wenn du laufende Summen oder Minimal-/Maximalwerte brauchst. Du möchtest vielleicht wissen, wie hoch die höchste Temperatur im Monat war.
Häufige Probleme bei der Verwendung von reduce()
Wenn du
reduce()benutzt, denk an die folgenden Fallstricke.reduce()“ für den Fall auf, dass du es wirklich brauchst.reduce()” in Situationen zu zwängen, in denen ein Generator oder ein Streaming-Ansatz effizienter wäre.operator“, wenn du kannst. Lambdas können, vor allem bei nicht-assoziativen Operationen, die Übersichtlichkeit beeinträchtigen.Python reduce() – Best Practices und Richtlinien
Wie bei jedem Tool gibt es auch bei „
reduce()“ bewährte Vorgehensweisen. Wenn du dich fürreduce()entschieden hast, findest du hier ein paar Tipps zur Nutzung.Entwerfe zuerst den Reduzierer.
minist esmath.infund fürset unionist esset(). So bleibt der Code stabil und frei von „TypeErrors“.Halte es einfach und verständlich
Dokument
Test
Leistung überwachen
Fortgeschrittene Anwendungen und praktische Anwendungsfälle für Python reduce()
Angesichts der Nachteile könnte man meinen, dass „
reduce()“ keinen echten Wert hat. Im Gegenteil, „reduce()“ hat viele coole Anwendungsfälle.Verarbeitung verschachtelter Strukturen
Reduce bietet eine saubere Möglichkeit, verschachtelte Datenstrukturen wie JSON-Objekte zu durchlaufen, indem es eine Folge von Schlüsseln zu aufeinanderfolgenden Suchvorgängen zusammenfasst.
Hier macht es Sinn, „
reduce()“ zu verwenden. Das JSON ist ziemlich verschachtelt:user → profile → address → city. Anstatt Lookups manuell zu verketten, stell den Pfad als Liste von Schlüsseln dar. Dann benutz „reduce(operator.getitem, path, data)“, um es zu durchlaufen. Dadurch bleibt der Code allgemein, verständlich und wiederverwendbar.Datenverarbeitungs-Pipelines
Reduce kann Datenverarbeitungs-Pipelines steuern, indem es Daten durch eine Reihe von Transformationen schickt. Jede Funktion macht einen Schritt, und die Pipeline kommt, wenn man sie der Reihe nach anwendet. Hier ist eine Spielzeug-Pipeline, die eine Textzeichenfolge vorverarbeitet, bevor sie in ein NLP-Modell eingespeist wird.
Fehlerbehandlung in komplizierten Anwendungen
Komm, wir schauen uns nochmal das Beispiel mit dem verschachtelten JSON an. Im Moment löst der direkte Aufruf von
reduce(operator.getitem, …)einen Fehler vom Typ „KeyError“ oder „TypeError“ aus, wenn ein Schlüssel fehlt oder wenn ein Nicht-Dict gefunden wird. Um den Code sicherer zu machen, definier eine Hilfsfunktion, dieoperator.getitemin einen try/except-Block einbindet und bei einem Fehler einen Standardwert zurückgibt.Hier ist eine Idee für die Hilfsfunktion.
Jetzt änderst du die Beispiel-Lookups, damit sie unsere neue Funktion anstelle einer unwrapped verwenden.
reduce()Mehrstufige Datenumwandlungen mit map() und filter()
Du kannst „
reduce()“ mit anderen praktischen Tools wie „map()“ und „filter()“ kombinieren, um mehrstufige Datenumwandlungen zu erstellen. Hier ist unsere frühere NLP-Vorverarbeitungs-Pipeline, die funktional geschrieben wurde.Die Transformationen sind:
„Der schnelle braune Fuchs springt über einen Baumstamm“ → „Der schnelle braune Fuchs springt über einen Baumstamm“
„Schneller brauner Fuchs springt über Baumstamm“ → „Schneller brauner Fuchs springt über Baumstamm“
Um mehr über funktionale Programmierung und Vektorisierung zu erfahren, check mal diese DataCamp-Artikel aus.
Integration in das moderne Python-Ökosystem
Um „
reduce()“ gut zu nutzen, ist es hilfreich zu verstehen, wie es in das moderne Python-Ökosystem passt. Schauen wir mal genauer, wie es mit NumPy und pandas zusammenpasst, wie es parallele und verteilte Systeme unterstützt und wie es mit modernen Tools wie statischen Analysatoren zusammenarbeitet.numpy und pandas
NumPy und pandas laufen mit optimiertem C-Code, also versuch nicht, ihre Funktionen mit
reduce()zu duplizieren.reduce()ist aber eine gute Wahl für Pipelines mit dynamischen Schritten. Du kannst zum Beispiel mehrere NumPy-Transformationen auf ein Array anwenden.Parallele und verteilte Rechenumgebungen
Reduzieren ist echt wichtig bei parallelen und verteilten Rechenumgebungen. Diese Systeme teilen einen Datensatz in Teile auf, bearbeiten diese Teile gleichzeitig und setzen dann die Teilergebnisse zu einer Antwort zusammen. Der Schritt „kombinieren“ ist die Reduktion.
Damit die Reduzierung in einem Cluster richtig funktioniert, musst du sicherstellen, dass die folgenden Bedingungen erfüllt sind.
Wenn diese Eigenschaften nicht zutreffen, werden Reduktionen langsam (weil sie nicht sicher parallelisiert werden können) oder fehlerhaft (weil die Ergebnisse von der Auswertungsreihenfolge abhängen).
Dieser Prozess braucht eine assoziative Operation und eine klare Identität. Sonst kriegst du langsamen Code oder falsche Ergebnisse.
Statische Analyse-Tools
Ein statischer Analysator (wie mypy, Pyright, ruff oder bandit) ist ein Tool, das Code checkt, ohne ihn auszuführen. Diese Tools finden Fehler, sorgen dafür, dass Stilregeln eingehalten werden, und checken, ob alles richtig geschrieben ist.
Statische Analysatoren haben Probleme mit „
reduce()“. Beim Faltvorgang kann der Akkumulatortyp vom Elementtyp abweichen, und die Typinferenz wird kompliziert.Schau dir mal diesen Code an.
Auch wenn dieser Code gut läuft, könnte ein statischer Analysator ein paar Probleme melden.
Um den Code für Leute und Analysatoren verständlicher zu machen, füge Typ-Hinweise hinzu.
Jetzt zeigt der Reduzierer ganz klar
List[str].str“.list[str]“.Mit diesen Tipps können statische Analysatoren den Code gründlich checken.
Fazit
Reduce kommt aus der funktionalen Programmierung, wo das Zusammenfassen von Sammlungen zu einem einzigen Ergebnis eine Kernidee ist. Auch wenn es in Python nicht mehr das beste Tool ist, hat es trotzdem seinen Platz. Wenn du flexible Pipelines, benutzerdefinierte Falzungen oder Vorgänge brauchst, die sich nicht sauber auf vorhandene Funktionen abbilden lassen, ist „
reduce()“ ein echt starkes Tool. Wenn man es richtig einsetzt, passt es super ins Python-Ökosystem und ist in den richtigen Situationen echt praktisch.Ein paar nützliche Links zu Python, die du vielleicht hilfreich findest.