Soru Tarihi
Tembel işleme (lazy evaluation) programlamada, hesaplamaların kodun sonucu ihtiyaç duyulana kadar ertelendiği bir tekniktir. Python dilinde bu paradigma, jeneratörler ve itertools gibi standart kütüphanedeki özel fonksiyonlar sayesinde popülarite kazanmıştır. Bu tür teknikler başlangıçta fonksiyonel dillerden gelmiştir, ancak Python, tembel işleme için yerel ve üçüncü taraf araçlar sunar.
Sorun
Geleneksel işleme (eager processing) tüm verilerin birden yüklenmesini ve hesaplanmasını gerektirir (örneğin, liste ifadeleri kullanıldığında), bu da büyük veya sonsuz dizilerle çalışırken yüksek bellek tüketimi ve performans düşüklüğüne yol açabilir. Tembel işleme, kaynak israfını önleyerek yalnızca gerektiği kadar “yükleme” ve elemanları işleme imkanı tanır.
Çözüm
Python'da tembel işleme yalnızca jeneratörler (yield) aracılığıyla değil, aynı zamanda itertools modüllerindeki özel tembel fonksiyonlar, map, filter gibi standart fonksiyonlar ve jeneratör ifadeleri türündeki nesneler aracılığıyla da uygulanmaktadır. Örneğin, map() fonksiyonu yalnızca ihtiyaç duyulduğunda değerleri hesaplayan tembel bir iteratör döndürür:
# Tembel işleme örneği: her sayıyı kareye yükseltme squares = map(lambda x: x ** 2, range(10**10)) # liste için bellek harcanmıyor print(next(squares)) # 0 print(next(squares)) # 1
Ana Özellikler:
Python'daki map() fonksiyonu her zaman tembel işleme mi gerçekleştirir? Hangi standart fonksiyonların tembel olduğunu nasıl öğrenebilirim?
Hayır, Python 3 itibarıyla map(), filter(), zip() fonksiyonları iteratör döndürür, yani tembel işleme gerçekleştirir. Python 2'de bu fonksiyonlar liste döndürüyordu. Bir nesnenin tembel olup olmadığını öğrenmek için türüne bakmak veya belgeleri incelemek gerekir:
result = map(lambda x: x+1, range(5)) print(type(result)) # 'map' — bu bir iteratördür
Sum() fonksiyonu içerisinde jeneratör ifadesi kullanıldığında tembel işleme işe yarar mı?
sum() fonksiyonu, iteratörün sonuna kadar geçmek zorundadır. Jeneratör ifadesi kendisi tembel olsa da, sum() sonuç olarak tüm diziyi tüketir:
s = sum(x**2 for x in range(1000000)) # jeneratör tamamen tüketilir
Tembel işleme, normal listeler ve demetler üzerinde, örneğin map/lambda ile uygulanabilir mi?
Evet, uygulanabilir, ancak listeler ve demetler yine de bellekte yüklenmiştir. map, bunlar üzerinde tembel bir iteratör döndürecektir, ancak orijinal veriler hala tamamen bellektedir. Tam anlamıyla tembel bir zincir oluşturmak için her aşamada jeneratörlerle çalışmak tercih edilmelidir:
def gen(): for i in range(1, 100): yield i squares = map(lambda x: x**2, gen()) # tamamen tembel
map() aracılığıyla) beklenmedik veri kaybına yol açarlist()) dönüştürmek, bellek tasarrufunu geçersiz kılarBir geliştirici, jeneratör ifadesi kullanarak dizeleri filtrelemek için büyük bir günlük dosyası için işleme yazıyor, ancak yanlışlıkla hemen bir listeye dönüştürüyor:
with open('biglog.txt') as f: important_lines = [line for line in f if 'ERROR' in line] # tüm dosyayı yüklüyor
Artılar:
Eksiler:
Başka bir ekip, bir jeneratör ifadesi kullanarak tembel bir yaklaşım benimsemiş ve dizeleri alındıkça işliyor:
with open('biglog.txt') as f: for line in (l for l in f if 'ERROR' in l): process(line)
Artılar:
Eksiler: