← Kursa Dön
📄 Text · 15 min

itertools ve functools

Giriş: Iterator Building Blocks

Lego'yu bilirsin: küçük parçaları birleştirerek büyük yapılar kurarsın. Python'daki itertools modülü de öyle — küçük, verimli iterator parçaları sağlar ve bunları birleştirerek karmaşık veri işleme pipeline'ları kurabilirsin.

itertools, C dilinde yazılmış, yüksek performanslı iterator araçları sunar. Hepsi lazy çalışır — elemanları önceden hesaplamaz, ihtiyaç duyuldukça üretir. Bu da devasa veri setlerini minimum bellekle işlemeni sağlar.

Bu derste itertools'un en kullanışlı araçlarını ve onun yoldaşı functools modülünü öğreneceksin.


Sonsuz Iteratorlar

Bu iterator'lar hiç bitmez — dikkatli kullan!

count(): Sonsuz Sayaç

from itertools import count

# 10'dan başla, 2'şer artır
for num in count(10, 2):
    if num > 20:
        break
    print(num, end=" ")
# 10 12 14 16 18 20

# enumerate benzeri kullanım
names = ["Ali", "Veli", "Ayşe"]
for i, name in zip(count(1), names):
    print(f"{i}. {name}")
# 1. Ali
# 2. Veli
# 3. Ayşe

# Float adımlarla da çalışır
for x in count(0.0, 0.5):
    if x > 2.0:
        break
    print(f"{x:.1f}", end=" ")
# 0.0 0.5 1.0 1.5 2.0

cycle(): Sonsuz Döngü

from itertools import cycle, islice

# Renkleri sonsuz döngüde tekrarla
colors = cycle(["kırmızı", "yeşil", "mavi"])
print(list(islice(colors, 7)))
# ['kırmızı', 'yeşil', 'mavi', 'kırmızı', 'yeşil', 'mavi', 'kırmızı']

# Pratik: Satırları alternatif renklerle göster
items = ["Elma", "Armut", "Muz", "Portakal", "Kivi"]
backgrounds = cycle(["⬜", "⬛"])

for bg, item in zip(backgrounds, items):
    print(f"{bg} {item}")
# ⬜ Elma
# ⬛ Armut
# ⬜ Muz
# ⬛ Portakal
# ⬜ Kivi

repeat(): Tekrarlama

from itertools import repeat

# 5 kez "Merhaba" tekrarla
print(list(repeat("Merhaba", 5)))
# ['Merhaba', 'Merhaba', 'Merhaba', 'Merhaba', 'Merhaba']

# map ile kullanım: her elemanı aynı sayıyla çarp
import operator
numbers = [1, 2, 3, 4, 5]
doubled = list(map(operator.mul, numbers, repeat(2)))
print(doubled)  # [2, 4, 6, 8, 10]

# Sonsuz repeat (sayı vermezsen)
for i, val in enumerate(repeat("ping")):
    if i >= 3:
        break
    print(val)

Sonlandıran Iteratorlar

Bu iterator'lar girdi tükenince durur.

chain(): Birleştir

Birden fazla iterable'ı tek bir iterator gibi birleştirir:

from itertools import chain

# Listeleri birleştir
list1 = [1, 2, 3]
list2 = [4, 5, 6]
list3 = [7, 8, 9]

combined = list(chain(list1, list2, list3))
print(combined)  # [1, 2, 3, 4, 5, 6, 7, 8, 9]

# chain.from_iterable: iç içe listeyi düzleştir
nested = [[1, 2], [3, 4], [5, 6]]
flat = list(chain.from_iterable(nested))
print(flat)  # [1, 2, 3, 4, 5, 6]

# Farklı tipler birleştirilebilir
mixed = list(chain("abc", [1, 2], (True, False)))
print(mixed)  # ['a', 'b', 'c', 1, 2, True, False]

compress(): Seçici Filtreleme

from itertools import compress

# Sadece True olan elemanları al
data = ["A", "B", "C", "D", "E"]
selectors = [True, False, True, False, True]

result = list(compress(data, selectors))
print(result)  # ['A', 'C', 'E']

# 1/0 ile de çalışır
result = list(compress(data, [1, 0, 1, 0, 1]))
print(result)  # ['A', 'C', 'E']

dropwhile() ve takewhile()

from itertools import dropwhile, takewhile

numbers = [1, 3, 5, 2, 4, 6, 1, 3]

# dropwhile: Koşul sağlandığı SÜRECE atla, sonra hepsini al
result = list(dropwhile(lambda x: x < 4, numbers))
print(result)  # [5, 2, 4, 6, 1, 3] — 5'ten itibaren hepsini aldı

