# Python'da üreteçler (generator): yield, yineleyiciler ve liste, sözlük üretimleri

URL: https://skillaria.top/tr/guides/python/python-generatorler

Python'da üreteç (generator), değerleri tek tek ve istendikçe üreten bir nesnedir; tüm diziyi bellekte tutmaz. Yield anahtar sözcüğüyle yazılan bir fonksiyonla ya da parantez içindeki bir üreteç ifadesiyle oluşturulur. Liste, sözlük ve küme üretimleri (comprehension) hazır bir koleksiyonu tek satırda kurar: [x * 2 for x in xs], {k: v for k, v in pairs}, {x for x in xs}.

Python'da üreteç (generator), değerleri birer birer ve yalnızca istendiğinde üreten bir nesnedir; tüm diziyi bellekte tutmaz. Üreteçleri iki şekilde oluşturursunuz: `yield` içeren bir fonksiyonla ya da parantez içinde bir üreteç ifadesiyle. Bunların yanında liste, sözlük ve küme üretimleri (comprehension) de bulunur; bunlar hazır bir koleksiyonu tek satırda kurmanın kısa yoludur. Bu yazıda python yield'ın nasıl çalıştığını, üreteç ile yineleyici (iterator) arasındaki farkı ve comprehension'ları okunaklı tutmanın yolunu inceliyoruz.

## Yineleyiciler: for döngüsünün temeli

Üreteçleri anlamak için önce yineleyicileri bilmek gerekir. Yinelenebilir nesne (iterable), üzerinde `for` ile dolaşılabilen şeydir: liste, metin, sözlük, dosya. Yineleyici (iterator) ise mevcut konumunu hatırlayan ve her `next()` çağrısında sıradaki elemanı veren nesnedir; elemanlar bittiğinde `StopIteration` fırlatır.

```
nums = [10, 20, 30]
it = iter(nums)      # listeden bir yineleyici al
print(next(it))      # 10
print(next(it))      # 20
print(next(it))      # 30
next(it)             # StopIteration
```

`for` döngüsü tam olarak bunu yapar: `iter()`'i çağırır, ardından `StopIteration` gelene kadar `next()`'i. Kendi yineleyicinizi `__iter__` ve `__next__` metotlarına sahip bir sınıfla yazabilirsiniz:

```
class Countdown:
    def __init__(self, start):
        self.current = start

    def __iter__(self):
        return self

    def __next__(self):
        if self.current  0:
        yield start
        start -= 1

gen = countdown(3)
print(gen)          #
print(next(gen))    # 3
print(list(gen))    # [2, 1] — durulan yerden devam etti
```

Fonksiyon sonuna geldiğinde (ya da `return` ile çıktığında) üreteç kendiliğinden `StopIteration` fırlatır ve `for` döngüsü düzgünce biter. Yani `yield` ile `return` arasındaki fark şudur: `return` fonksiyonu bitirir ve tek bir değer döndürür; `yield` fonksiyonu duraklatır ve istediğiniz kadar değer verebilir.

### Üreteç ile yineleyici arasındaki farklar

Her üreteç bir yineleyicidir: `__iter__` ve `__next__` metotlarına sahiptir. Ama her yineleyici üreteç değildir. Fark, oluşturma biçiminde ve sunduğu imkânlarda.

|  | Yineleyici (sınıf) | Üreteç |
| --- | --- | --- |

| Nasıl oluşturulur | `__iter__` ve `__next__` olan sınıf | `yield` içeren fonksiyon ya da `(x for x in ...)` |

| Durum (state) | elle, öznitelikler içinde tutulur | yerel değişkenlerde otomatik saklanır |

| StopIteration | sizin fırlatmanız gerekir | fonksiyon bitince kendisi fırlatır |

| Ek özellikler | yazdıklarınız kadar | `send()`, `throw()`, `close()` |

İkisinin ortak önemli özelliği: yalnızca bir kez dolaşılabilirler. Tükenmiş bir üreteç üzerinde tekrar `for` döngüsü çalıştırırsanız hiçbir şey gelmez; yeni bir üreteç oluşturmanız gerekir.

### Sonsuz üreteç ve büyük dosyalar

Üreteç bitmek zorunda değildir. Sonsuz bir dizi, yalnızca sınırlı sayıda eleman alıyorsanız güvenlidir:

```
from itertools import islice

def ids():
    n = 1
    while True:
        yield f"ID-{n:04d}"
        n += 1

print(list(islice(ids(), 3)))  # ['ID-0001', 'ID-0002', 'ID-0003']
```

