Neden önemli?
Anadolu Market'in rapor ekibinden şu istek gelsin: "Siparişleri önce şehre göre alfabetik, aynı şehirde tutara göre büyükten küçüğe sırala." Ya da Ilgaz Bank'tan: "Her şube için farklı komisyon oranıyla hesaplama yapan bir fonksiyon üret." Bu isteklerin ortak noktası, fonksiyonun kendisinin bir veri gibi ele alınmasıdır: sıralama ölçütünü fonksiyon olarak ver, komisyon oranını fonksiyona gömüp yeni fonksiyon üret. Python'da fonksiyonlar birinci sınıf (first-class) nesnelerdir: değişkene atanır, listeye konur, argüman olarak geçirilir, başka fonksiyondan döndürülür. Bu dersteki araçların hepsi bu tek fikrin üstüne kuruludur.
Fonksiyon bir değerdir
def kdv_ekle(tutar):
return round(tutar * 1.20, 2)
def indirim_uygula(tutar):
return round(tutar * 0.90, 2)
# Değişkene ata (parantez yok: çağırmıyoruz, işaret ediyoruz)
islem = kdv_ekle
print(islem(100))
# Listeye koy, sırayla uygula
boru = [indirim_uygula, kdv_ekle]
tutar = 1000
for f in boru:
tutar = f(tutar)
print(f.__name__, "->", tutar)
# Argüman olarak geçir
def uygula(fonk, degerler):
return [fonk(d) for d in degerler]
print(uygula(kdv_ekle, [100, 250]))
kdv_ekle ile kdv_ekle() arasındaki fark kritik: ilki fonksiyon nesnesinin kendisi, ikincisi onun çağrılıp dönen sonucu. Argüman olarak her zaman parantezsiz halini verirsin.
lambda: tek satırlık isimsiz fonksiyon
lambda parametreler: ifade, tek bir ifade döndüren küçük bir fonksiyon üretir. Adı yoktur, return yazılmaz, gövdesi tek ifadedir. Yeri, başka fonksiyona kısa bir ölçüt verirken.
kare = lambda x: x * x
print(kare(7))
# Asıl kullanım: argüman olarak
siparisler = [("IST-1", 1200), ("ANK-2", 350), ("IZM-3", 4800)]
print(sorted(siparisler, key=lambda s: s[1]))
print(max(siparisler, key=lambda s: s[1]))
kare = lambda x: x * x çalışır ama PEP 8 buna karşıdır: hata mesajlarında adı "<lambda>" görünür ve docstring yazamazsın. Bir ad verecek kadar önemliyse def yaz. lambda yalnız "o anda, o satırda" gereken küçük ölçütler içindir.sorted ve key: tek, çoklu ve karışık yönlü sıralama
sorted(kaynak, key=f), her öğe için f(öğe)'yi hesaplar ve bu değere göre sıralar; öğenin kendisi değişmez. Birden fazla ölçüt için key bir demet döndürür: Python demetleri soldan sağa karşılaştırır.
siparisler = [
{"no": 101, "sehir": "İstanbul", "tutar": 1200},
{"no": 102, "sehir": "Ankara", "tutar": 350},
{"no": 103, "sehir": "İstanbul", "tutar": 4800},
{"no": 104, "sehir": "Ankara", "tutar": 2100},
{"no": 105, "sehir": "İzmir", "tutar": 90},
]
# Tek anahtar
for s in sorted(siparisler, key=lambda s: s["tutar"], reverse=True):
print(s["no"], s["tutar"])
print("---")
# Çok anahtar: şehir artan, sonra tutar artan
for s in sorted(siparisler, key=lambda s: (s["sehir"], s["tutar"])):
print(s["sehir"], s["tutar"])
print("---")
# Karışık yön: şehir artan, tutar AZALAN -> sayıyı eksiyle ters çevir
for s in sorted(siparisler, key=lambda s: (s["sehir"], -s["tutar"])):
print(s["sehir"], s["tutar"])
Karışık yön hilesi sayılar için -değer. Metin alanını ters çevirmek gerekirse iki aşamalı sıralama yaparsın: önce ikincil anahtara göre sırala, sonra birincil anahtara göre reverse=True ile; Python'ın sıralaması kararlı (stable) olduğu için eşit öğelerin önceki sırası korunur. Sözlük alanlarına erişim için operator.itemgetter("sehir", "tutar") lambda'nın hazır ve biraz daha hızlı karşılığıdır.
key=str.casefold yeter; tam Türkçe sıralama için locale ya da özel bir anahtar fonksiyonu gerekir.map, filter, reduce
Üç klasik: map(f, kaynak) her öğeye f uygular, filter(f, kaynak) f'nin True döndürdüğü öğeleri tutar, functools.reduce(f, kaynak) öğeleri ikişer ikişer birleştirerek tek değere indirger. map ve filter tembel yineleyici (iterator) döndürür.
from functools import reduce tutarlar = [1200, 350, 4800, 90] print(list(map(lambda t: t * 1.2, tutarlar))) print(list(filter(lambda t: t > 500, tutarlar))) print(reduce(lambda a, b: a + b, tutarlar)) print(reduce(lambda a, b: a if a > b else b, tutarlar)) # map ile hazır fonksiyon: lambda gerekmez metin_tutarlar = ["1200", "350"] print(list(map(int, metin_tutarlar))) print(sum(map(int, metin_tutarlar))) # Aynı işler comprehension ile print([t * 1.2 for t in tutarlar]) print([t for t in tutarlar if t > 500]) print(sum(tutarlar), max(tutarlar))
Son blok önemli bir dersi gösteriyor: map/filter'ın lambda ile kullanımı çoğu zaman comprehension'dan daha az okunur, ve reduce'un en yaygın iki kullanımı zaten sum ve max olarak hazır. Kural şu:
| Durum | Tercih | Örnek |
|---|---|---|
| Hazır fonksiyon uygulanacak | map | map(int, satirlar), map(str.strip, satirlar) |
| Dönüşüm bir ifade | comprehension | [t * 1.2 for t in tutarlar] |
| Süzme + dönüşüm birlikte | comprehension | [t * 1.2 for t in tutarlar if t > 500] |
| Toplam, en büyük, en küçük | sum/max/min | sum(tutarlar) |
| Özel ikili birleştirme | reduce | Sözlükleri birleştirmek, çarpım |
| Fonksiyonu zincire argüman vermek | map/filter | Boru hattında (pipeline) ölçüt dışarıdan geliyorsa |
any ve all
İki küçük ama çok kullanılan araç. any "en az biri doğru mu", all "hepsi doğru mu" sorar; ikisi de üreteç (generator) ifadesiyle en güzel okunur ve sonucu bulur bulmaz durur (kısa devre).
basvurular = [
{"ad": "Ayşe", "gelir": 25000, "kara_liste": False},
{"ad": "Mehmet", "gelir": 9000, "kara_liste": False},
{"ad": "Zeynep", "gelir": 40000, "kara_liste": True},
]
print(any(b["kara_liste"] for b in basvurular))
print(all(b["gelir"] >= 10000 for b in basvurular))
print(all(b["ad"] for b in basvurular)) # boş ad yok mu?
print(any([]), all([])) # boş kaynakta: False, True
Closure: ayarını içinde taşıyan fonksiyon
Bir fonksiyon, içinde tanımlanan başka bir fonksiyonu döndürürse, içteki fonksiyon dıştakinin değişkenlerini "hatırlar". Bu yapıya closure (kapanış) denir. Ilgaz Bank'ta her şubenin komisyon oranı farklı olsun: tek bir fabrika (factory) fonksiyonundan şubeye özel hesaplayıcılar üretirsin.
def komisyon_hesaplayici(oran, asgari):
def hesapla(tutar):
return max(round(tutar * oran, 2), asgari)
return hesapla
kadikoy = komisyon_hesaplayici(0.015, 5)
merkez = komisyon_hesaplayici(0.010, 10)
print(kadikoy(1000), kadikoy(100))
print(merkez(1000), merkez(100))
# İçteki fonksiyon neyi hatırlıyor?
print(kadikoy.__closure__[0].cell_contents, kadikoy.__closure__[1].cell_contents)
# Sayaç: durum tutan closure (nonlocal gerekir)
def sayac():
adet = 0
def arttir():
nonlocal adet
adet += 1
return adet
return arttir
say = sayac()
print(say(), say(), say())
nonlocal, içteki fonksiyonun dıştaki değişkene yazmasına izin verir; yalnız okuyorsan gerekmez. Closure, "ayar + davranış" paketlemenin hafif yoludur; sınıf yazmaya değmeyecek kadar küçük durumlar için idealdir. Bir sonraki derste göreceğin dekoratörler (decorator) tümüyle closure'dur.
functools.partial: argümanı önceden doldur
Varolan bir fonksiyonun bazı argümanlarını sabitleyip yeni fonksiyon üretmek istiyorsan closure yazmana gerek yok; partial bunu tek satırda yapar.
from functools import partial
def fiyatla(tutar, kdv, indirim):
return round(tutar * (1 - indirim) * (1 + kdv), 2)
standart = partial(fiyatla, kdv=0.20, indirim=0.0)
kampanya = partial(fiyatla, kdv=0.20, indirim=0.15)
print(standart(1000))
print(kampanya(1000))
print(list(map(kampanya, [100, 200])))
# Hazır fonksiyonlarla da
ikili = partial(int, base=2)
print(ikili("1010"))
partial ürünü sıradan bir çağrılabilirdir; map'e, sorted'ın key'ine, bir dekoratöre argüman olarak verilebilir. Doldurduğun argümanları çağrı anında yeniden geçersen senin verdiğin kazanır.
Hepsi bir arada: Anadolu Market sipariş raporu
from functools import partial
siparisler = [
{"no": 101, "sehir": "İstanbul", "tutar": 1200, "durum": "teslim"},
{"no": 102, "sehir": "Ankara", "tutar": 350, "durum": "iptal"},
{"no": 103, "sehir": "İstanbul", "tutar": 4800, "durum": "kargoda"},
{"no": 104, "sehir": "Ankara", "tutar": 2100, "durum": "teslim"},
]
def esik_ustu(esik, s):
return s["durum"] != "iptal" and s["tutar"] >= esik
buyukler = list(filter(partial(esik_ustu, 1000), siparisler))
sirali = sorted(buyukler, key=lambda s: (s["sehir"], -s["tutar"]))
for s in sirali:
print(f'{s["sehir"]:<10}{s["no"]:>5}{s["tutar"]:>8}')
print("Hepsi 1000 üstü mü:", all(s["tutar"] >= 1000 for s in sirali))
Derinleş: fonksiyonlarla çalışan araç kutusu
lambda, sorted/key, map/filter/reduce, any/all, closure ve partial'ı gördün. Bu bölüm, iş kodunda bunları daha temiz ve doğru kullanmanı sağlayan araçları ekliyor: operator modülü, kararlı sıralamayla karışık yönlü sıralama, Türkçe alfabetik sıralama, min/max'ın default parametresi, fonksiyon tabloları (dispatch) ve fonksiyon birleştirme (composition). Closure'ların kapsam ayrıntıları için Kapsam ve kapanışlar dersine bakabilirsin.
operator modülü: lambda yazmadan anahtar
key=lambda k: k["tutar"] o kadar sık yazılır ki standart kütüphane bunun hazırını sunar. itemgetter sözlük anahtarı ya da indeksle, attrgetter nesne özniteliğiyle, methodcaller metot çağrısıyla değer çeken fonksiyonlar üretir. Birden çok alan verirsen demet döndürür; çoklu sıralama anahtarı hazır olur. Ayrıca biraz daha hızlıdır ve adı ne yaptığını söyler.
| lambda ile | operator ile |
|---|---|
lambda k: k["tutar"] | itemgetter("tutar") |
lambda k: (k["sehir"], k["tutar"]) | itemgetter("sehir", "tutar") |
lambda d: d[1] | itemgetter(1) |
lambda m: m.bakiye | attrgetter("bakiye") |
lambda s: s.strip() | methodcaller("strip") |
lambda a, b: a * b | operator.mul |
from operator import itemgetter, mul
from functools import reduce
siparisler = [
{"id": 3, "sehir": "İzmir", "tutar": 820.0},
{"id": 1, "sehir": "Ankara", "tutar": 1830.0},
{"id": 2, "sehir": "Ankara", "tutar": 450.0},
]
for s in sorted(siparisler, key=itemgetter("sehir", "tutar")):
print(s["id"], s["sehir"], s["tutar"])
kac = itemgetter("id", "tutar")
print([kac(s) for s in siparisler])
print("Faktöriyel 5:", reduce(mul, range(1, 6), 1))
Kararlı sıralama: karışık yönler, metinlerde bile
Sayısal alanda azalan sıralama için eksi işareti hilesini (-k["tutar"]) gördün. Ama metni "eksi" yapamazsın: "şehre göre artan, aynı şehirde ada göre azalan" isteğinde ne yaparsın? Python'un sıralaması kararlıdır (stable): eşit anahtarlı elemanların önceki sırası korunur. Bu yüzden önce ikincil ölçüte göre, sonra birincil ölçüte göre sıralarsın; her geçiş kendi yönünü alır.
from operator import itemgetter
calisanlar = [("Satış", "Ayşe"), ("BT", "Can"), ("Satış", "Mehmet"), ("BT", "Ali"), ("Satış", "Zeynep")]
adim1 = sorted(calisanlar, key=itemgetter(1), reverse=True) # ikincil: ad, azalan
adim2 = sorted(adim1, key=itemgetter(0)) # birincil: departman, artan
for dep, ad in adim2:
print(dep, ad)
Türkçe alfabetik sıralama
sorted(["Çorum", "Bursa", "Ağrı", "Iğdır", "İzmir"]) Unicode kod sırasına göre sıralar: Ç, I, İ, Ş, Ğ gibi harfler Latin harflerinin sonuna düşer. Türkçe sıralama için kendi anahtarını yazarsın: her harfi Türk alfabesindeki sıra numarasına çeviren bir fonksiyon. (Kendi bilgisayarında locale.strxfrm ile işletim sisteminin Türkçe ayarı da kullanılabilir ama sunucuda o ayar kurulu olmayabilir.)
ALFABE = "abcçdefgğhıijklmnoöprsştuüvyz"
SIRA = {h: i for i, h in enumerate(ALFABE)}
def tr_kucuk(metin):
return metin.replace("I", "ı").replace("İ", "i").lower()
def tr_anahtar(metin):
return [SIRA.get(h, 100 + ord(h)) for h in tr_kucuk(metin)]
iller = ["Çorum", "Bursa", "Şanlıurfa", "Iğdır", "İzmir", "Ağrı", "Uşak", "Ordu", "Ödemiş"]
print("Varsayılan:", sorted(iller))
print("Türkçe :", sorted(iller, key=tr_anahtar))
min, max: key ve default
max(siparisler, key=itemgetter("tutar")) en büyük tutarlı siparişi (sözlüğü) döndürür, tutarın kendisini değil. Boş veride ValueError fırlatır; default= parametresi bunu önler. Eşitlikte ilk karşılaşılan eleman döner.
from operator import itemgetter
depolar = [{"ad": "Gebze", "km": 42, "stok": 0}, {"ad": "Tuzla", "km": 18, "stok": 5}, {"ad": "Esenyurt", "km": 18, "stok": 9}]
uygun = [d for d in depolar if d["stok"] > 0]
print(min(uygun, key=itemgetter("km"))["ad"]) # eşitlikte ilki
print(min((d for d in depolar if d["stok"] > 100), key=itemgetter("km"), default=None))
Fonksiyon tablosu ve fonksiyon birleştirme
Fonksiyonlar değer olduğu için sözlüğe konabilir. Uzun bir if/elif zinciri yerine "işlem adı → fonksiyon" tablosu (dispatch table) kurarsın; yeni işlem eklemek tek satırdır. Fonksiyonları sıralı bir boru hattına bağlamak (composition) ise reduce ile birkaç satırdır.
from functools import reduce
KOMISYON = {
"havale": lambda t: 0.0,
"eft": lambda t: 4.5,
"swift": lambda t: max(25.0, round(t * 0.002, 2)),
}
def komisyon(tur, tutar):
try:
return KOMISYON[tur](tutar)
except KeyError:
raise ValueError(f"bilinmeyen işlem türü: {tur}") from None
print(komisyon("eft", 1000), komisyon("swift", 50000))
def boru(*fonksiyonlar):
return lambda x: reduce(lambda deger, f: f(deger), fonksiyonlar, x)
temizle = boru(str.strip, str.lower, lambda s: s.replace(" ", "_"))
print(temizle(" Anadolu Market Rapor "))
reduce(f, liste) boş listede TypeError verir. Üçüncü argüman (initializer) hem boş durumu çözer hem de türü belirler: reduce(mul, sayilar, 1). Toplama için ise reduce yerine sum kullan.map ve filter'ın iki küçük sırrı
map(f, a, b)birden çok yinelenebilir alır vef(a[i], b[i])çağırır; en kısa olan bitince durur:list(map(mul, fiyatlar, adetler)).filter(None, veri)yalnız doğru (truthy) değerleri bırakır: boş metinleri, 0'ları ve None'ları atmanın kısa yolu. Dikkat: 0 da gider; 0 geçerli bir değerse kullanma.
itemgetter ve iki geçişli sıralama), (2) iş kurallarının tablo hâlinde tutulması (komisyon türleri, KDV oranları, durum geçişleri: if/elif yerine sözlük), (3) veri temizleme adımlarının sıralı bir fonksiyon listesi olarak yazılması. Üçünde de amaç aynı: kural değişince kodun yapısı değil, yalnızca tablo ya da liste değişsin. Türkçe sıralama ise müşteri listesi, il listesi gibi kullanıcıya gösterilen her ekranda istenir; "Çorum neden en sonda?" sorusu mutlaka gelir.Saf fonksiyon ve test edilebilirlik
Fonksiyonel programlamanın kalbi saf fonksiyondur (pure function): aynı girdiye her zaman aynı çıktıyı verir, dışarıdaki hiçbir şeyi değiştirmez. kdv_ekle(100) saftır; "global listeye ekleyen" bir fonksiyon değildir. Saf fonksiyonlar test etmesi en kolay kod parçalarıdır: girdi ver, çıktıyı assert et, bitti. İş kodunda veri dönüşümlerini saf fonksiyonlara, yan etkileri (dosya yazma, veritabanı, e-posta) ise sınırlı birkaç noktaya toplarsan hem test hem hata ayıklama (debugging) kolaylaşır.
Closure'ın geç bağlanma tuzağı
Döngü içinde lambda ya da iç fonksiyon üretirken dikkat: [lambda: i for i in range(3)] listesindeki üç fonksiyon da çağrıldığında 2 döndürür; çünkü i çağrı anında okunur, döngü bitmiştir. Çözüm değeri o anda bağlamak: lambda i=i: i ya da partial. Bu tuzak kod incelemelerinde (code review) ve mülakatlarda sık çıkar.
İş hayatında nasıl görünür?
pandas'ta df["tutar"].map(f) ve df.apply(f, axis=1), bu dersteki map'in sütun üzerinde çalışan hali. Web çerçevelerinde yol (route) işleyicileri, olay dinleyicileri ve geri çağırma (callback) fonksiyonları hep "fonksiyonu argüman olarak geçir" kalıbıdır. Konfigürasyonla davranış üretme (şube bazlı komisyon, müşteri segmentine göre indirim) ise closure ve partial'ın doğal alanıdır; bu desenle bir "if şube == ... elif şube == ..." zincirinden kurtulursun.