# takewhile: Koşul sağlandığı SÜRECE al, sonra dur
result = list(takewhile(lambda x: x < 4, numbers))
print(result)  # [1, 3] — 5 gelince durdu

# Pratik: Log dosyasından belirli tarihe kadar oku
logs = [
    "2024-01-01 Başlangıç",
    "2024-01-02 İşlem",
    "2024-01-03 Hata",
    "2024-01-04 Düzeltme",
]
january_first_two = list(takewhile(
    lambda x: "01-03" not in x, logs
))
print(january_first_two)
# ['2024-01-01 Başlangıç', '2024-01-02 İşlem']

islice(): Iterator Dilimleme

from itertools import islice, count

# Sonsuz iterator'dan ilk 5 elemanı al
print(list(islice(count(100), 5)))
# [100, 101, 102, 103, 104]

# Belirli aralıktan al (start, stop, step)
print(list(islice(range(100), 10, 20, 3)))
# [10, 13, 16, 19]

# Generator'ın ilk N elemanı
def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

print(list(islice(fibonacci(), 10)))
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Kombinatorik Iteratorlar

product(): Kartezyen Çarpım

from itertools import product

# İki listenin kartezyen çarpımı
colors = ["kırmızı", "mavi"]
sizes = ["S", "M", "L"]

combinations = list(product(colors, sizes))
print(combinations)
# [('kırmızı', 'S'), ('kırmızı', 'M'), ('kırmızı', 'L'),
#  ('mavi', 'S'), ('mavi', 'M'), ('mavi', 'L')]

# 3 liste ile
a = [1, 2]
b = ["a", "b"]
c = [True, False]
print(list(product(a, b, c)))
# [(1, 'a', True), (1, 'a', False), (1, 'b', True), ...]

# repeat parametresi: kendisiyle çarpım
# Tüm 2-basamaklı binary sayılar
print(list(product([0, 1], repeat=3)))
# [(0,0,0), (0,0,1), (0,1,0), (0,1,1), (1,0,0), (1,0,1), (1,1,0), (1,1,1)]

permutations(): Sıralı Dizilimler

from itertools import permutations

# 3 elemanın tüm sıralı dizilimleri
letters = ["A", "B", "C"]
perms = list(permutations(letters))
print(perms)
# [('A','B','C'), ('A','C','B'), ('B','A','C'),
#  ('B','C','A'), ('C','A','B'), ('C','B','A')]
print(f"Toplam: {len(perms)}")  # 6 (3! = 6)

# Belirli uzunlukta permütasyonlar
perms2 = list(permutations(letters, 2))
print(perms2)
# [('A','B'), ('A','C'), ('B','A'), ('B','C'), ('C','A'), ('C','B')]
print(f"Toplam: {len(perms2)}")  # 6 (P(3,2) = 6)

# Kelime anagramları
word = "CAT"
anagrams = ["".join(p) for p in permutations(word)]
print(anagrams)  # ['CAT', 'CTA', 'ACT', 'ATC', 'TCA', 'TAC']

combinations(): Sırasız Seçimler

from itertools import combinations, combinations_with_replacement

# 4 elemandan 2'li kombinasyonlar
items = ["A", "B", "C", "D"]
combos = list(combinations(items, 2))
print(combos)
# [('A','B'), ('A','C'), ('A','D'), ('B','C'), ('B','D'), ('C','D')]
print(f"Toplam: {len(combos)}")  # 6 (C(4,2) = 6)

# Loto: 49 sayıdan 6'lı kombinasyon
from itertools import combinations
loto_count = sum(1 for _ in combinations(range(1, 50), 6))
print(f"Loto kombinasyonu: {loto_count:,}")  # 13,983,816

# Tekrarlı kombinasyon
combos_rep = list(combinations_with_replacement("AB", 3))
print(combos_rep)
# [('A','A','A'), ('A','A','B'), ('A','B','B'), ('B','B','B')]

groupby(): Veriyi Gruplama

groupby(), ardışık aynı key'e sahip elemanları gruplar. Önemli: Veri önceden sıralı olmalı!

from itertools import groupby

# Basit gruplama
data = "AAABBBCCAAB"
for key, group in groupby(data):
    print(f"{key}: {list(group)}")
# A: ['A', 'A', 'A']
# B: ['B', 'B', 'B']
# C: ['C', 'C']
# A: ['A', 'A']    — Dikkat: A tekrar gruplanmadı!
# B: ['B']

Sıralayarak Gruplama

from itertools import groupby
from operator import itemgetter

