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.0cycle(): 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
# ⬜ Kivirepeat(): 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ıralamagroupbyiç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) # 15partial(): 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_cachesadece 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ışır3. 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ı —isliceveyabreakile 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.
AI Asistan
Sorularını yanıtlamaya hazır