Ana içeriğe geç
Kesit Python
Orta · Pythonic kod

Comprehension'lar (liste, sözlük, küme kurma): tek satırda dönüştür ve süz

Liste, sözlük ve küme comprehension ile üreteç ifadesini öğrenir; koşullu ve iç içe biçimlerini kullanır; ne zaman klasik döngüye dönmen gerektiğini anlarsın.

Uygulamada aç, 23 alıştırmayı çözÜcretsiz, hesap gerekmez, kurulum yok.

Bu sayfa dersin okuma sürümüdür. Metin bir kutuyu çalıştırmanı ya da adıma geçmeni söylüyorsa dersi uygulamada aç.

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)
En sık karışan noktaSondaki 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çimSözdizimiSonuçTipik iş
Liste[f(x) for x in k]list, hemen kurulurDönüştürülmüş liste gerekince
Sözlük{a: b for x in k}dictListeyi anahtarla erişilir yapmak
Küme{f(x) for x in k}set, tekrarsızBenzersiz değerler
Üreteç ifadesi(f(x) for x in k)generator, tembelsum/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:

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)
Kural: niyet tek nefeste okunmalıComprehension'ı yazdıktan sonra yüksek sesle oku. "Geçerli siparişlerin temizlenmiş halinden no-değer sözlüğü" diyebiliyorsan iyidir. Nefesin yetmiyorsa parçala.
Değişken sızması yokPython 3'te comprehension'ın döngü değişkeni (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ş

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)
Parantez şartif 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]listHayırDönüştürülmüş yeni liste
{k: v for x in it}dictHayırArama tablosu, indeks
{ifade for x in it}setHayırTekilleştirme
(ifade for x in it)generatorHayırsum/max/any'ye akış
for x in it:(yan etki)Evet, son değer kalırBirden ç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())))
Gruplu sürümün maliyetiSon örnek her kategori için tüm sözlüğü yeniden tarar (n × k işlem). Küçük veride sorun değil; büyük veride tek geçişli 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)])
Gerçek işteKod incelemelerinde (code review) comprehension'lar için yaygın kural şudur: tek satıra sığmıyorsa, iki 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.
Uzmanlaş

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.

Alıştırmalara geç (23)