Last chance! 50% off DataCamp Premium
Sale ends in
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.
Meestal is een ingebouwde functie of een lus de betere keuze. Toch is reduce() in sommige gevallen een goede optie.
Laten we de werking van reduce() verkennen.
De basisfunctiehandtekening van reduce() is
functools.reduce(function, iterable, [initializer])
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:
Het eindresultaat is 13.
De volgende speelgoedvoorbeelden tonen de werking van reduce().
from functools import reduce
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"
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 import reduce
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, "")
Initializers zaaien ook een resultaat met een lege container. Zo kun je bijvoorbeeld woorden samenvoegen tot een platte lijst met karakters.
from functools import reduce
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']
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 import reduce
import operator as op
numbers = [2, 4, 6]
total = reduce(op.add, numbers, 0) # instead of reduce(lambda x,y: x + y, numbers)
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 import reduce
items = ['the', 'wild', 'wild', 'world', 'is', 'the', 'wide', 'world', 'is', 'the', 'world']
def dedup(acc, x):
if x not in acc: # O(n) membership test
acc.append(x)
return acc
unique = reduce(dedup, items, []) # ['the', 'wild', 'world', 'is', 'wide']
Voor meer ideeën over het verwijderen van duplicaten, bekijk deze tutorial.
reduce() brengt performance-overhead met zich mee.
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.
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:
Ingebouwde functies:
reduce() met zich meebrengt. Dit snelheidsvoordeel stapelt op bij grote invoer. sum, min), dus hun bedoeling is duidelijk. Een aanroep van reduce() dwingt je om de gevouwen functie te ontcijferen.Lussen:
reduce().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 import reduce
list(itertools.accumulate([1, 2, 3, 4], initial=0)) # [0, 1, 3, 6, 10]
reduce(operator.add, [1, 2, 3, 4], 0) # 10
Accumulate is handig wanneer je lopende totalen of minima/maxima nodig hebt. Je wilt bijvoorbeeld maand na maand de maximale temperatuur weten.
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.operator wanneer dat kan. Lambda's, zeker bij niet-associatieve operaties, kunnen de helderheid schaden.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.
min is dat math.inf, en voor set union is dat set(). Zo blijft de code robuust en vrij van TypeErrors.Gezien de nadelen lijkt het misschien dat reduce() geen echte waarde heeft. Integendeel, reduce() heeft veel nuttige use-cases.
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 import reduce
import operator
data = json.loads('''
{
"user": {
"id": "ABC123",
"name": "Alice",
"email": "alice@example.com",
"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) # 34
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.
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 import reduce
import re
# Define preprocessing steps
def strip_punctuation(s):
return re.sub(r"[^\w\s]", "", s)
def to_lower(s):
return s.lower()
def remove_stopwords(s):
stops = {"the", "is", "a", "of"}
return " ".join(word for word in s.split() if word not in stops)
def stem_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 log
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.
def deep_get(data, keys, default=None):
"""Traverse nested dicts/lists safely with reduce."""
try:
return reduce(operator.getitem, keys, data)
except (KeyError, IndexError, TypeError):
return default
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"
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 import reduce
import re
# Define preprocessing steps
def strip_punctuation(s):
return re.sub(r"[^\w\s]", "", s)
def to_lower(s):
return " ".join(map(str.lower, s.split()))
def remove_stopwords(s):
stops = {"the", "is", "a", "of"}
return " ".join(filter(lambda w: w not in stops, s.split()))
def stem_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 log
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.
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 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 import reduce
import numpy as np
def standardize(x):
return (x - x.mean()) / (x.std() + 1e-9)
def clip(x):
return np.clip(x, 0, 1)
def log(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 ndarray
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.
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 import reduce
def add_chars(acc, word):
acc.extend(word) # extend adds each character of the string
return acc
chars = reduce(add_chars, ["hi", "ok"], [])
print(chars) # ['h', 'i', 'o', 'k']
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.
from functools import reduce
from typing import List
def add_chars(acc: List[str], word: str) -> List[str]:
acc.extend(word) # extend adds each character of the string
return acc
chars: List[str] = reduce(add_chars, ["hi", "ok"], [])
print(chars) # ['h', 'i', 'o', 'k']
Nu toont de reducer expliciet
List[str].str.list[str].Met deze hints kunnen statische analyzers de code rigoureus controleren.
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.
Het past herhaaldelijk een twee-argumentenfunctie toe op een iterable om die te reduceren tot één resultaat.
Voor versie 3 was het een ingebouwde functie. Sindsdien staat het in de module functools.
Eenvoudige, associatieve en goed gedocumenteerde functies. Vermijd neveneffecten en niet-associatieve logica.
reduce() geeft alleen het eindresultaat terug, accumulate() levert alle tussenresultaten op.
Gebruik een initializer wanneer de iterable leeg kan zijn.
Topcursussen in Python
Cursus
Leerpad
Cursus

blog
Adel Nehme
15 min