Python's reduce()-functie komt uit de wereld van functioneel programmeren. Functioneel programmeren (FP) is een programmeerparadigma waarin programma's resultaten opbouwen door functies toe te passen op onveranderlijke data.
Een veelvoorkomend patroon in deze stijl is de "fold", die een reeks samenklapt tot één resultaat. Bijvoorbeeld, het samenklappen van de lijst met getallen [2, 4, 5, 3] onder optelling levert 14 op via opeenvolgende stappen: [2, 4, 5, 3] → [6, 5, 3] → [11, 3] → 14.
reduce() generaliseert dit idee. Het past een binaire operatie toe over een iterable totdat alleen het resultaat overblijft.
In dit artikel bespreek ik de kernelementen van Python's reduce() en geef ik enkele praktische voorbeelden. Heb je een opfrisser nodig van de basis van Python, bekijk dan deze bronnen:
Python bevat ook andere functies voor functioneel programmeren, zoals map() en filter().
Functionele constructies zoals map() en filter() zaten al in Python 1.0. Guido van Rossum was er geen fan van; hij wees erop dat reduce() lastig te doorgronden was en dat een for-lus bijna altijd beter leesbaar is. In Python 3.0, conform PEP 3100, verwijderden ontwikkelaars reduce() als ingebouwde functie en verplaatsten het naar de module functools. Deze verhuizing naar functools degradeerde het feitelijk tot een nichetool.
Waarom reduce() gebruiken?
Meestal is een ingebouwde functie of een lus de betere keuze. Toch is reduce() in sommige gevallen een goede optie.
Functionele pijplijnen. Koppel een (eventueel dynamische) reeks transformaties op een nette manier aan elkaar.
Algebraïsche folds. Gebruik voor operaties met natuurlijke identiteitswaarden, zoals vereniging van verzamelingen met de lege verzameling of bitmask-bewerkingen met nul.
Aangepaste fold zonder ingebouwde functie. Definieer je eigen merge op domeinspecifieke wijze wanneer er geen ingebouwde bestaat.
Gestructureerde accumulators. Houd meerdere stukjes toestand tegelijk bij binnen één aangepaste accumulatorfunctie.
De reduce()-functie neemt twee verplichte argumenten en een optioneel derde argument.
function: een binaire functie die specificeert hoe twee elementen gecombineerd worden,
iterable: de sequentie of iterable om te reduceren, zoals een lijst of tuple, initializer (optioneel): een startwaarde om de functie mee te zaaien.
Stap-voor-stap voorbeeld:
[1, 3, 2, 7] → [4, 2, 7].
[4, 2, 7] → [6, 7].
[6, 7] → [13].
Het eindresultaat is 13.
Eenvoudige reduce()-voorbeelden
De volgende speelgoedvoorbeelden tonen de werking van reduce().
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"Mogelijk gemaakt door
Initializer
Zonder initializer neemt reduce() het eerste element van de iterable als startwaarde. Als de iterable leeg is, geeft reduce() een TypeError. Om code robuust te maken, geef je een initializer op om het gedrag bij lege invoer te definiëren.
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,"")Mogelijk gemaakt door
Initializers zaaien ook een resultaat met een lege container. Zo kun je bijvoorbeeld woorden samenvoegen tot een platte lijst met karakters.
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']Mogelijk gemaakt door
Wil je verder aan de slag met Python en datamanipulatie, dan raad ik deze opties aan.
Tot nu toe hebben we lambda-functies gebruikt om de binaire operator te definiëren.
Je kunt ook operatoren uit de module operator gebruiken. De operator-module bevat functievarianten van gangbare operatoren en method calls. In plaats van x + y gebruik je bijvoorbeeld operator.add(x, y). Zo kun je vooraf gedefinieerde (en efficiënte) operatoren doorgeven aan reduce() zonder een lambda te hoeven schrijven.
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)Mogelijk gemaakt door
Een derde optie is een eigen functie schrijven. Dit is handig wanneer er geen vooraf gedefinieerde operator is of wanneer de functie te ingewikkeld is voor een lambda.
Stel bijvoorbeeld dat je duplicaten uit een lijst wilt verwijderen, maar de volgorde van eerste voorkomen wilt behouden. Je zou een reducer kunnen definiëren die een item alleen aan een lijst toevoegt als het nog niet eerder is voorgekomen.
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']Mogelijk gemaakt door
Voor meer ideeën over het verwijderen van duplicaten, bekijk deze tutorial.
reduce() brengt performance-overhead met zich mee.
Overhead van functiekoppelingen
Python reduce() roept onze functie één keer per element aan. Bij een lijst met een miljoen items betekent dit een miljoen functieaanroepen, die elk een frame creëren, argumenten afhandelen en referentietellingen bijwerken. Dit zorgt voor aanzienlijke overhead.
Ter vergelijking: een ingebouwde functie zoals sum doet één enkele aanroep naar een C-functie en voert de miljoen optellingen uit binnen de C-lus. Dit verschil kan de ingebouwde functie orders van grootte sneller maken.
Cache-locality en CPU-efficiëntie
Reduce lijdt ook onder slechte cache-locality en CPU-efficiëntie.
Een moderne CPU kan enkele miljarden instructies per seconde uitvoeren, maar RAM-toegang is vele ordes van grootte trager. Ter compensatie hebben moderne CPU's caches (L1, L2, L3) die data opslaan voor snelle toegang.
Deze caches benutten twee patronen.
Tijdelijke locality: data die recent gebruikt is, zal waarschijnlijk opnieuw gebruikt worden.
Ruimtelijke locality: data nabij een recent gebruikt adres zal waarschijnlijk binnenkort gebruikt worden.
Daarentegen omvat elke stap van reduce() pointer-chasing om het volgende element te vinden en Python-functieaanroepen, wat de locality doorbreekt en de CPU doet stagneren. Ingebouwde en gevectoriseerde functies vermijden dit probleem door in strakke C-lussen te draaien.
Voor een opfrisser over het schrijven van idiomatische en efficiënte Python-code, bekijk:
Geoptimaliseerde C-code. Ingebouwde functies draaien hun lussen in geoptimaliseerde C-code, niet in Python. Dat voorkomt de overhead die reduce() met zich meebrengt. Dit snelheidsvoordeel stapelt op bij grote invoer.
Leesbaarheid. Ingebouwde functies hebben beschrijvende namen (sum, min), dus hun bedoeling is duidelijk. Een aanroep van reduce() dwingt je om de gevouwen functie te ontcijferen.
Lussen:
Performance. Lussen draaien over het algemeen trager dan ingebouwde functies, maar sneller dan reduce().
Leesbaarheid. Net als ingebouwde functies zijn lussen meestal beter leesbaar dan reduce(). Een aanroep van reduce() dwingt je tot het parsen van een functionele uitspraak, terwijl een lus meer Pythonic is.
itertools.accumulate()
Python's itertools-bibliotheek biedt een verzameling performante iterators zoals count(), product() en combinations(). Een nuttige itertools-functie is itertools.accumulate(). Net als reduce() vouwt het een functie over een iterable. accumulate() bewaart echter de tussenresultaten van de berekening, niet alleen het eindresultaat.
Bijvoorbeeld,
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)# 10Mogelijk gemaakt door
Accumulate is handig wanneer je lopende totalen of minima/maxima nodig hebt. Je wilt bijvoorbeeld maand na maand de maximale temperatuur weten.
Veelvoorkomende valkuilen bij het gebruik van reduce()
Wanneer je reduce() gebruikt, houd dan de volgende valkuilen in gedachten.
Geef de voorkeur aan eenvoudigere alternatieven zoals ingebouwde functies of lussen. Bewaar reduce() voor wanneer je het echt nodig hebt.
Ga om met lege iterables. Gebruik altijd een geschikte initializer om een fout bij lege invoer te vermijden.
Let op geheugenproblemen. Dwing reduce() niet in situaties waar een generator- of streamingaanpak efficiënter zou zijn.
Vermijd lastige lambda's. Gebruik functies uit de module operator wanneer dat kan. Lambda's, zeker bij niet-associatieve operaties, kunnen de helderheid schaden.
Kies duidelijkheid boven slimheid.
Best practices en richtlijnen voor Python reduce()
Zoals met elke tool zijn er best practices voor reduce(). Als je hebt besloten dat reduce() de juiste tool is, houd dan deze richtlijnen aan.
Ontwerp eerst de reducer
Definieer het contract in één zin. "Combineer dictionary-keys door tellingen per key op te tellen."
Houd het indien mogelijk associatief. Dit maakt paralleliseren en testen eenvoudiger.
Identificeer je identiteitselement en gebruik dit als initializer. Voor som is de identiteit bijvoorbeeld 0. Voor min is dat math.inf, en voor set union is dat set(). Zo blijft de code robuust en vrij van TypeErrors.
Houd het simpel en leesbaar
Eén duidelijke bewerking. Geen neveneffecten.
Geef de reducer een beschrijvende naam.
Documenteer
Leg in de docstring vast: identiteitselement en gedrag bij lege invoer, associativiteitsaanname, foutafhandeling.
Test
Unittests op randgevallen: lege iterable, gemengde types, extreme waarden.
Test associativiteit.
Monitor performance
Benchmark kleine en grote invoer. Vergelijk met ingebouwde functies en lussen.
Als snelheid telt, overweeg pre-processing, batchen en zware berekeningen verplaatsen naar NumPy of pandas.
Geavanceerde toepassingen en realistische use-cases voor Python reduce()
Gezien de nadelen lijkt het misschien dat reduce() geen echte waarde heeft. Integendeel, reduce() heeft veel nuttige use-cases.
Verwerken van geneste structuren
Database-achtige operaties
Dataverwerkingspijplijnen
MapReduce-toepassingen
Verwerken van geneste structuren
Reduce biedt een nette manier om door geneste datastructuren, zoals JSON-objecten, te traverseren door een reeks keys te vouwen tot opeenvolgende lookups.
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)# 34Mogelijk gemaakt door
Hier is reduce() logisch. De JSON is diep genest: user → profile → address → city. In plaats van lookups handmatig te koppelen, representeer je het pad als een lijst met keys. Gebruik vervolgens reduce(operator.getitem, path, data) om het te traverseren. Dit houdt de code generiek, leesbaar en herbruikbaar.
Dataverwerkingspijplijnen
Reduce kan dataverwerkingspijplijnen aansturen door data door een reeks transformaties te leiden. Elke functie handelt een enkele stap af en de pijplijn ontstaat door ze op volgorde toe te passen. Hier is een eenvoudig voorbeeld dat een tekststring voorbewerkt voordat die aan een NLP-model wordt gevoerd.
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 logMogelijk gemaakt door
Foutafhandeling in complexe toepassingen
Laten we teruggaan naar het geneste JSON-voorbeeld. Nu gooit de directe aanroep van reduce(operator.getitem, …) een KeyError of TypeError als een key ontbreekt of als er een niet-dict wordt aangetroffen. Om de code veiliger te maken, definieer je een helperfunctie die operator.getitem omhult met een try/except-blok en een standaardwaarde teruggeeft wanneer er een fout optreedt.
Hier is een mogelijke helperfunctie.
defdeep_get(data, keys, default=None):"""Traverse nested dicts/lists safely with reduce."""try:returnreduce(operator.getitem, keys, data)except(KeyError, IndexError, TypeError):return defaultMogelijk gemaakt door
Pas nu de voorbeeld-lookups aan zodat ze onze nieuwe functie gebruiken in plaats van een onomhulde 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"Mogelijk gemaakt door
Meerstapsdatatransformaties met map() en filter()
Je kunt reduce() combineren met andere functionele tools, zoals map() en filter(), om meerstapsdatatransformaties te bouwen. Hier is onze eerdere NLP-voorbewerking functioneel geschreven.
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 logMogelijk gemaakt door
De transformaties zijn:
Verwijder leestekens. "The quick brown fox is Jumping over a log." → "The quick brown fox is Jumping over a log"
Kleine letters
"The quick brown fox is Jumping over a log" → "the quick brown fox is jumping over a log"
Verwijder stopwoorden. "the quick brown fox is jumping over a log" → "quick brown fox jumping over log"
Stem woorden
"quick brown fox jumping log" → "quick brown fox jump log"
Wil je functioneel programmeren en vectorisatie verder verkennen, bekijk dan deze DataCamp-artikelen.
Om reduce() goed te gebruiken helpt het om te begrijpen hoe het past in het moderne Python-ecosysteem. Laten we bekijken hoe het zich verhoudt tot NumPy en pandas, hoe het ten grondslag ligt aan parallelle en gedistribueerde systemen, en hoe het samenwerkt met moderne tooling zoals statische analyzers.
numpy en pandas
NumPy en pandas draaien op geoptimaliseerde C-code, dus dupliceer hun functionaliteit niet met reduce(). Wel is reduce() een goede keuze voor pijplijnen met dynamische stappen. Je kunt bijvoorbeeld veel NumPy-transformaties op een array componeren.
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 ndarrayMogelijk gemaakt door
Parallelle en gedistribueerde computing-frameworks
Reduce is centraal in parallelle en gedistribueerde computing-frameworks. Deze systemen werken door een dataset op te splitsen in partities, die partities parallel te verwerken en vervolgens de partiële resultaten te combineren tot één antwoord. De "combine"-stap is de reductie.
Opdat reductie correct werkt over een cluster, moeten de volgende voorwaarden gelden.
Associativiteit. De operatie moet hetzelfde resultaat geven ongeacht de groepering. Dit maakt het mogelijk partiële resultaten in elke volgorde samen te voegen over het netwerk.
Identiteitselement. De operatie moet een initializer hebben die het eindresultaat niet beïnvloedt. Dit waarborgt correctheid wanneer partities leeg zijn of ongelijk van grootte.
Als deze eigenschappen niet gelden, worden reducties traag (omdat ze niet veilig te paralleliseren zijn) of onjuist (omdat resultaten afhangen van de evaluatievolgorde).
Dit proces heeft dus een associatieve operatie en een duidelijk identiteitselement nodig. Anders krijg je trage code of onjuiste resultaten.
Statische analysetools
Een statische analyzer (zoals mypy, Pyright, ruff of bandit) is een tool die code inspecteert zonder deze uit te voeren. Deze tools vangen bugs, handhaven stijlafspraken en controleren typecorrectheid.
Statische analyzers hebben moeite met reduce(). In het vouwproces kan het accumulatortype verschillen van het elementtype, en type-inferentie wordt rommelig.
Beschouw deze code.
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']Mogelijk gemaakt door
Hoewel deze code prima draait, kan een statische analyzer over een paar dingen klagen.
Het elementtype van de initializer [] is ambigu.
Analyzers gaan er mogelijk vanuit dat accumulator- en elementtypen overeenkomen.
Maak de code duidelijker voor mensen en analyzers door typehints toe te voegen.
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']Mogelijk gemaakt door
Nu toont de reducer expliciet
De accumulator is een List[str].
Elk element is een str.
Het geretourneerde type is een list[str].
Met deze hints kunnen statische analyzers de code rigoureus controleren.
Conclusie
Reduce komt uit het functioneel programmeren, waar het vouwen van collecties tot één resultaat een kernidee is. Hoewel het geen eersteklas hulpmiddel meer is in Python, heeft het zijn plaats. Wanneer je flexibele pijplijnen, aangepaste folds of operaties nodig hebt die niet netjes op bestaande functies passen, is reduce() een krachtig hulpmiddel. Zorgvuldig gebruikt integreert het netjes met het bredere Python-ecosysteem en blijft het een praktische tool in de juiste situaties.
Enkele gerelateerde Python-links die je wellicht handig vindt.
Verbeter je datavaardigheden, leer hoe je data kunt bewerken en visualiseren, en gebruik geavanceerde analyses om beslissingen te nemen op basis van data.
Python's
reduce()-functie komt uit de wereld van functioneel programmeren. Functioneel programmeren (FP) is een programmeerparadigma waarin programma's resultaten opbouwen door functies toe te passen op onveranderlijke data.Een veelvoorkomend patroon in deze stijl is de "fold", die een reeks samenklapt tot één resultaat. Bijvoorbeeld, het samenklappen van de lijst met getallen [2, 4, 5, 3] onder optelling levert 14 op via opeenvolgende stappen: [2, 4, 5, 3] → [6, 5, 3] → [11, 3] → 14.
reduce()generaliseert dit idee. Het past een binaire operatie toe over een iterable totdat alleen het resultaat overblijft.In dit artikel bespreek ik de kernelementen van Python's
reduce()en geef ik enkele praktische voorbeelden. Heb je een opfrisser nodig van de basis van Python, bekijk dan deze bronnen:Geschiedenis
Python bevat ook andere functies voor functioneel programmeren, zoals
map()enfilter().Functionele constructies zoals
map()enfilter()zaten al in Python 1.0. Guido van Rossum was er geen fan van; hij wees erop datreduce()lastig te doorgronden was en dat eenfor-lus bijna altijd beter leesbaar is. In Python 3.0, conform PEP 3100, verwijderden ontwikkelaarsreduce()als ingebouwde functie en verplaatsten het naar de modulefunctools. Deze verhuizing naarfunctoolsdegradeerde het feitelijk tot een nichetool.Waarom reduce() gebruiken?
Meestal is een ingebouwde functie of een lus de betere keuze. Toch is
reduce()in sommige gevallen een goede optie.Hoe Python reduce() werkt
Laten we de werking van
reduce()verkennen.De basisfunctiehandtekening van
reduce()isDe
reduce()-functie neemt twee verplichte argumenten en een optioneel derde argument.function: een binaire functie die specificeert hoe twee elementen gecombineerd worden,iterable: de sequentie of iterable om te reduceren, zoals een lijst of tuple,initializer(optioneel): een startwaarde om de functie mee te zaaien.Stap-voor-stap voorbeeld:
Het eindresultaat is 13.
Eenvoudige reduce()-voorbeelden
De volgende speelgoedvoorbeelden tonen de werking van
reduce().Initializer
Zonder initializer neemt
reduce()het eerste element van de iterable als startwaarde. Als de iterable leeg is, geeftreduce()eenTypeError. Om code robuust te maken, geef je een initializer op om het gedrag bij lege invoer te definiëren.Initializers zaaien ook een resultaat met een lege container. Zo kun je bijvoorbeeld woorden samenvoegen tot een platte lijst met karakters.
Wil je verder aan de slag met Python en datamanipulatie, dan raad ik deze opties aan.
De reducer definiëren
Tot nu toe hebben we lambda-functies gebruikt om de binaire operator te definiëren.
Je kunt ook operatoren uit de module
operatorgebruiken. Deoperator-module bevat functievarianten van gangbare operatoren en method calls. In plaats van x + y gebruik je bijvoorbeeldoperator.add(x, y). Zo kun je vooraf gedefinieerde (en efficiënte) operatoren doorgeven aanreduce() zonder een lambda te hoeven schrijven.Een derde optie is een eigen functie schrijven. Dit is handig wanneer er geen vooraf gedefinieerde operator is of wanneer de functie te ingewikkeld is voor een lambda.
Stel bijvoorbeeld dat je duplicaten uit een lijst wilt verwijderen, maar de volgorde van eerste voorkomen wilt behouden. Je zou een reducer kunnen definiëren die een item alleen aan een lijst toevoegt als het nog niet eerder is voorgekomen.
Voor meer ideeën over het verwijderen van duplicaten, bekijk deze tutorial.
Performance van Python reduce()
reduce()brengt performance-overhead met zich mee.Overhead van functiekoppelingen
Python
reduce()roept onze functie één keer per element aan. Bij een lijst met een miljoen items betekent dit een miljoen functieaanroepen, die elk een frame creëren, argumenten afhandelen en referentietellingen bijwerken. Dit zorgt voor aanzienlijke overhead.Ter vergelijking: een ingebouwde functie zoals
sumdoet één enkele aanroep naar een C-functie en voert de miljoen optellingen uit binnen de C-lus. Dit verschil kan de ingebouwde functie orders van grootte sneller maken.Cache-locality en CPU-efficiëntie
Reduce lijdt ook onder slechte cache-locality en CPU-efficiëntie.
Een moderne CPU kan enkele miljarden instructies per seconde uitvoeren, maar RAM-toegang is vele ordes van grootte trager. Ter compensatie hebben moderne CPU's caches (L1, L2, L3) die data opslaan voor snelle toegang.
Deze caches benutten twee patronen.
Daarentegen omvat elke stap van
reduce()pointer-chasing om het volgende element te vinden en Python-functieaanroepen, wat de locality doorbreekt en de CPU doet stagneren. Ingebouwde en gevectoriseerde functies vermijden dit probleem door in strakke C-lussen te draaien.Voor een opfrisser over het schrijven van idiomatische en efficiënte Python-code, bekijk:
Alternatieven voor Python reduce()
Ingebouwde functies:
reduce()met zich meebrengt. Dit snelheidsvoordeel stapelt op bij grote invoer.sum,min), dus hun bedoeling is duidelijk. Een aanroep vanreduce()dwingt je om de gevouwen functie te ontcijferen.Lussen:
reduce().reduce(). Een aanroep vanreduce()dwingt je tot het parsen van een functionele uitspraak, terwijl een lus meer Pythonic is.itertools.accumulate()Python's
itertools-bibliotheek biedt een verzameling performante iterators zoalscount(),product()encombinations(). Een nuttigeitertools-functie isitertools.accumulate(). Net alsreduce()vouwt het een functie over een iterable.accumulate() bewaart echter de tussenresultaten van de berekening, niet alleen het eindresultaat.Bijvoorbeeld,
Accumulate is handig wanneer je lopende totalen of minima/maxima nodig hebt. Je wilt bijvoorbeeld maand na maand de maximale temperatuur weten.
Veelvoorkomende valkuilen bij het gebruik van reduce()
Wanneer je
reduce()gebruikt, houd dan de volgende valkuilen in gedachten.reduce()voor wanneer je het echt nodig hebt.reduce()niet in situaties waar een generator- of streamingaanpak efficiënter zou zijn.operatorwanneer dat kan. Lambda's, zeker bij niet-associatieve operaties, kunnen de helderheid schaden.Best practices en richtlijnen voor Python reduce()
Zoals met elke tool zijn er best practices voor
reduce(). Als je hebt besloten datreduce()de juiste tool is, houd dan deze richtlijnen aan.Ontwerp eerst de reducer
minis datmath.inf, en voorset unionis datset(). Zo blijft de code robuust en vrij vanTypeErrors.Houd het simpel en leesbaar
Documenteer
Test
Monitor performance
Geavanceerde toepassingen en realistische use-cases voor Python reduce()
Gezien de nadelen lijkt het misschien dat
reduce()geen echte waarde heeft. Integendeel,reduce()heeft veel nuttige use-cases.Verwerken van geneste structuren
Reduce biedt een nette manier om door geneste datastructuren, zoals JSON-objecten, te traverseren door een reeks keys te vouwen tot opeenvolgende lookups.
Hier is
reduce()logisch. De JSON is diep genest:user → profile → address → city. In plaats van lookups handmatig te koppelen, representeer je het pad als een lijst met keys. Gebruik vervolgensreduce(operator.getitem, path, data)om het te traverseren. Dit houdt de code generiek, leesbaar en herbruikbaar.Dataverwerkingspijplijnen
Reduce kan dataverwerkingspijplijnen aansturen door data door een reeks transformaties te leiden. Elke functie handelt een enkele stap af en de pijplijn ontstaat door ze op volgorde toe te passen. Hier is een eenvoudig voorbeeld dat een tekststring voorbewerkt voordat die aan een NLP-model wordt gevoerd.
Foutafhandeling in complexe toepassingen
Laten we teruggaan naar het geneste JSON-voorbeeld. Nu gooit de directe aanroep van
reduce(operator.getitem, …)eenKeyErrorofTypeErrorals een key ontbreekt of als er een niet-dict wordt aangetroffen. Om de code veiliger te maken, definieer je een helperfunctie dieoperator.getitemomhult met een try/except-blok en een standaardwaarde teruggeeft wanneer er een fout optreedt.Hier is een mogelijke helperfunctie.
Pas nu de voorbeeld-lookups aan zodat ze onze nieuwe functie gebruiken in plaats van een onomhulde
reduce()Meerstapsdatatransformaties met map() en filter()
Je kunt
reduce()combineren met andere functionele tools, zoalsmap()enfilter(), om meerstapsdatatransformaties te bouwen. Hier is onze eerdere NLP-voorbewerking functioneel geschreven.De transformaties zijn:
"The quick brown fox is Jumping over a log" → "the quick brown fox is jumping over a log"
"quick brown fox jumping log" → "quick brown fox jump log"
Wil je functioneel programmeren en vectorisatie verder verkennen, bekijk dan deze DataCamp-artikelen.
Integratie met het moderne Python-ecosysteem
Om
reduce()goed te gebruiken helpt het om te begrijpen hoe het past in het moderne Python-ecosysteem. Laten we bekijken hoe het zich verhoudt tot NumPy en pandas, hoe het ten grondslag ligt aan parallelle en gedistribueerde systemen, en hoe het samenwerkt met moderne tooling zoals statische analyzers.numpy en pandas
NumPy en pandas draaien op geoptimaliseerde C-code, dus dupliceer hun functionaliteit niet met
reduce(). Wel isreduce()een goede keuze voor pijplijnen met dynamische stappen. Je kunt bijvoorbeeld veel NumPy-transformaties op een array componeren.Parallelle en gedistribueerde computing-frameworks
Reduce is centraal in parallelle en gedistribueerde computing-frameworks. Deze systemen werken door een dataset op te splitsen in partities, die partities parallel te verwerken en vervolgens de partiële resultaten te combineren tot één antwoord. De "combine"-stap is de reductie.
Opdat reductie correct werkt over een cluster, moeten de volgende voorwaarden gelden.
Als deze eigenschappen niet gelden, worden reducties traag (omdat ze niet veilig te paralleliseren zijn) of onjuist (omdat resultaten afhangen van de evaluatievolgorde).
Dit proces heeft dus een associatieve operatie en een duidelijk identiteitselement nodig. Anders krijg je trage code of onjuiste resultaten.
Statische analysetools
Een statische analyzer (zoals mypy, Pyright, ruff of bandit) is een tool die code inspecteert zonder deze uit te voeren. Deze tools vangen bugs, handhaven stijlafspraken en controleren typecorrectheid.
Statische analyzers hebben moeite met
reduce(). In het vouwproces kan het accumulatortype verschillen van het elementtype, en type-inferentie wordt rommelig.Beschouw deze code.
Hoewel deze code prima draait, kan een statische analyzer over een paar dingen klagen.
Maak de code duidelijker voor mensen en analyzers door typehints toe te voegen.
Nu toont de reducer expliciet
List[str].str.list[str].Met deze hints kunnen statische analyzers de code rigoureus controleren.
Conclusie
Reduce komt uit het functioneel programmeren, waar het vouwen van collecties tot één resultaat een kernidee is. Hoewel het geen eersteklas hulpmiddel meer is in Python, heeft het zijn plaats. Wanneer je flexibele pijplijnen, aangepaste folds of operaties nodig hebt die niet netjes op bestaande functies passen, is
reduce()een krachtig hulpmiddel. Zorgvuldig gebruikt integreert het netjes met het bredere Python-ecosysteem en blijft het een praktische tool in de juiste situaties.Enkele gerelateerde Python-links die je wellicht handig vindt.