Kurs
Python'daki reduce() fonksiyonu, fonksiyonel programlama dünyasından gelir. Fonksiyonel programlama (FP), programların değiştirilemez verilere fonksiyonlar uygulayarak sonuçlar ürettiği bir programlama paradigmasıdır.
Bu tarzda yaygın bir örüntü, bir diziyi tek bir sonuca indirgeyen “fold” işlemidir. Örneğin, [2, 4, 5, 3] sayı listesini toplama altında katlayarak ardışık adımlarla 14’e indirgeriz: [2, 4, 5, 3] → [6, 5, 3] → [11, 3] → 14.
reduce() bu fikri genelleştirir. İkili bir işlemi, yalnızca sonuç kalana kadar bir yinelenebilir üzerinde uygular.
Bu yazıda Python'un reduce() fonksiyonunun temel unsurlarını inceleyecek ve bazı pratik örnekler vereceğim. Python temellerine hızlı bir göz atmaya ihtiyacınız varsa, şu kaynaklara bakmanızı öneririm:
Tarihçe
Python, map() ve filter() gibi diğer fonksiyonel programlama işlevlerini de içerir.
map() ve filter() gibi fonksiyonel yapılar Python 1.0’da vardı. Guido van Rossum bunlardan hoşlanmıyordu; reduce()’ın okunmasının zor olduğunu ve for döngüsünün neredeyse her zaman daha okunaklı olduğunu belirtiyordu. Python 3.0’da, PEP 3100’ün ardından, geliştiriciler reduce()’ı yerleşik olmaktan çıkarıp functools modülüne taşıdılar. functools’a taşınması onu esasen niş bir araç statüsüne indirdi.
Neden reduce() Kullanılır?
Çoğu zaman yerleşik bir fonksiyon ya da döngü kullanmanın daha iyi bir seçenek olduğunu düşünüyorum. Yine de, bazı kullanım durumlarında reduce() iyi bir tercihtir.
- Fonksiyon boru hatları. (Gerekirse dinamik) bir dizi dönüşümü temiz bir şekilde zincirleyin.
- Cebirsel fold’lar. Boş küme ile küme birleşimi veya sıfırla bitmask işlemleri gibi doğal özdeşlik değerlerine sahip işlemler için kullanın.
- Yerleşik olmayan özel fold. Yerleşik bir fonksiyon yoksa, alanınıza özgü birleştirme mantığınızı tanımlayın.
- Yapılandırılmış biriktiriciler. Tek bir özel biriktirici fonksiyon içinde aynı anda birden fazla durumu takip edin.
Python reduce() Nasıl Çalışır
Şimdi reduce()’ın işleyişini inceleyelim.
reduce() fonksiyonunun temel imzası
functools.reduce(function, iterable, [initializer])
reduce() iki zorunlu, bir de isteğe bağlı üçüncü argüman alır.
function: iki öğenin nasıl birleştirileceğini belirleyen ikili bir fonksiyon,iterable: bir liste veya demet gibi indirgenecek dizi ya da yinelenebilir,initializer(isteğe bağlı): fonksiyonu başlatacak bir başlangıç değeri.
Adım adım örnek:
- [1, 3, 2, 7] → [4, 2, 7].
- [4, 2, 7] → [6, 7].
- [6, 7] → [13].
Nihai sonuç 13’tür.
Basit reduce() örnekleri
Aşağıdaki küçük örnekler, reduce()’un nasıl kullanılacağına dair işleyişi gösterir.
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"
Başlatıcı (initializer)
Bir başlatıcı olmadan, reduce() başlangıç değeri olarak yinelenebilirin ilk öğesini alır. Yinelenebilir boşsa, reduce() bir TypeError fırlatır. Kodu sağlam kılmak için, boş girdideki davranışı tanımlamak üzere bir başlatıcı sağlayın.
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, "")
Başlatıcılar ayrıca sonucu boş bir kapla tohumlar. Örneğin, sözcükleri düz bir karakter listesine birleştirebilirsiniz.
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']
Python ve veri işleme konularını daha fazla keşfetmek için önerilerim:
- Python'da Veri İçe Aktarmaya Giriş kursu
- Python ile Veri Manipülasyonu yetkinlik yolu
- Python'da pandas ile Veriyi Yeniden Şekillendirme cheat sheet
- Python'da Boyut İndirgeme kursu
- Veri Önişleme: Python Örnekleriyle Kapsamlı Rehber blog yazısı
Azaltıcıyı (reducer) tanımlama
Şimdiye kadar ikili işleci tanımlamak için lambda fonksiyonları kullandık.
operator modülündeki operatörleri de kullanabilirsiniz. operator modülü, yaygın operatörlerin ve metot çağrılarının fonksiyon sürümlerini içerir. Örneğin, x + y yerine operator.add(x, y) kullanabilirsiniz. Bu sayede bir lambda yazmadan reduce() içine önceden tanımlı (ve verimli) operatörleri geçebilirsiniz.
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)
Üçüncü seçenek özel bir fonksiyon yazmaktır. Önceden tanımlı bir operatör olmadığında veya fonksiyon bir lambda için fazla karmaşık olduğunda iyi bir seçenektir.
Örneğin, bir listedeki yinelenenleri kaldırmak ama ilk görüldükleri sırayı korumak istiyorsunuz. Daha önce görünmemişse bir öğeyi listeye ekleyen bir azaltıcı tanımlayabilirsiniz.
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']
Yinelenenleri kaldırmaya dair başka fikirler için şu eğitime göz atın.
Python reduce() Performansı
reduce() performansla ilgili sorunlar barındırır.
Fonksiyon çağrısı ek yükü
Python reduce() fonksiyonumuzu listedeki her öğe için bir kez çağırır. Bir milyon öğeli bir listede bu, her biri bir frame oluşturan, argümanları işleyen ve referans sayaçlarını güncelleyen bir milyon fonksiyon çağrısı demektir. Bu ciddi bir ek yük oluşturur.
Buna karşılık, sum gibi bir yerleşik fonksiyon, C fonksiyonuna tek bir çağrı yapar ve milyonlarca toplamayı C döngüsü içinde gerçekleştirir. Bu fark, yerleşik fonksiyonu katbekat daha hızlı kılabilir.
Önbellek yerelliği ve CPU verimliliği sorunları
Reduce, önbellek yerelliği ve CPU verimliliği açısından da sıkıntılar yaşar.
Modern bir CPU saniyede birkaç milyar komut çalıştırabilir, ancak RAM erişimi katbekat daha yavaştır. Bunu telafi etmek için modern CPU’larda hızlı erişim için veriyi saklayan önbellekler (L1, L2, L3) bulunur.
Bu önbellekler iki örüntüden yararlanır.
- Zamansal yerellik: Yakın zamanda kullanılan veri muhtemelen tekrar kullanılacaktır.
- Mekânsal yerellik: Yakın zamanda kullanılan bir adrese yakın veri yakında kullanılma olasılığı yüksektir.
Buna karşılık, reduce()’ın her adımı bir sonrakini bulmak için gösterge (pointer) zincirlemesi ve Python fonksiyon çağrıları içerir; bu da yerelliği bozar ve CPU’yu duraksatır. Yerleşik ve vektörleştirilmiş fonksiyonlar bu sorunu sıkı C döngülerinde çalışarak aşar.
İdiyomatik ve verimli Python kodu yazmaya dair hatırlatmalar için şunlara göz atın:
- İdiyomatik Pandas Kodu Yazmak İçin 5 İpucu eğitimi
- Verimli Python Kodu Yazma kursu
- pandas ile Verimli Kod Yazma kursu
Python reduce() Alternatifleri
Yerleşik fonksiyonlar:
- Optimize C Kodu. Yerleşik fonksiyonlar döngülerini Python’da değil, optimize C kodunda çalıştırır. Bu,
reduce()’ın getirdiği ek yükten kaçınır. Bu hız avantajı büyük girdilerde katlanarak artar. - Okunabilirlik. Yerleşik fonksiyonların açıklayıcı adları vardır (
sum,min), bu yüzden niyetleri açıktır. Birreduce()çağrısı, katlanan fonksiyonu okumanızı gerektirir.
Döngüler:
- Performans. Döngüler genellikle yerleşik fonksiyonlardan daha yavaş ama
reduce()’tan daha hızlı çalışır. - Okunabilirlik. Yerleşik fonksiyonlarda olduğu gibi döngüler de genellikle
reduce()’tan daha okunabilirdir.reduce()çağrısı fonksiyonel bir ifadeyi okumanızı gerektirirken, bir döngü daha Pythonik’tir.
itertools.accumulate()
Python'un itertools kütüphanesi, count(), product() ve combinations() gibi performanslı yineleyiciler sunar. Faydalı itertools fonksiyonlarından biri de itertools.accumulate()’tir. reduce() gibi bir fonksiyonu yinelenebilir üzerinde katlar. Ancak accumulate() yalnızca nihai sonucu değil, ara değerleri de saklar.
Örneğin,
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, kümülatif toplamlar veya minimum/maksimumlar gerektiğinde kullanışlıdır. Örneğin, ay bazında maksimum sıcaklığı bilmek isteyebilirsiniz.
reduce() Kullanırken Yaygın Tuzaklar
reduce() kullanırken aşağıdaki tuzakları aklınızda tutun.
- Yerleşik fonksiyonlar veya döngüler gibi daha basit alternatifleri tercih edin.
reduce()’ı gerçekten ihtiyacınız olduğunda kullanın. - Boş yinelenebilirleri yönetin. Boş girdide hatayı önlemek için her zaman uygun bir başlatıcı kullanın.
- Bellek sorunlarına dikkat edin. Bir üreteç veya akış yaklaşımının daha verimli olacağı durumlara
reduce()’ı zorla sığdırmayın. - Zorlayıcı lambdalardan kaçının. Mümkün olduğunda
operatormodülündeki fonksiyonları kullanın. Özellikle birleşme özelliği olmayan (non-associative) işlemlerde lambdalar açıklığı zedeleyebilir. - Zekice olandan ziyade açıklığı tercih edin.
Python reduce() En İyi Uygulamalar ve Yönergeler
Her araçta olduğu gibi, reduce() için de en iyi uygulamalar vardır. reduce()’ın doğru araç olduğuna karar verdiyseniz, kullanımı için bazı yönergeler şunlardır.
Önce azaltıcıyı tasarlayın
- Sözleşmeyi tek cümleyle tanımlayın. “Sözlük anahtarlarını, anahtar başına sayımları toplayarak birleştir.”
- Mümkünse birleşmeli (associative) tutun. Bu, paralelleştirmeyi ve test etmeyi kolaylaştırır.
- Özdeşlik (identity) öğenizi belirleyin ve başlatıcı olarak kullanın. Örneğin, toplam için özdeşlik 0’dır.
miniçinmath.inf,küme birleşimiiçinset()’dir. Bu, kodu sağlam tutar veTypeError’lardan uzak durmanızı sağlar.
Basit ve okunabilir tutun
- Tek ve net bir işlem. Yan etkisiz.
- Azaltıcıya açıklayıcı bir ad verin.
Belgeleyin
- Docstring içinde özdeşlik öğesini ve boş girdi davranışını, birleşmelik varsayımını, hata politikasını kaydedin.
Test edin
- Köşe durumlarında birim testleri: boş yinelenebilir, karışık türler, uç değerler.
- Birleşmeyi test edin.
Performansı izleyin
- Küçük ve büyük girdilerde karşılaştırmalı ölçümler yapın. Yerleşik fonksiyonlar ve döngülerle kıyaslayın.
- Hız önemliyse, ön işleme, toplu işlemeye ve ağır matematiği NumPy veya pandas’a taşımaya bakın.
Python reduce() için İleri Düzey Uygulamalar ve Gerçek Dünya Kullanımları
Dezavantajları göz önüne alındığında, reduce()’ın gerçek bir değeri yokmuş gibi görünebilir. Aksine, reduce()’ın birçok faydalı kullanım alanı vardır.
- İç içe geçmiş yapıların işlenmesi
- Veritabanı tarzı işlemler
- Veri işleme boru hatları
- MapReduce uygulamaları
İç içe geçmiş yapıların işlenmesi
Reduce, JSON nesneleri gibi iç içe geçmiş veri yapılarını, bir anahtar dizisini ardışık aramalara katlayarak dolaşmanın temiz bir yolunu sunar.
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
reduce() kullanmak burada mantıklıdır. JSON derinlemesine iç içedir: user → profile → address → city. Aramaları elle zincirlemek yerine, yolu bir anahtar listesi olarak temsil edin. Ardından reduce(operator.getitem, path, data) ile gezin. Bu, kodu genel, okunabilir ve yeniden kullanılabilir tutar.
Veri işleme boru hatları
Reduce, veriyi bir dizi dönüşümden geçirerek veri işleme boru hatlarını çalıştırabilir. Her fonksiyon tek bir adımı ele alır ve boru hattı, bunların sırayla uygulanmasıyla oluşur. İşte bir NLP modeline vermeden önce bir metni önişleyen küçük bir boru hattı.
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
Karmaşık uygulamalarda hata yönetimi
İç içe JSON örneğine geri dönelim. Şu anda, doğrudan reduce(operator.getitem, …) çağrısı, bir anahtar eksikse veya sözlük olmayan bir şeyle karşılaşırsa KeyError ya da TypeError fırlatır. Kodu daha güvenli hale getirmek için, operator.getitem’i try/except bloğunda saran ve hata oluştuğunda varsayılan bir değer döndüren bir yardımcı fonksiyon tanımlayın.
Yardımcı fonksiyon için bir olasılık şöyle.
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
Şimdi, sarmalanmamış bir reduce() yerine yeni fonksiyonumuzu kullanacak şekilde örnek aramaları değiştirin
# 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"
map() ve filter() ile çok adımlı veri dönüşümleri
reduce()’ı map() ve filter() gibi diğer fonksiyonel araçlarla birleştirip çok adımlı veri dönüşümleri oluşturabilirsiniz. İşte önceki NLP önişleme boru hattımızın fonksiyonel yazımı.
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
Dönüşümler şunlardır:
- Noktalama işaretlerini kaldırma. "The quick brown fox is Jumping over a log." → "The quick brown fox is Jumping over a log"
- Küçük harfe çevirme
"The quick brown fox is Jumping over a log" → "the quick brown fox is jumping over a log"
- Durdurma sözcüklerini kaldırma. "the quick brown fox is jumping over a log" → "quick brown fox jumping over log"
- Köklerine ayırma (stemming)
"quick brown fox jumping log" → "quick brown fox jump log"
Fonksiyonel programlama ve vektörleştime fikirlerini daha fazla keşfetmek için şu DataCamp makalelerini öneririz.
- Python filter(): İhtiyacınız Olanı Tutun - eğitim
- Groupby, split-apply-combine ve pandas - eğitim
- Pandas Apply Eğitimi - eğitim
Modern Python Ekosistemiyle Entegrasyon
reduce()’ı iyi kullanmak için, modern Python ekosistemine nasıl oturduğunu anlamak faydalıdır. NumPy ve pandas ile nasıl yan yana çalıştığına, paralel ve dağıtık sistemlerin temelini nasıl oluşturduğuna ve statik analizörler gibi modern araçlarla nasıl etkileştiğine bakalım.
numpy ve pandas
NumPy ve pandas optimize C kodundan çalışır; bu nedenle işlevlerini reduce() ile kopyalamayın. Ancak, adımları dinamik olan boru hatları için reduce() iyi bir tercihtir. Örneğin, bir dizi üzerinde birçok NumPy dönüşümünü birleştirebilirsiniz.
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
Paralel ve dağıtık hesaplama çerçeveleri
Reduce, paralel ve dağıtık hesaplama çerçevelerinin merkezindedir. Bu sistemler, bir veri kümesini parçalara bölerek, bu parçaları paralel olarak işleyip ardından bu kısmi sonuçları tek bir yanıtta birleştirerek çalışır. “Birleştirme” adımı indirgemedir.
Küme genelinde indirgeme düzgün çalışsın diye aşağıdaki koşulların sağlandığından emin olun.
- Birleşmelik. İşlem, gruplaştırmadan bağımsız olarak aynı sonucu vermelidir. Bu, kısmi sonuçların ağ genelinde herhangi bir sırada birleştirilmesine olanak tanır.
- Özdeşlik öğesi. İşlemin, nihai sonucu etkilemeyen bir başlatıcısı olmalıdır. Bu, bölümler boş veya dengesiz boyutlarda olduğunda doğruluğu sağlar.
Bu özellikler yoksa, indirgemeler ya yavaşlar (çünkü güvenle paralelleştirilemezler) ya da yanlış sonuç verir (çünkü sonuçlar değerlendirme sırasına bağlı olur).
Bu süreç, birleşmeli bir işlem ve net bir özdeşlik gerektirir. Aksi halde yavaş kod veya hatalı sonuçlar elde edersiniz.
Statik analiz araçları
Statik analizör (mypy, Pyright, ruff veya bandit gibi), kodu çalıştırmadan inceleyen bir araçtır. Bu araçlar hataları yakalar, stil kurallarını uygular ve tür doğruluğunu denetler.
Statik analizörler, reduce() ile zorlanır. Katlama sürecinde biriktirici türü, öğe türünden farklı olabilir ve tür çıkarımı karmaşıklaşır.
Şu kodu düşünün.
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']
Bu kod gayet iyi çalışsa da, bir statik analizör birkaç konuda şikayet edebilir.
- Başlatıcı olan []’ın öğe türü belirsizdir.
- Analizörler biriktirici ve öğe türlerinin eşleştiğini varsayabilir.
Kodu insanlar ve analizörler için daha net kılmak adına tür ipuçları ekleyin.
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']
Artık azaltıcı açıkça şunları gösteriyor
- Biriktirici bir
List[str]’dir. - Her öğe bir
str’dir. - Dönüş türü bir
list[str]’dir.
Bu ipuçlarıyla, statik analizörler kodu sıkı şekilde denetleyebilir.
Sonuç
Reduce, koleksiyonları tek bir sonuca katlamanın temel bir fikir olduğu fonksiyonel programlamadan gelir. Artık Python’da birinci sınıf bir araç olmasa da, yeri vardır. Esnek boru hatlarına, özel fold’lara veya mevcut fonksiyonlara düzgünce uymayan işlemlere ihtiyacınız olduğunda, reduce() güçlü bir araçtır. Dikkatli kullanıldığında, daha geniş Python ekosistemiyle temiz şekilde bütünleşir ve doğru durumlarda pratik bir araç olmaya devam eder.
Faydalı bulabileceğiniz bazı ilgili Python bağlantıları.
Python reduce() SSS
reduce() ne yapar?
İki argümanlı bir fonksiyonu, bir yinelenebilir üzerinde tekrarlı olarak uygulayıp onu tek bir sonuca indirger.
reduce() nerede tanımlıdır?
Sürüm 3’ten önce yerleşik bir fonksiyondu. O zamandan beri functools modülünde yer alır.
reduce()'a ne tür fonksiyonlar geçirmeliyim?
Basit, birleşmeli ve iyi belgelenmiş fonksiyonlar. Yan etkilerden ve birleşmeli olmayan mantıktan kaçının.
itertools.accumulate() ile nasıl karşılaştırılır?
reduce() yalnızca nihai sonucu döndürür, accumulate() tüm ara sonuçları üretir.
Başlatıcıyı ne zaman kullanmalıyım?
Yinelenebilir boş olabilecekse bir başlatıcı kullanın.