students = [
    {"name": "Ali", "grade": "A"},
    {"name": "Veli", "grade": "B"},
    {"name": "Ayşe", "grade": "A"},
    {"name": "Fatma", "grade": "B"},
    {"name": "Mehmet", "grade": "A"},
]

# Önce sırala, sonra grupla!
sorted_students = sorted(students, key=itemgetter("grade"))

for grade, group in groupby(sorted_students, key=itemgetter("grade")):
    names = [s["name"] for s in group]
    print(f"Not {grade}: {', '.join(names)}")
# Not A: Ali, Ayşe, Mehmet
# Not B: Veli, Fatma

⚠️ Dikkat: groupby() ardışık elemanları gruplar. Veriyi önceden sıralamamışsan beklemediğin sonuçlar alırsın. Sıralama groupby için şarttır.

Sayıları Çift/Tek Gruplama

from itertools import groupby

numbers = sorted(range(1, 11), key=lambda x: x % 2)

for key, group in groupby(numbers, key=lambda x: "Çift" if x % 2 == 0 else "Tek"):
    print(f"{key}: {list(group)}")
# Çift: [2, 4, 6, 8, 10]
# Tek: [1, 3, 5, 7, 9]

accumulate(): Kümülatif İşlem

from itertools import accumulate
import operator

numbers = [1, 2, 3, 4, 5]

# Kümülatif toplam (default)
print(list(accumulate(numbers)))
# [1, 3, 6, 10, 15]

# Kümülatif çarpım
print(list(accumulate(numbers, operator.mul)))
# [1, 2, 6, 24, 120]

# Kümülatif maksimum
data = [3, 1, 4, 1, 5, 9, 2, 6]
print(list(accumulate(data, max)))
# [3, 3, 4, 4, 5, 9, 9, 9]

# Başlangıç değeri ile (Python 3.8+)
print(list(accumulate(numbers, operator.add, initial=100)))
# [100, 101, 103, 106, 110, 115]

Pratik: Banka Hesabı Bakiye Takibi

from itertools import accumulate

initial_balance = 1000
transactions = [500, -200, 300, -150, -400, 100]

# Her işlemden sonraki bakiye
balances = list(accumulate(transactions, initial=initial_balance))
print("İşlem sonrası bakiyeler:")
for tx, balance in zip(transactions, balances[1:]):
    sign = "+" if tx > 0 else ""
    print(f"  {sign}{tx:>6} → Bakiye: {balance:,}")

zip_longest(): Eşit Olmayan Uzunluklar

Normal zip() kısa olanda durur. zip_longest() uzun olanda durur:

from itertools import zip_longest

names = ["Ali", "Veli", "Ayşe"]
scores = [90, 85]

# Normal zip: kısa olan yerde durur
print(list(zip(names, scores)))
# [('Ali', 90), ('Veli', 85)]  — Ayşe kayboldu!

# zip_longest: eksikleri doldurur
print(list(zip_longest(names, scores, fillvalue=0)))
# [('Ali', 90), ('Veli', 85), ('Ayşe', 0)]

# Farklı fillvalue
print(list(zip_longest(names, scores, fillvalue="N/A")))
# [('Ali', 90), ('Veli', 85), ('Ayşe', 'N/A')]

Pratik: Tablo Hizalama

from itertools import zip_longest

headers = ["İsim", "Yaş", "Şehir"]
row1 = ["Ali", 25, "İstanbul"]
row2 = ["Veli", 30]  # Eksik sütun
row3 = ["Ayşe"]       # 2 eksik sütun

rows = [row1, row2, row3]
for row in rows:
    padded = list(zip_longest(headers, row, fillvalue="-"))
    for header, value in padded:
        print(f"{header}: {value}", end=" | ")
    print()

functools Modülü

functools, higher-order fonksiyonlar ve callable nesneler için araçlar sunar.

reduce(): Yukarıda Gördük

from functools import reduce

# Toplam
total = reduce(lambda a, b: a + b, [1, 2, 3, 4, 5])
print(total)  # 15

partial(): Yukarıda Gördük

from functools import partial

def power(base, exp):
    return base ** exp

square = partial(power, exp=2)
cube = partial(power, exp=3)

lru_cache(): Memoization (Çok Önemli!)

lru_cache, fonksiyon sonuçlarını cache'ler. Aynı argümanlarla tekrar çağrıldığında hesaplama yapmak yerine cache'ten döner.

LRU = Least Recently Used. En az kullanılan önce cache'ten silinir.

from functools import lru_cache
import time

# Cache'siz: Her çağrıda yeniden hesaplar
def fibonacci_slow(n):
    if n < 2:
        return n
    return fibonacci_slow(n - 1) + fibonacci_slow(n - 2)