En pratik kullanım, büyük bir dosyayı satır satır işlemektir. Üreteçler zincirlenerek bir boru hattı (pipeline) kurulabilir; bellekte her an yalnızca bir satır bulunur, tüm dosya değil:

```
def read_lines(path):
    with open(path, encoding="utf-8") as f:
        for line in f:
            yield line.rstrip("\n")

def only_errors(lines):
    for line in lines:
        if "ERROR" in line:
            yield line

for line in only_errors(read_lines("app.log")):
    print(line)
```

### yield from ve send

`yield from`, başka bir yinelenebilir nesnenin tüm değerlerini aktarır; iç içe döngüye gerek kalmaz:

```
def flatten(groups):
    for g in groups:
        yield from g

print(list(flatten([[1, 2], [3], [4, 5]])))  # [1, 2, 3, 4, 5]
```

`send()` metodu üreteç içine bir değer gönderir; bu değer `yield` ifadesinin sonucu olur. İlk `send`'den önce üreteci `next()` ile başlatmak gerekir. Örneğin hareketli ortalama hesaplayan bir üreteç böyle yazılır:

```
def averager():
    total, count = 0, 0
    avg = None
    while True:
        value = yield avg
        total += value
        count += 1
        avg = total / count

a = averager()
next(a)            # ilk yield'e kadar ilerle
print(a.send(10))  # 10.0
print(a.send(20))  # 15.0
```

Uygulama kodunda `send` nadiren kullanılır, ama mülakatlarda sık sorulur.

### range bir üreteç midir?

Hayır. `range` tembel (lazy) bir dizidir: o da tüm sayıları tutmaz, ama indeksleme, `len()`, `in` kontrolü destekler ve istediğiniz kadar dolaşılabilir. Üreteç bunların hiçbirini yapamaz.

```
r = range(5)
print(r[2], len(r), 3 in r)  # 2 5 True
print(list(r), list(r))      # ikisinde de [0, 1, 2, 3, 4]
```

## Liste üretimi: list comprehension

Liste üretimi (list comprehension), `[ifade for x in koleksiyon]` biçimindedir. Yeni bir liste oluşturur ve kaynağı değiştirmez. Soldan sağa okunur: önce "neyi koyacağız", sonra "nereden alacağız".

```
amounts = [100, 250, 400]

# normal döngü
with_markup = []
for a in amounts:
    with_markup.append(a * 1.2)

# aynısı comprehension ile
with_markup = [a * 1.2 for a in amounts]
print(with_markup)  # [120.0, 300.0, 480.0]
```

Türkçede "liste üreteci" olarak adlandırılsa da sonuç hazır bir listedir, üreteç değildir: tüm elemanlar hemen hesaplanır.

### if ve if else ile liste üretimi

En çok karıştırılan konu budur. Filtre koşulu sona yazılır ve elemanları eler. Seçim koşulu (üçlü operatör) başa yazılır ve her elemanı iki değerden birine dönüştürür; hiçbir şeyi atmaz.

```
amounts = [100, 250, 400]

big = [a for a in amounts if a > 200]                    # [250, 400]
labels = ["çok" if a > 200 else "az" for a in amounts]
# ['az', 'çok', 'çok']

# filtre ve seçim birlikte
odd_marks = ["çift" if n % 2 == 0 else "tek" for n in range(6) if n > 2]
# ['tek', 'çift', 'tek']
```

`[a for a in amounts if a > 200 else 0]` yazımı sözdizimi hatasıdır: `else` yalnızca başta yazılan koşulla birlikte kullanılır.

### İç içe liste üretimi ve iki boyutlu liste

Çift `for`, iç içe döngülerle aynı sırada yazılır, yukarıdan aşağıya. Liste içindeki listeleri birleştirirken sırayı karıştırmamanızı sağlar:

```
groups = [[1, 2], [3], [4, 5]]
flat = [x for g in groups for x in g]
# eşdeğeri:
# for g in groups:
#     for x in g:
#         flat.append(x)
print(flat)  # [1, 2, 3, 4, 5]
```

İki boyutlu bir liste (matris) için comprehension içinde comprehension kullanılır. Klasik hata, listeyi çarparak satır oluşturmaktır: satırlar aynı nesne olur.

```
bad = [[0] * 3] * 2
bad[0][0] = 1
print(bad)   # [[1, 0, 0], [1, 0, 0]] — iki satır da değişti

grid = [[0] * 3 for _ in range(2)]
grid[0][0] = 1
print(grid)  # [[1, 0, 0], [0, 0, 0]]

# transpoz
m = [[1, 2, 3], [4, 5, 6]]
print([[row[i] for row in m] for i in range(3)])  # [[1, 4], [2, 5], [3, 6]]
```

