ProgramlamaBackend geliştirici

Python'da jeneratör ifadeleri (generator expressions) nedir, jeneratör fonksiyonlarından ve list comprehension'lardan ne farkı vardır ve en uygun nerelerde kullanılır?

Hintsage yapay zeka asistanı ile mülakatları geçin

Cevap

Tarihçe

Python, 2.4 sürümünden itibaren, liste kapsamlarını (list comprehensions) "jeneratör ifadeleri" ile genişletmiştir. Bu, jeneratörlere benzer şekilde tembel bir değer dizisi oluşturmaya olanak tanır, ancak daha kompakt ve okunabilir bir biçimde.

Sorun

Liste kapsamları ([x for x in iterable]) tüm öğeleri hemen belleğe yükleyerek bir liste oluşturur. Bu, öğe sayısı çok fazla olduğunda etkisiz veya tehlikeli olabilir. Jeneratör fonksiyonları (yield kullanarak) daha esnektir, ancak ayrı bir fonksiyon tanımlamasını ve daha fazla kod satırı gerektirir.

Çözüm

Jeneratör ifadeleri ((x for x in iterable)) tembel diziler üretmek için kısa bir sözdizimi sunar (öğeler gerektiği ölçüde hesaplanır, hepsi birden yüklenmez). Liste kapsamlarına benzer görünürler ama parantez kullanır:

# Liste kapsamı her şeyi belleğe yükler squares_list = [x**2 for x in range(10**6)] # Jeneratör ifadesi: öğeler talep edildiğinde gelir, bellek neredeyse kullanılmaz squares_gen = (x**2 for x in range(10**6)) # Jeneratörden ilk beş değeri almak for _ in range(5): print(next(squares_gen))

Anahtar özellikler:

  • Jeneratör ifadeleri tüm koleksiyonu bir anda belleğe yüklemez
  • Herhangi bir yinelemeli nesne gereken yerlerde kullanılır (örneğin, sum(), max(), any() fonksiyonlarında)
  • Sözdizimi kısadır ve ayrı bir fonksiyon tanımlanmasını gerektirmez

Yanıltıcı Sorular.

Aynı jeneratör ifadesinden birden çok kez "geçebilir miyiz"?

Hayır, bir kez yineleme yaptıktan sonra jeneratör "tükenir". Yeniden geçiş yapmak için yeni bir jeneratör oluşturmanız veya liste kapsamını kullanmanız gerekir.

it = (x for x in range(3)) print(list(it)) # [0,1,2] print(list(it)) # [] — artık değerler elde edilemez

Jeneratörler kullanımlar arasında durumlarını korur mu?

Evet, jeneratör ifadesi next() (veya her tekrar sırasında) arasında "pozisyonunu" korur, ancak yeni bir nesne oluşturmadıkça başa döndürülemez.

Bir jeneratör ifadesini bir satırda birden çok kez kullanmak mümkün mü?

Hayır! Eğer jeneratörü aynı anda birden çok yere "paketlerseniz" (örneğin, birden çok fonksiyona eşzamanlı olarak, listenin geri dönmesini sağlamadan), bazı veriler kaybolur — her alt kullanım işaretçiyi ileri taşır.

g = (x for x in range(3)) print(sum(g), list(g)) # sum(g) tümünü alır, list(g) boş kalır

Tipik Hatalar ve Anti-Paternerler

  • Jeneratör ifadelerinin, tüm koleksiyonun gerektiği durumlarda kullanılmasının bir kez kullanım yarattığı; bu durumda verilerin kaybolmasına yol açar
  • "Tükenmiş" bir jeneratörün yerine yeni bir jeneratör verilmesi (boş bir koleksiyon alırsınız)

Gerçek Hayattan Bir Örnek

Olumsuz Durum

Büyük dosyaların analizi için projede kullanıldı:

data = (parse_line(line) for line in file) process(list(data)) other_process(list(data))

Artılar:

  • Herhangi bir veri için kodu kolayca değiştirmek mümkün

Eksiler:

  • İlk list(data) çağrısından sonra jeneratör tükeniyor, veriler sadece ilk işleyiciye geliyor, ikincisi hiçbir şey almıyor

Olumlu Durum

Verilerin yeniden kullanımının gerektiği durumlarda liste kapsamı kullanıldı veya tek seferlik tüketim için bir jeneratör oluşturuldu:

# Jeneratör sadece bir kez analiz için (örneğin, toplamı hesaplamak) total = sum(parse_line(line) for line in file)

Artılar:

  • Bellek tasarrufu, kodun basitliği

Eksiler:

  • Jeneratörü yeniden oluşturmadan verileri kullanmak mümkün değil