# Cache'li: Sonuçları hatırlar
@lru_cache(maxsize=128)
def fibonacci_fast(n):
    if n < 2:
        return n
    return fibonacci_fast(n - 1) + fibonacci_fast(n - 2)

# Hız farkı
start = time.perf_counter()
fibonacci_slow(35)
slow_time = time.perf_counter() - start

start = time.perf_counter()
fibonacci_fast(35)
fast_time = time.perf_counter() - start

print(f"Cache'siz: {slow_time:.3f}s")  # ~3-5 saniye
print(f"Cache'li:  {fast_time:.6f}s")  # ~0.000001 saniye!
print(f"Hızlanma:  {slow_time/fast_time:,.0f}x")

lru_cache Detayları

@lru_cache(maxsize=256)
def expensive_query(user_id, include_details=False):
    """Pahalı veritabanı sorgusu simülasyonu."""
    import time
    time.sleep(0.1)  # DB sorgusu
    return f"User_{user_id}_{'detail' if include_details else 'basic'}"

# İlk çağrı: hesaplar
result1 = expensive_query(42)

# İkinci çağrı: cache'ten döner (anında)
result2 = expensive_query(42)

# Cache istatistikleri
info = expensive_query.cache_info()
print(info)
# CacheInfo(hits=1, misses=1, maxsize=256, currsize=1)

# Cache'i temizle
expensive_query.cache_clear()

Python 3.9+: cache()

from functools import cache

# maxsize=None ile sınırsız cache (lru_cache(maxsize=None) kısayolu)
@cache
def factorial(n):
    if n <= 1:
        return 1
    return n * factorial(n - 1)

print(factorial(100))

💡 İpucu: lru_cache sadece hashable argümanlarla çalışır. Liste veya dict geçiremezsin. Tuple geçirebilirsin. Argümanlar immutable olmalı.

total_ordering: Karşılaştırma Kolaylaştırıcı

__eq__ ve bir karşılaştırma metodu tanımlarsan, total_ordering geri kalanını otomatik oluşturur:

from functools import total_ordering

@total_ordering
class Student:
    def __init__(self, name, grade):
        self.name = name
        self.grade = grade
    
    def __eq__(self, other):
        return self.grade == other.grade
    
    def __lt__(self, other):
        return self.grade < other.grade

# Sadece __eq__ ve __lt__ yazdık, geri kalanı otomatik:
s1 = Student("Ali", 85)
s2 = Student("Veli", 92)

print(s1 < s2)   # True
print(s1 > s2)   # False (otomatik oluşturuldu)
print(s1 <= s2)   # True (otomatik oluşturuldu)
print(s1 >= s2)   # False (otomatik oluşturuldu)

wraps: Decorator Metadatasını Koru

from functools import wraps

# wraps olmadan: orijinal fonksiyonun bilgileri kaybolur
def bad_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

# wraps ile: orijinal fonksiyonun bilgileri korunur
def good_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """Wrapper docstring'i."""
        return func(*args, **kwargs)
    return wrapper

@bad_decorator
def my_func_bad():
    """Bu önemli bir fonksiyon."""
    pass

@good_decorator
def my_func_good():
    """Bu önemli bir fonksiyon."""
    pass

print(my_func_bad.__name__)   # wrapper 😢
print(my_func_good.__name__)  # my_func_good 😊
print(my_func_good.__doc__)   # Bu önemli bir fonksiyon. 😊

Pratik: Kartezyen Çarpım ile Test

from itertools import product

# Test senaryoları oluştur
browsers = ["chrome", "firefox", "safari"]
os_list = ["windows", "macos", "linux"]
resolutions = ["1920x1080", "1366x768"]

test_cases = list(product(browsers, os_list, resolutions))
print(f"Toplam test senaryosu: {len(test_cases)}")  # 18

for i, (browser, os_name, res) in enumerate(test_cases, 1):
    print(f"Test #{i:2d}: {browser:>8} | {os_name:>8} | {res}")

Pratik: Permütasyon ile Şifre Kırma (Eğitim Amaçlı)

from itertools import permutations, product

# 4 haneli PIN: tüm olasılıklar
digits = "0123456789"
pin_count = sum(1 for _ in product(digits, repeat=4))
print(f"4 haneli PIN olasılıkları: {pin_count:,}")  # 10,000

# 3 harfli şifre: permütasyonlar
letters = "abc"
passwords = ["".join(p) for p in permutations(letters)]
print(f"'{letters}' permütasyonları: {passwords}")
# ['abc', 'acb', 'bac', 'bca', 'cab', 'cba']