## Sözlük ve küme üretimi

Aynı yöntem diğer koleksiyonlar için de geçerlidir. İki nokta üst üste içeren süslü parantez bir sözlük (dict comprehension), içermeyen süslü parantez ise bir küme (set comprehension) oluşturur.

```
spent = [("yiyecek", 500), ("taksi", 300), ("yiyecek", 200)]

categories = {cat for cat, _ in spent}
print(categories)  # {'yiyecek', 'taksi'} — kümenin sırası garanti değildir

last = {cat: amount for cat, amount in spent}
print(last)        # {'yiyecek': 200, 'taksi': 300}

squares = {n: n ** 2 for n in range(4)}  # {0: 0, 1: 1, 2: 4, 3: 9}
swapped = {v: k for k, v in squares.items()}
```

`last` örneğine dikkat edin: anahtar tekrar ettiğinde son değer kalır, 500'lük harcama kaybolur. Toplama yapmanız gerekiyorsa comprehension uygun değildir; döngü yazın ya da `collections.Counter` / `defaultdict` kullanın.

### Demet üretimi var mı?

Python'da ayrı bir "tuple comprehension" yoktur. Parantez içindeki ifade bir üreteç ifadesi olur; demeti ise bunu `tuple()`'a vererek elde edersiniz:

```
t = tuple(n * 2 for n in range(3))  # (0, 2, 4)
```

## Üreteç ifadesi: saklamadan üretmek

Köşeli parantezleri yuvarlak parantezle değiştirirseniz bir üreteç ifadesi elde edersiniz. Değerleri önceden hesaplamaz, istendikçe verir. Bir milyon eleman için bellek farkı çok büyüktür:

```
import sys

as_list = [n for n in range(1_000_000)]
as_gen = (n for n in range(1_000_000))
print(sys.getsizeof(as_list))  # yaklaşık 8 MB
print(sys.getsizeof(as_gen))   # birkaç yüz bayt
```

Üreteç ifadeleri çoğunlukla toplayıcı fonksiyonların içinde yaşar. Üreteç tek argümansa çevresindeki parantezleri yazmanız gerekmez. `any()` ve `all()` ilk cevabı bulunca durur, kalanı taramaz.

```
spent = [("yiyecek", 500), ("taksi", 300), ("yiyecek", 200)]

total = sum(a for _, a in spent)            # 1000
has_big = any(a > 400 for _, a in spent)    # True, 500'den sonrasına bakmaz
biggest = max(spent, key=lambda p: p[1])    # ('yiyecek', 500)
```

Seçim kuralı basittir: sonuç birden fazla kez gerekiyorsa, indeksleme ya da `len()` lazımsa liste kullanın. Bir kez dolaşıp bir şey hesaplayacaksanız üreteç kullanın.

## Kapsam: değişken dışarı sızmaz

Deneyimli geliştiricileri bile yanıltan bir durum:

```
x = 10
squares = [x for x in range(3)]
print(x)  # 10
```

Comprehension içindeki döngü değişkeni kendi kapsamında yaşar ve dıştaki değişkeni ezmez (Python 3'te böyledir; Python 2'de farklıydı). Normal bir `for` döngüsünde ise döngüden sonra `x` 2 olurdu. Python 3.12'den itibaren (PEP 709) comprehension'lar çevreleyen fonksiyona gömülerek daha hızlı çalışır, ama görünürlük kuralı aynı kalmıştır.

## Python 3.15'te comprehension içinde * ve ** (PEP 798)

Açma (unpacking) için yıldız işareti fonksiyon çağrılarında ve liste literallerinde uzun süredir kullanılabiliyordu; ama comprehension içinde bu bir `SyntaxError` idi. Python 3.15'te bu kısıt kaldırıldı: `*öğe`, öğenin tüm elemanlarını oraya yerleştirmek anlamına gelir. Liste, küme ve üreteçlerde çalışır; asenkron olanlar dahil.

```
# Python 3.15+
groups = [[1, 2], [3], [4, 5]]
flat = [*g for g in groups]        # [1, 2, 3, 4, 5]
uniq = {*g for g in groups}        # küme
lazy = (*g for g in groups)        # tembel üreteç
```

Çift yıldız da sözlükler için aynı işi görür. Bu, "varsayılan → dosya → bayraklar" biçimindeki katmanlı ayarlar için kullanışlıdır: her sonraki katman önceki katmanın anahtarlarını ezer.

