Neden önemli?
Anadolu Market'in sipariş listesinde her gün binlerce kayıt var. Analist olarak en sık yaptığın iş üç adımdır: bir listeyi dönüştür (her siparişin tutarını KDV'li yap), süz (yalnız İstanbul siparişlerini al) ve yeniden grupla (ürün koduna göre sözlük kur). Temel seviyede bunu for döngüsü ve boş listeye append ile yaptın. Çalışır, ama dört satır tutar ve niyetin satırlar arasında kaybolur. Comprehension (kapsamlı ifade), aynı işi tek ve okunur bir satırda yazmanı sağlar. Python topluluğunun "Pythonic" dediği kodun ilk işareti budur.
Liste comprehension
Kalıp şudur: [ifade for öğe in kaynak]. Köşeli parantez "sonuç bir liste" der; for kısmı kaynağı gezer; baştaki ifade her öğeden ne üretileceğini söyler.
tutarlar = [1200, 350, 4800, 90]
# Klasik yol
kdvli = []
for t in tutarlar:
kdvli.append(round(t * 1.20, 2))
print(kdvli)
# Comprehension
kdvli2 = [round(t * 1.20, 2) for t in tutarlar]
print(kdvli2)
print(kdvli == kdvli2)
İkisi aynı listeyi üretir. Farkı okuyuş biçiminde: ilkinde "boş liste aç, döngü kur, ekle" adımlarını izlersin; ikincisinde "tutarların KDV'li hali" diye tek nefeste okursun. Comprehension'ı sağdan sola değil, baştan oku: "şunu al, her öğe için, kaynaktan".
Süzme: sondaki if
Kaynaktaki her öğeyi değil, yalnız koşulu sağlayanları istiyorsan for'dan sonra bir if eklersin. Bu if bir eleme filtresidir; else'i yoktur.
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": "İzmir", "tutar": 90, "durum": "teslim"},
{"no": 105, "sehir": "İstanbul", "tutar": 760, "durum": "iptal"},
]
ist = [s["no"] for s in siparisler if s["sehir"] == "İstanbul"]
print("İstanbul siparişleri:", ist)
gecerli_tutar = [s["tutar"] for s in siparisler if s["durum"] != "iptal"]
print("İptal olmayan tutarlar:", gecerli_tutar)
print("Ciro:", sum(gecerli_tutar))
# Birden fazla koşul: and ile
buyuk_ist = [s["no"] for s in siparisler
if s["sehir"] == "İstanbul" and s["tutar"] > 1000]
print("İstanbul ve 1000 üstü:", buyuk_ist)
Koşullu değer: baştaki if-else
Her öğeyi tutmak ama bazısına farklı değer vermek istiyorsan if-else ifadenin başına gelir. Bu bir filtre değil, "şu durumda A, aksi halde B" seçimidir.
tutarlar = [1200, 350, 4800, 90]
etiket = ["büyük" if t >= 1000 else "küçük" for t in tutarlar]
print(etiket)
# İkisi birlikte: önce süz (sondaki if), sonra seç (baştaki if-else)
pozitif_etiket = ["büyük" if t >= 1000 else "küçük"
for t in tutarlar if t > 100]
print(pozitif_etiket)
if öğe eler, else alamaz. Baştaki if ... else ... öğe elemez, her öğeye bir değer verir ve else zorunludur. [x for x in liste if x > 0 else 0] yazarsan SyntaxError alırsın.Sözlük ve küme comprehension
Süslü parantez ve anahtar: değer yazarsan sözlük, yalnız tek değer yazarsan küme üretilir. Sözlük comprehension, listeyi "anahtara göre erişilebilir" hale getirmenin en kısa yoludur.
siparisler = [
{"no": 101, "sehir": "İstanbul", "tutar": 1200},
{"no": 102, "sehir": "Ankara", "tutar": 350},
{"no": 103, "sehir": "İstanbul", "tutar": 4800},
{"no": 104, "sehir": "İzmir", "tutar": 90},
]
# Sipariş no -> tutar sözlüğü
tutar_by_no = {s["no"]: s["tutar"] for s in siparisler}
print(tutar_by_no[103])
# Fiyat listesini yüzde 10 zamla yeniden kur
fiyat = {"kahve": 120, "çay": 80, "şeker": 45}
zamli = {urun: round(f * 1.10) for urun, f in fiyat.items()}
print(zamli)
# Küme: tekrarsız şehirler
sehirler = {s["sehir"] for s in siparisler}
print(sehirler, len(sehirler))
Sözlük comprehension'da aynı anahtar iki kez üretilirse sonuncusu kazanır; bu bazen istediğin şeydir (son fiyatı tut), bazen sessiz bir veri kaybı. Emin değilsen önce anahtarların tekrarsız olduğunu kontrol et.
İç içe comprehension
İki for yan yana yazılabilir; sıraları, klasik iç içe döngüdeki sırayla aynıdır: dıştaki önce. Tipik kullanım, liste listesini düzleştirmek (flatten) ya da iki listenin çiftlerini üretmektir.
kargolar = [
["K-1", "K-2"],
["K-3"],
["K-4", "K-5", "K-6"],
]
duz = [k for paket in kargolar for k in paket]
print(duz)
# Aynı şeyin klasik hali; for sırası birebir aynı
duz2 = []
for paket in kargolar:
for k in paket:
duz2.append(k)
print(duz == duz2)
# Ürün x şehir çiftleri
urunler = ["kahve", "çay"]
sehirler = ["Ankara", "İzmir"]
print([(u, s) for u in urunler for s in sehirler])
İç içe iki for ile bir if hâlâ okunur. Üçüncü for ya da iç içe bir comprehension (comprehension içinde comprehension) başladığında dur ve klasik döngü yaz; aşağıda nedenini göreceksin.
Üreteç ifadesi: parantezsiz tembel liste
Köşeli parantez yerine yuvarlak parantez kullanırsan ortaya bir liste değil, bir üreteç (generator) çıkar. Üreteç, öğeleri istendikçe tek tek üretir; tamamını bellekte tutmaz. Toplama, en büyük bulma gibi tek geçişlik işlerde bu, hem bellek hem zaman kazandırır.
siparisler = [
{"no": 101, "tutar": 1200, "durum": "teslim"},
{"no": 102, "tutar": 350, "durum": "iptal"},
{"no": 103, "tutar": 4800, "durum": "kargoda"},
]
# Liste: önce tüm tutarlar bellekte kurulur, sonra toplanır
print(sum([s["tutar"] for s in siparisler if s["durum"] != "iptal"]))
# Üreteç: öğeler sum'a aktıkça üretilir
print(sum(s["tutar"] for s in siparisler if s["durum"] != "iptal"))
# any / all ile doğal eşleşir
print(any(s["durum"] == "iptal" for s in siparisler))
print(all(s["tutar"] > 0 for s in siparisler))
g = (s["no"] for s in siparisler)
print(type(g).__name__)
print(list(g))
print(list(g)) # ikinci kez boş: üreteç tükendi
Son iki satır önemli: üreteç bir kere gezilir. Sonucu iki kez kullanacaksan listeye çevir ya da üreteci yeniden kur. Fonksiyona tek argüman olarak veriyorsan dıştaki parantez yeterlidir: sum(x for x in ...).
| Biçim | Sözdizimi | Sonuç | Tipik iş |
|---|---|---|---|
| Liste | [f(x) for x in k] | list, hemen kurulur | Dönüştürülmüş liste gerekince |
| Sözlük | {a: b for x in k} | dict | Listeyi anahtarla erişilir yapmak |
| Küme | {f(x) for x in k} | set, tekrarsız | Benzersiz değerler |
| Üreteç ifadesi | (f(x) for x in k) | generator, tembel | sum/max/any/all, büyük veri |
Ne zaman kullanılmamalı?
Comprehension bir okunurluk aracıdır; okunurluğu bozuyorsa amacından sapmış demektir. Şu işaretlerde klasik döngüye dön:
- Yan etki:
[print(x) for x in liste]gibi sonucu kullanılmayan comprehension yazma. Hem gereksiz bir liste kurar hem niyeti gizler.foryaz. - Üçten fazla parça: İki
for+ birifsınırdır. Daha fazlası tek satıra sığsa bile kimse okuyamaz. - Karmaşık ifade: Baştaki ifade uzun bir
if-elsezinciri ya da birden çok fonksiyon çağrısıysa önce yardımcı fonksiyon yaz, comprehension'da onu çağır. - Hata yakalama gerekiyorsa: Comprehension içinde
tryyazılamaz. Öğe bazında hata olasılığı varsa döngü ya da yardımcı fonksiyon. - Döngü içinde durma gerekiyorsa:
breakcomprehension'da yoktur. Belirli bir koşulda durman gerekiyorsa döngü ya daitertools.takewhile.
siparisler = [
{"no": 101, "sehir": "istanbul ", "tutar": "1200"},
{"no": 102, "sehir": "ANKARA", "tutar": "350"},
{"no": 103, "sehir": " İzmir", "tutar": "4800"},
]
# Okunmaz: her şey tek satırda
kotu = {s["no"]: (s["sehir"].strip().title(), int(s["tutar"]) * 1.2) for s in siparisler if s["tutar"].isdigit() and len(s["sehir"].strip()) > 2}
# Okunur: yardımcı fonksiyon + kısa comprehension
def temizle(s):
return (s["sehir"].strip().title(), round(int(s["tutar"]) * 1.2, 2))
def gecerli(s):
return s["tutar"].isdigit() and len(s["sehir"].strip()) > 2
iyi = {s["no"]: temizle(s) for s in siparisler if gecerli(s)}
print(iyi)
print(kotu == iyi)
s, x) kendi kapsamında kalır; dışarıdaki aynı adlı değişkeni ezmez. Python 2'de ezerdi; eski kaynaklardaki uyarılar bu yüzdendir.Anadolu Market: sipariş süzme boru hattı
Şimdi parçaları birleştir. Ham sipariş listesinden şu rapor istensin: iptal olmayan, tutarı 500 üstü siparişlerin şehir bazında toplam cirosu.
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": "İzmir", "tutar": 90, "durum": "teslim"},
{"no": 105, "sehir": "İstanbul", "tutar": 760, "durum": "iptal"},
{"no": 106, "sehir": "Ankara", "tutar": 2100, "durum": "teslim"},
]
gecerli = [s for s in siparisler if s["durum"] != "iptal" and s["tutar"] > 500]
sehirler = {s["sehir"] for s in gecerli}
rapor = {sehir: sum(s["tutar"] for s in gecerli if s["sehir"] == sehir)
for sehir in sorted(sehirler)}
print(rapor)
Üç comprehension, üç net adım: süz, benzersiz şehirleri bul, her şehir için topla. Her satır tek bir şey yapıyor; bu yüzden bir ay sonra da anlaşılır.
Derinleş: comprehension'ın ince ayarları
Liste, sözlük ve küme comprehension'larını, iç içe yapıları ve üreteç ifadelerini gördün. Bu bölüm, iş kodunda ve PCAP sınavında karşına çıkan ince noktaları topluyor: aynı hesabı iki kez yapmamak için walrus işleci (:=), döngü değişkeninin kapsamı, iki boyutlu listelerde paylaşılan satır tuzağı, sözlükleri dönüştürürken anahtar çakışması ve enumerate/zip ile birlikte kullanım.
Walrus (:=): hesapla, süz ve kullan
Bir değeri hem süzme koşulunda hem sonuçta kullanacaksan, comprehension'da onu iki kez hesaplamak zorunda kalırsın. Python 3.8 ile gelen atama ifadesi (assignment expression), halk arasındaki adıyla walrus :=, değeri koşulun içinde bir ada bağlar; aynı ad ifadenin başında kullanılabilir.
urunler = [("Kulaklık", 899.90), ("Kablo", 79.90), ("Monitör", 4599.0), ("Mouse", 349.50)]
def kdvli(fiyat):
return round(fiyat * 1.20, 2)
# İki kez hesaplar: kdvli() hem koşulda hem sonuçta
pahali_1 = [(ad, kdvli(f)) for ad, f in urunler if kdvli(f) > 1000]
# Bir kez hesaplar
pahali_2 = [(ad, k) for ad, f in urunler if (k := kdvli(f)) > 1000]
print(pahali_1)
print(pahali_2 == pahali_1)
if k := kdvli(f) > 1000 yazarsan önce karşılaştırma yapılır ve k'ye True/False atanır. Her zaman (k := ifade) > 1000 biçiminde parantezle. Walrus'un bağladığı ad, comprehension bittikten sonra dışarıda da görünür; bu bilinçli bir tasarım kararıdır.Kapsam: döngü değişkeni dışarı sızmaz
Python 3'te her comprehension kendi küçük kapsamında (scope) çalışır. [x for x in ...] içindeki x, dışarıdaki aynı adlı değişkene dokunmaz. Normal for döngüsünde ise döngü değişkeni döngüden sonra son değerini korur. İkisini karıştırmak eski Python 2 kodlarını taşırken sık görülen bir hatadır.
i = "dokunulmadı"
kareler = [i * i for i in range(4)]
print(kareler, "| dışarıdaki i:", i)
for j in range(4):
pass
print("for sonrası j:", j) # for döngüsü sızdırır
toplam = [(n := n_ + 1) for n_ in range(3)]
print("walrus sızar, n =", n)
| Yapı | Sonuç | Döngü değişkeni dışarıda? | Tipik kullanım |
|---|---|---|---|
[ifade for x in it] | list | Hayır | Dönüştürülmüş yeni liste |
{k: v for x in it} | dict | Hayır | Arama tablosu, indeks |
{ifade for x in it} | set | Hayır | Tekilleştirme |
(ifade for x in it) | generator | Hayır | sum/max/any'ye akış |
for x in it: | (yan etki) | Evet, son değer kalır | Birden çok iş, try/except, break |
İki boyutlu listeler: paylaşılan satır tuzağı
Depo raf düzeni, koltuk planı, haftalık vardiya tablosu gibi ızgaraları (grid) liste listesiyle tutarsın. [[0] * 3] * 2 yazmak masum görünür ama dış çarpım aynı iç listeyi iki kez koyar; bir hücreyi değiştirince tüm satırlarda değişir. Doğrusu her satırı comprehension ile ayrı oluşturmaktır.
yanlis = [[0] * 3] * 2
yanlis[0][0] = 9
print("Yanlış:", yanlis) # iki satır da değişti!
dogru = [[0] * 3 for _ in range(2)]
dogru[0][0] = 9
print("Doğru :", dogru)
# Devrik (transpose): satırlar sütun olur
satis = [[10, 20, 30], # Ankara: Oca, Şub, Mar
[5, 15, 25]] # İzmir
aylik = [[satir[ay] for satir in satis] for ay in range(3)]
print("Aya göre:", aylik)
print("zip ile :", [list(s) for s in zip(*satis)])
Dikkat: [[satir[ay] for satir in satis] for ay in range(3)] içiçe iki ayrı comprehension'dır ve 2 boyutlu sonuç üretir; [x for satir in m for x in satir] ise tek comprehension'da iki for'dur ve düzleştirir. Köşeli parantezlerin yeri her şeyi değiştirir.
Sözlükleri dönüştürmek
Sözlük comprehension'ı .items() ile birlikte en çok üç iş için kullanılır: değerleri dönüştürmek, öğeleri süzmek ve anahtar-değeri ters çevirmek. Ters çevirirken iki anahtarın değeri aynıysa son gelen kazanır; veri sessizce kaybolur. Çakışma olabilecekse grupla.
fiyat = {"KLV": 1249.0, "MOU": 349.5, "KUL": 899.9, "KBL": 79.9}
zamli = {k: round(v * 1.1, 2) for k, v in fiyat.items()}
ucuz = {k: v for k, v in fiyat.items() if v < 500}
print(zamli)
print(ucuz)
kategori = {"KLV": "çevre", "MOU": "çevre", "KUL": "ses", "KBL": "kablo"}
ters = {v: k for k, v in kategori.items()}
print("Ters (kayıp var):", ters)
gruplu = {kat: sorted(k for k, v in kategori.items() if v == kat)
for kat in set(kategori.values())}
print("Gruplu:", dict(sorted(gruplu.items())))
for + defaultdict(list) kullan (collections dersi). Comprehension kısa yazmak içindir, her zaman en hızlı yol değildir.enumerate, zip ve birden çok koşul
Sıra numarası lazımsa enumerate, iki listeyi eşleştireceksen zip comprehension'ın içinde de çalışır. Arka arkaya birden çok if yazmak and ile aynıdır; okunabilirlik için uzun koşulu bir yardımcı fonksiyona taşı.
musteriler = ["Ayşe", "Mehmet", "Elif", "Can"]
puanlar = [92, 61, 78, 45]
liste = [f"{no}. {ad} ({p})" for no, (ad, p) in enumerate(zip(musteriler, puanlar), 1) if p >= 60 if p < 90]
print(liste)
def vip_mi(ad, puan):
return puan >= 90 or ad.startswith("A")
print([ad for ad, p in zip(musteriler, puanlar) if vip_mi(ad, p)])
for'dan fazlası varsa ya da içinde yan etki (print, append, dosyaya yazma) varsa normal döngüye dön. Takım arkadaşın onu üç saniyede okuyabilmeli. API'den gelen JSON listesini {k["id"]: k for k in kayitlar} ile id → kayıt arama tablosuna çevirmek ise en çok kullanılan ve herkesin hemen tanıdığı kalıptır; bir listede tekrar tekrar arama yapmaktan çok daha hızlıdır.Comprehension ve performans
Liste comprehension, eşdeğer for+append döngüsünden genellikle biraz daha hızlıdır; çünkü append metodunu her turda aramak yerine yorumlayıcı özel bir "listeye ekle" komutu kullanır. Ama bu fark küçüktür ve seçim nedenin hız değil okunurluk olmalıdır. Asıl büyük fark, liste ile üreteç ifadesi arasındadır: milyon satırlık bir kaynakta sum([...]) önce milyon öğelik liste kurar, sum(...) ise tek tek üretip toplar. Büyük veride üreteç ifadesini varsayılan yap; listeye yalnız sonucu birden çok kez kullanacaksan çevir.
İş hayatında nasıl görünür?
Kod incelemelerinde (code review) comprehension'ın en sık iki eleştirisi vardır: "bu satır çok uzun, böl" ve "burada liste yerine üreteç yeterdi". Bir de ters yönde: "burada comprehension yazılabilirdi" diye işaretlenen üç satırlık append döngüleri. Her ikisini de otomatik tarayan ruff ve pylint gibi araçlar vardır; ekipler çoğu zaman satır uzunluğunu 88 ya da 100 karakterle sınırlar. İleride pandas öğrendiğinde comprehension'ların yerini büyük ölçüde sütun işlemleri alacak; ama JSON yanıtlarını, API verisini ve konfigürasyon listelerini şekillendirirken comprehension hep elinin altında olacak.