Pratik: Cache'li Fibonacci

from functools import lru_cache
import time

@lru_cache(maxsize=None)
def fib(n):
    """Cache'li Fibonacci — O(n) zaman, O(n) bellek."""
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

# İlk 20 Fibonacci sayısı
for i in range(20):
    print(f"fib({i:2d}) = {fib(i)}")

# Büyük sayılar bile anında
start = time.perf_counter()
result = fib(500)
elapsed = time.perf_counter() - start
print(f"\nfib(500) = {str(result)[:50]}... ({len(str(result))} basamak)")
print(f"Süre: {elapsed:.6f}s")

# Cache istatistikleri
print(f"Cache: {fib.cache_info()}")

itertools Tarifleri (Recipes)

Python docs'ta yararlı tarifler var. İşte en kullanışlıları:

from itertools import chain, repeat, islice, zip_longest

def take(n, iterable):
    """İlk n elemanı al."""
    return list(islice(iterable, n))

def flatten(list_of_lists):
    """İç içe listeyi düzleştir."""
    return list(chain.from_iterable(list_of_lists))

def repeatfunc(func, times=None, *args):
    """Fonksiyonu tekrar tekrar çağır."""
    if times is None:
        return (func(*args) for _ in count())
    return (func(*args) for _ in range(times))

def grouper(iterable, n, fillvalue=None):
    """Iterable'ı n'li gruplara böl."""
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)

# Kullanım
print(take(5, count(100)))      # [100, 101, 102, 103, 104]
print(flatten([[1,2],[3,4]]))   # [1, 2, 3, 4]

for group in grouper(range(10), 3, fillvalue="X"):
    print(group)
# (0, 1, 2)
# (3, 4, 5)
# (6, 7, 8)
# (9, 'X', 'X')

Sliding Window

from itertools import islice
from collections import deque

def sliding_window(iterable, n):
    """n boyutlu kayan pencere."""
    it = iter(iterable)
    window = deque(islice(it, n), maxlen=n)
    if len(window) == n:
        yield tuple(window)
    for x in it:
        window.append(x)
        yield tuple(window)

# Hareketli ortalama hesaplama
data = [10, 20, 30, 40, 50, 60, 70]
for window in sliding_window(data, 3):
    avg = sum(window) / len(window)
    print(f"Pencere: {window} → Ortalama: {avg:.1f}")

Yaygın Hatalar

1. groupby'ı Sıralamadan Kullanmak

from itertools import groupby

# ❌ Sıralanmamış veri
data = [("A", 1), ("B", 2), ("A", 3)]
for key, group in groupby(data, key=lambda x: x[0]):
    print(f"{key}: {list(group)}")
# A: [('A', 1)]
# B: [('B', 2)]
# A: [('A', 3)]  — A iki kez çıktı!

# ✅ Önce sırala
data_sorted = sorted(data, key=lambda x: x[0])
for key, group in groupby(data_sorted, key=lambda x: x[0]):
    print(f"{key}: {list(group)}")
# A: [('A', 1), ('A', 3)]
# B: [('B', 2)]

2. lru_cache ile Mutable Argüman

from functools import lru_cache

# ❌ Liste argüman — TypeError!
@lru_cache
def process(items):
    return sum(items)

# process([1, 2, 3])  # TypeError: unhashable type: 'list'

# ✅ Tuple kullan
@lru_cache
def process(items):
    return sum(items)

process((1, 2, 3))  # Çalışır

3. Sonsuz Iterator'ı list()'e Çevirmeye Çalışmak

from itertools import count

# ❌ Bellek tükenir!
# all_numbers = list(count())  # ASLA yapma!

# ✅ islice ile sınırla
from itertools import islice
first_10 = list(islice(count(), 10))

Özet

  • `itertools` modülü C ile yazılmış, yüksek performanslı iterator araçları sunar. Hepsi lazy çalışır ve bellek dostudur.

  • Sonsuz iteratorlar (count, cycle, repeat) dikkatli kullanılmalı — islice veya break ile sınırla.

  • Kombinatorik araçlar (product, permutations, combinations) test senaryoları, olasılık hesapları ve brute-force çözümler için idealdir.

  • `groupby` ardışık aynı key'e sahip elemanları gruplar — veriyi önceden sıralamak şart.

  • `functools.lru_cache` fonksiyon sonuçlarını cache'leyerek tekrarlı hesaplamaları dramatik şekilde hızlandırır (Fibonacci: 5s → 0.000001s).

  • `functools.wraps` decorator yazarken orijinal fonksiyon bilgilerini korur. `total_ordering` karşılaştırma metodlarını otomatik oluşturur.