```
# Python 3.15+
layers = [{"debug": False, "port": 80}, {"port": 8080}, {"debug": True}]
config = {**layer for layer in layers}
# {'debug': True, 'port': 8080}
```

Kursta vurgulanan iki not var. Yıldızı, parantez olmadan bir koşullu ifadeyle birleştiremezsiniz: `[*w if w else 0 for w in weeks]` sözdizimi hatasıdır; koşulu parantez içine almanız gerekir. Ayrıca bu kod 3.14 ve daha eski sürümlerde çalışmaz: kütüphaneniz eski sürümleri destekliyorsa çift `for` yapısında ya da `itertools.chain.from_iterable` ile kalın.

## Asenkron üreteçler

`async def` fonksiyonunda `yield` varsa asenkron bir üreteç elde edersiniz. Onun üzerinde `async for` ile dolaşılır ve içeride ağ ya da disk işlemleri `await` ile beklenebilir. Asenkron liste üretimleri de vardır.

```
import asyncio

async def ticks(n):
    for i in range(n):
        await asyncio.sleep(0.1)
        yield i

async def main():
    result = [i async for i in ticks(3)]
    print(result)  # [0, 1, 2]

asyncio.run(main())
```

## Tipik hatalar

- **Üç katlı comprehension.** İki `for`, iki `if` ve bir üçlü operatör aynı satırda olursa bir hafta sonra kimse okuyamaz. Tek satıra sığmıyorsa normal bir döngü yazın: PEP 8 okunabilirliği kısalığın önüne koyar.
- **Tükenmiş üretecin ikinci kez kullanılması.** Aynı üreteç üzerinde ikinci `sum(gen)` 0 döndürür, çünkü üreteç çoktan tükenmiştir.
- **Yan etki için comprehension.** `[print(x) for x in xs]` gereksiz bir `None` listesi oluşturur. Eylemler için `for` döngüsü kullanın.
- **Üreteç ifadesinden liste beklemek.** `(x for x in xs)[0]` bir `TypeError` verir: üreteçlerde indeks yoktur.

## Pratik için üreteç görevleri

1. Sonsuz Fibonacci sayıları üreten bir `fib()` üreteci yazın ve `islice` ile ilk 10 tanesini alın.
2. Üç harften uzun kelimeler için "kelime → uzunluk" sözlüğünü tek satırda üretin.
3. Bir milyona kadar çift sayıların karelerinin toplamını, liste oluşturmadan hesaplayın.
4. Bir listeyi `n` elemanlık parçalara bölen bir `chunks(seq, n)` üreteci yazın.

## Özet

Yineleyici elemanları `next()` ile birer birer verir; üreteç bunu yazmanın en basit yoludur: `yield` içeren bir fonksiyon ya da parantez içinde bir ifade. Liste, sözlük ve küme üretimleri koleksiyonu tek satırda kurar; `if` filtresi sona, `if else` seçimi başa yazılır. Bellek tasarrufu ya da veri akışını bir kez işlemek için üreteç, sonucu birden fazla kullanmak için liste seçin. Üreteçler, comprehension'lar ve Python 3.15'in yeni açma sözdizimi kursta harcama takipçisi örneğiyle uygulamalı olarak işlenir.

## FAQ

**Üreteç fonksiyonunda return kullanabilir miyim?**

Evet. Değersiz return üreteci sonlandırır; değerli return ise değeri StopIteration istisnasının value özelliğine koyar ve bunu yield from ifadesi alır.

**Bir üreteci nasıl listeye çevirebilirim?**

list() fonksiyonuna verin: list(gen). Bundan sonra üreteç tükenmiş olur ve tüm değerler bellekte bulunur.

**List comprehension mi yoksa append kullanan döngü mü daha hızlıdır?**

Genellikle comprehension biraz hızlıdır, çünkü her turda append metodunu çağırmaz. Ama seçim okunabilirliğe göre yapılmalı; fark nadiren belirleyici olur.

**Bir nesnenin üreteç olup olmadığını nasıl anlarım?**

Nesne için inspect.isgenerator(obj), fonksiyon için inspect.isgeneratorfunction(f) kullanın. Bir nesnenin yineleyici olduğunu isinstance(obj, collections.abc.Iterator) ile kontrol edebilirsiniz.

**Üreteç ifadesi ile liste üretimi arasındaki fark nedir?**

Köşeli parantez tüm listeyi hemen bellekte oluşturur; yuvarlak parantez ise elemanları istendikçe hesaplayan bir üreteç döndürür ve yalnızca bir kez dolaşılabilir.


---
Source: Skillaria.Top (https://skillaria.top)
