Python liste üreteçleri (list comprehension), bir listeyi tek satırda oluşturmanıza, filtrelemenize ve dönüştürmenize yarayan kısa bir sözdizimidir. Klasik for döngüsüyle yazılan birkaç satırlık kodu [ifade for eleman in kaynak] kalıbıyla tek satıra indirir. Sonuç, döngüyle oluşturduğunuz listenin aynısıdır; fark sadece yazım şeklinde ve genellikle performansta kendini gösterir. Bu yazıda liste üreteçlerinin yapısını, koşullu filtrelemeyi ve dönüştürme ile filtrelemenin birlikte kullanımını örneklerle inceleyeceğiz.
Liste üreteci nedir, neyi çözer?
Bir sayı listesindeki her elemanın karesini almak istediğinizi düşünün. Klasik for döngüsüyle bu işlem şöyle yazılır:
sayilar = [1, 2, 3, 4, 5]
kareler = []
for sayi in sayilar:
kareler.append(sayi ** 2)
print(kareler) # [1, 4, 9, 16, 25]
Burada üç ayrı adım var: boş bir liste oluşturmak, döngüyü çalıştırmak ve her sonucu append ile listeye eklemek. Liste üreteci bu üç adımı tek satırda birleştirir:
sayilar = [1, 2, 3, 4, 5]
kareler = [sayi ** 2 for sayi in sayilar]
print(kareler) # [1, 4, 9, 16, 25]
İki kod parçası da aynı sonucu üretir. Liste üreteci, boş liste tanımlama ve append çağrısı gibi tekrarlayan kısımları ortadan kaldırdığı için kodu daha kısa ve okunması genelde daha hızlı hale getirir. Ayrıca Python içeride bu döngüyü optimize ettiğinden, büyük veri kümelerinde klasik döngüden biraz daha hızlı çalışabilir.
Yapının üç parçası
Bir liste üretecinin genel kalıbı şu şekildedir:
[ifade for eleman in kaynak if koşul]
Bu kalıp üç (aslında dördüncüsü isteğe bağlı olmak üzere) temel parçadan oluşur:
- İfade: Listeye eklenecek değeri belirler.
sayi ** 2örneğinde olduğu gibi, her elemana uygulanan dönüşümdür. İfade sabit bir değer, bir fonksiyon çağrısı ya da matematiksel bir işlem olabilir. for eleman in kaynak: Klasik döngüdeki döngü satırının karşılığıdır.kaynakherhangi bir yinelenebilir (iterable) nesne olabilir: liste, tuple, string, range veya bir sözlüğün anahtarları.- İsteğe bağlı
ifkoşulu: Sadece belirli şartı sağlayan elemanların listeye dahil edilmesini sağlar. Bu kısım yazılmazsa kaynaktaki tüm elemanlar işlenir.
Bu üç parçanın sırası sabittir: önce ifade, sonra döngü, en son (varsa) koşul gelir. Sıra değiştirilemez.
Koşullu filtreleme ekleme
Bir listeden sadece belirli koşulu sağlayan elemanları seçmek istediğinizde if koşulu devreye girer. Örneğin bir sayı listesinden sadece çift sayıları almak isteyelim. Klasik döngüyle:
sayilar = [1, 2, 3, 4, 5, 6, 7, 8]
ciftler = []
for sayi in sayilar:
if sayi % 2 == 0:
ciftler.append(sayi)
print(ciftler) # [2, 4, 6, 8]
Aynı işlem liste üreteciyle:
sayilar = [1, 2, 3, 4, 5, 6, 7, 8]
ciftler = [sayi for sayi in sayilar if sayi % 2 == 0]
print(ciftler) # [2, 4, 6, 8]
Burada ifade kısmı (sayi) elemanı değiştirmeden bırakıyor, if sayi % 2 == 0 koşulu ise sadece çift sayıların listeye girmesini sağlıyor. Koşulu sağlamayan elemanlar sessizce atlanır, herhangi bir hata oluşmaz. Bu yapı, bir listeyi belirli bir kurala göre süzmek istediğinizde filter() fonksiyonuna göre daha okunaklı bir alternatif sunar.
Filtreleme ve dönüştürmeyi birlikte kullanma
Liste üretecinde iki farklı if kullanımı var ve bunları birbirine karıştırmak çok yaygın bir hata. Biri filtreleme yapar, diğeri değer seçer. İkisi aynı yerde durmuyor.
Filtre if'i ifadenin sonunda yer alır ve hangi elemanların listeye gireceğine karar verir:
sayilar = [1, 2, 3, 4, 5, 6]
ciftler = [n for n in sayilar if n % 2 == 0]
# [2, 4, 6]
Burada if n % 2 == 0 koşulunu sağlamayan elemanlar listeye hiç girmiyor. Yani bu satır bir "eleme" işlemi.
Koşullu ifade (ternary) ise ifade kısmının içinde durur ve her eleman için bir değer üretir, hiçbir elemanı elemez:
etiketler = ["çift" if n % 2 == 0 else "tek" for n in sayilar]
# ['tek', 'çift', 'tek', 'çift', 'tek', 'çift']
Burada liste hep aynı uzunlukta kalıyor, sadece her elemanın karşılığı değişiyor. Fark şu: filtre if'i eleman sayısını değiştirir, ternary if/else eleman sayısını değiştirmeden değeri dönüştürür. İkisini aynı anda da kullanabilirsiniz:
sonuc = ["çift" if n % 2 == 0 else "tek" for n in sayilar if n > 2]
# önce n > 2 filtresi uygulanır, sonra kalanlar etiketlenir
Bu satırı okurken sırayı doğru kurmak önemli: önce filtre çalışır, sonra ifade kısmındaki dönüştürme her kalan eleman için uygulanır.
İç içe yapıların sınırı
Liste üreteçleri iç içe döngüleri de tek satırda ifade edebilir, ama bu noktada okunabilirlik hızla bozulmaya başlar. Klasik örnek, bir matrisi (liste listesi) düz bir listeye çevirmek:
matris = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
duz_liste = [sayi for satir in matris for sayi in satir]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
Bu satır, klasik for döngüsündeki dış-iç sırayı aynen takip eder:
duz_liste = []
for satir in matris:
for sayi in satir:
duz_liste.append(sayi)
Tek seviye iç içe yapı için bu hâlâ takip edilebilir. Ama buna bir filtre veya ikinci bir iç içe seviye eklediğinizde satır hem uzar hem de okuyanın zihninde döngü sırasını tersten çözmesini gerektirir:
duz_liste = [sayi for satir in matris for sayi in satir if sayi % 2 == 0]
Bu hâlâ çalışır ama artık "önce hangi döngü, sonra hangi koşul" sorusunu cevaplamak için satırı iki kez okumak gerekiyor. İki seviyeden fazla iç içe yapı veya birden fazla koşul eklediğinizde liste üreteci, çözmeye çalıştığı okunabilirlik sorununu tersine çevirip yeni bir okunabilirlik sorunu yaratmaya başlar.
Liste üreteci ne zaman kullanılmamalı?
Liste üreteci her durumda doğru araç değil. Üç durumda klasik for döngüsüne dönmek daha sağlıklı:
Yan etkili işlemler. Liste üreteci bir liste üretmek içindir; ekrana yazdırma, dosyaya yazma veya bir API çağrısı yapma gibi yan etkiler için kullanılmamalı. [print(x) for x in liste] çalışır ama gereksiz bir liste nesnesi oluşturur ve amacı belirsizleştirir. Bu tür durumlarda düz bir for döngüsü hem daha açık hem daha doğrudur.
Çok adımlı mantık. İfade kısmında birden fazla işlem, iç içe koşul veya ara değişken gerekiyorsa liste üreteci sıkışır. Mantığı birkaç satıra yaymak isteyen bir işlem, tek satıra zorlandığında okunması değil çözülmesi gereken bir bilmeceye dönüşür.
Okunabilirlik kaybı. Bir liste üreteci satırını anlamak için ikinci kez okumanız gerekiyorsa, o satır artık zaman kazandırmıyor. Bu durumda for döngüsüne dönmek kod kalitesinden ödün vermek değil, doğru aracı seçmektir.
Özet
Liste üreteci, bir listeyi tek satırda ve okunabilir şekilde oluşturmak için üç parçadan oluşur: sonuçta neyin üretileceğini belirten ifade, elemanların nereden geleceğini gösteren döngü ve isteğe bağlı olarak hangi elemanların dahil edileceğini belirleyen koşul. Basit bir dönüştürme yapılacaksa sadece ifade ve döngü yeterlidir; belirli elemanları elemek gerekiyorsa sona bir if koşulu eklenir; her ikisi birden gerekiyorsa dönüştürme ifadesi ile filtreleme koşulu aynı satırda birleştirilir. Buradaki karar kriteri karmaşıklıktır: mantık tek bir dönüştürme veya tek bir filtrelemeden ibaretse liste üreteci hem daha kısa hem daha okunabilir bir çözümdür. Ancak iç içe döngü sayısı ikiyi geçiyorsa, koşullar birbirine bağımlı hale geliyorsa veya her adımda ayrı hata yönetimi gerekiyorsa, klasik for döngüsüne geçmek kodu daha anlaşılır kılar. Kısacası liste üreteci bir kısayol değil, belirli bir okunabilirlik sınırı içinde çalışan bir araçtır; bu sınırı zorlamak, kazandığı avantajı tersine çevirir.
Sık Sorulan Sorular
Liste üreteci ile map/filter fonksiyonları arasında fark var mı?
İkisi de benzer işi yapar ama liste üreteci genellikle daha okunaklıdır çünkü dönüştürme ve filtreleme mantığını tek satırda, ek fonksiyon tanımlamadan gösterir. map ve filter kullanmak bazı durumlarda lambda ifadeleriyle birleştiğinde daha az anlaşılır hale gelebilir.
Liste üreteci büyük veri kümelerinde performans sorunu yaratır mı?
Liste üreteci tüm sonucu bellekte tutar, bu yüzden çok büyük veri kümelerinde bellek tüketimi artabilir. Bu durumda parantez yerine köşeli parantez kullanmadan yazılan generator ifadesi tercih edilerek elemanlar tek tek üretilebilir.
Liste üreteci içinde fonksiyon çağırmak sakıncalı mı?
Hayır, ifade kısmına bir fonksiyon çağrısı yazılabilir. Ancak fonksiyon karmaşık bir işlem yapıyorsa veya hata fırlatma ihtimali varsa, bu mantığı üreteç dışında ayrı bir fonksiyonda tutmak hata ayıklamayı kolaylaştırır.
Sözlük veya küme için de benzer bir yapı kullanılabilir mi?
Evet, aynı mantık süslü parantezlerle sözlük ve küme üreteçlerine de uygulanır. Söz dizimi neredeyse aynıdır; sadece anahtar-değer çifti veya tekil değer üretimi farklılaşır.
Liste üreteçleri, doğru yerde kullanıldığında Python kodunu daha derli toplu hale getiren pratik bir araçtır; önemli olan ne zaman avantaj sağladığını ve ne zaman okunabilirliği zorladığını ayırt edebilmektir.