Python'da Context Manager ve with Bloğu Derinlemesine
Python'da Context Manager ve with Bloğu Derinlemesine
Dosya açtın, işlemini yaptın, kapatmayı unuttun. Veritabanına bağlandın, sorguyu çalıştırdın, bağlantıyı kapatmadın. Kilitleme (lock) aldın, exception fırladı, kilidi bırakmadın. Bu senaryoların hepsi aynı kök problemi paylaşıyor: kaynak yönetimi. Bir kaynağı aldıktan sonra ne olursa olsun — başarılı da olsa, hata da fırlasa — geri vermek zorundasın.
Python'un with bloğu ve context manager'lar tam olarak bu problemi çözer. Ve bunu o kadar zarif yapar ki, bir kez alıştığında with olmadan kaynak yönetimi yapmak sana ilkel gelmeye başlar. Bu yazıda context manager'ları derinlemesine öğreneceksin — sadece kullanmayı değil, kendi context manager'ını yazmayı, contextlib'in güçlü araçlarını ve gerçek dünya senaryolarını.
Neden Context Manager?
Bunu bir otel odası analojisiyle düşün. Resepsiyondan kartı alırsın (kaynağı edinirsin), odayı kullanırsın (işlemini yaparsın), çıkışta kartı iade edersin (kaynağı bırakırsın). Kartı iade etmezsen, otel sistemi o odayı müsait göremez ve başkası kullanamaz. İşte kaynak sızıntısı (resource leak) tam olarak budur.
Context manager olmadan Python'da kaynak yönetimi şöyle görünür:
# ❌ Tehlikeli — hata durumunda dosya kapanmaz
f = open("veri.txt", "r")
icerik = f.read() # Ya burada exception fırlarsa?
f.close() # Bu satıra hiç ulaşılmayabilir!Try-finally ile düzeltmeye çalışırsan:
# 😐 Çalışır ama boilerplate fazla
f = open("veri.txt", "r")
try:
icerik = f.read()
# işlemler...
finally:
f.close() # Ne olursa olsun çalışırContext manager ile:
# ✅ Temiz, güvenli, Pythonic
with open("veri.txt", "r") as f:
icerik = f.read()
# işlemler...
# f otomatik olarak kapatıldı — exception fırlasa bilewith bloğundan çıkıldığında — normal çıkış veya exception fark etmez — f.close() otomatik olarak çağrılır. Unutma riski sıfır.
with Bloğu Nasıl Çalışır?
with ifadesi çalıştığında Python arka planda şunu yapar:
İfadeyi değerlendir (evaluate) → context manager nesnesini al
Nesnenin
__enter__()metodunu çağır → dönen değeriasdeğişkenine atawithbloğunun gövdesini çalıştırBlok bittiğinde (normal veya hata ile)
__exit__()metodunu çağır
# with ifadesi:
with expression as variable:
body
# Python'un arka planda yaptığı:
manager = expression # context manager nesnesini al
variable = manager.__enter__() # kaynağı edin
try:
body # iş mantığını çalıştır
except:
if not manager.__exit__(*sys.exc_info()): # hata bilgisi ile çık
raise # __exit__ True dönmediyse hatayı tekrar fırlat
else:
manager.__exit__(None, None, None) # normal çıkışBu iki sihirli metot — __enter__ ve __exit__ — context manager protokolünün tamamı. Herhangi bir nesne bu iki metodu implement ederse, with ile kullanılabilir.
Kendi Context Manager'ını Yaz: Sınıf Tabanlı
En temel yaklaşım bir sınıf yazmak:
import time
class TimerContext:
"""Bir kod bloğunun ne kadar sürede çalıştığını ölçer."""
def __init__(self, label="İşlem"):
self.label = label
self.start_time = None
self.elapsed = None
def __enter__(self):
"""with bloğuna girerken çağrılır. Zamanlayıcıyı başlat."""
self.start_time = time.perf_counter()
return self # as değişkenine atanacak değer
def __exit__(self, exc_type, exc_val, exc_tb):
"""with bloğundan çıkarken çağrılır. Süreyi hesapla."""
self.elapsed = time.perf_counter() - self.start_time
print(f"⏱ {self.label}: {self.elapsed:.4f} saniye")
# False döndür → exception varsa tekrar fırlatılsın
# True döndürürsen exception yutulur (genelde istemezsin)
return False
# Kullanım
with TimerContext("Veri işleme") as timer:
# ağır bir işlem simülasyonu
total = sum(range(10_000_000))
print(f"Ölçülen süre: {timer.elapsed:.4f}s")Çıktı:
⏱ Veri işleme: 0.3821 saniye
Ölçülen süre: 0.3821s`__exit__` parametreleri:
exc_type: Exception sınıfı (hata yoksaNone)exc_val: Exception nesnesi (hata yoksaNone)exc_tb: Traceback nesnesi (hata yoksaNone)Dönüş değeri:
Truedönerse exception bastırılır,FalseveyaNonedönerse exception tekrar fırlatılır
⚠️ Dikkat: __exit__'ten True döndürmek exception'ı yutmak demektir. Bunu sadece bilinçli olarak, çok spesifik durumlar için yap. Genel kural: False veya None döndür.
Gerçek Dünya Örneği: Veritabanı Bağlantı Yönetimi
Veritabanı bağlantıları en kritik kaynaklardan biri. Kapatılmayan bağlantılar pool tükenmesine ve uygulamanın çökmesine yol açar:
import sqlite3
class DatabaseConnection:
"""Veritabanı bağlantısını güvenle yöneten context manager.
Başarılı işlemlerde commit, hata durumunda rollback yapar.
Her durumda bağlantıyı kapatır.
"""
def __init__(self, db_path, autocommit=True):
self.db_path = db_path
self.autocommit = autocommit
self.connection = None
self.cursor = None
def __enter__(self):
"""Bağlantıyı aç ve cursor oluştur."""
self.connection = sqlite3.connect(self.db_path)
self.connection.row_factory = sqlite3.Row # dict-like erişim
self.cursor = self.connection.cursor()
return self.cursor # with ... as cursor şeklinde kullanılacak
def __exit__(self, exc_type, exc_val, exc_tb):
"""Hata varsa rollback, yoksa commit. Her durumda kapat."""
if exc_type is not None:
# Exception fırladı — değişiklikleri geri al
print(f"⚠️ Hata oluştu, rollback yapılıyor: {exc_val}")
self.connection.rollback()
elif self.autocommit:
# Başarılı — değişiklikleri kaydet
self.connection.commit()
# Her durumda bağlantıyı kapat
self.cursor.close()
self.connection.close()
return False # exception'ı tekrar fırlat
# Kullanım — başarılı senaryo
with DatabaseConnection("app.db") as cursor:
cursor.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)")
cursor.execute("INSERT INTO users (name, email) VALUES (?, ?)", ("Tolgahan", "tolgahan@example.com"))
cursor.execute("INSERT INTO users (name, email) VALUES (?, ?)", ("Ahmet", "ahmet@example.com"))
# with bloğu bittiğinde otomatik commit
# Kullanım — hata senaryosu
try:
with DatabaseConnection("app.db") as cursor:
cursor.execute("INSERT INTO users (name, email) VALUES (?, ?)", ("Ali", "ali@example.com"))
raise ValueError("Bir şeyler ters gitti!") # Hata fırladı
cursor.execute("INSERT INTO users (name, email) VALUES (?, ?)", ("Veli", "veli@example.com"))
except ValueError:
print("Hata yakalandı — Ali de Veli de eklenmedi (rollback yapıldı)")Bu pattern'in güzelliği şu: veritabanı işlemlerinin atomik (atomic) olmasını garanti ediyorsun. Ya hepsi başarılı olur ya da hiçbiri. Ve bağlantı ne olursa olsun kapatılır.
contextlib: Daha Az Kodla Context Manager
Sınıf yazmak bazen fazla ceremoni (boilerplate) getirir. Python'un contextlib modülü bunu dramatik şekilde sadeleştirir.
@contextmanager Decorator
@contextmanager decorator'ı, bir generator fonksiyonu context manager'a dönüştürür:
from contextlib import contextmanager
import time
@contextmanager
def timer(label="İşlem"):
"""Sınıf yazmadan context manager — generator tabanlı."""
start = time.perf_counter()
try:
yield start # yield'den ÖNCE = __enter__, yield değeri = as değişkeni
finally:
elapsed = time.perf_counter() - start # yield'den SONRA = __exit__
print(f"⏱ {label}: {elapsed:.4f}s")
# Kullanım — sınıf tabanlı ile aynı, ama çok daha az kod
with timer("Hesaplama"):
total = sum(x**2 for x in range(1_000_000))Nasıl çalışıyor?
yield'den önceki kod →__enter__()gibi çalışıryieldedilen değer →asdeğişkenine atanıryield'den sonraki kod →__exit__()gibi çalışırtry/finally→ exception olsa bile cleanup yapılır
Bu yaklaşım, tek seferlik veya basit context manager'lar için idealdir. Karmaşık durum yönetimi gerekiyorsa (birden fazla kaynak, conditional logic) sınıf tabanlı yaklaşımı tercih et.
Veritabanı Bağlantısı — contextlib ile
from contextlib import contextmanager
import sqlite3
@contextmanager
def db_connection(db_path, autocommit=True):
"""Veritabanı bağlantı yönetimi — contextlib versiyonu."""
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
try:
yield cursor
if autocommit:
conn.commit()
except Exception:
conn.rollback()
raise # exception'ı tekrar fırlat
finally:
cursor.close()
conn.close()
# Kullanım aynı
with db_connection("app.db") as cursor:
cursor.execute("SELECT * FROM users")
for row in cursor.fetchall():
print(dict(row))Dosya Kilitleme (File Locking) Context Manager
Birden fazla process aynı dosyaya yazmaya çalıştığında veri bozulur. Dosya kilitleme ile bunu önleyebilirsin:
from contextlib import contextmanager
import fcntl
import os
@contextmanager
def file_lock(filepath):
"""Dosya tabanlı kilitleme — aynı anda tek process yazabilir.
Unix/Linux sistemlerde fcntl.flock kullanır.
"""
lock_path = filepath + ".lock"
lock_file = open(lock_path, "w")
try:
# Kilidi al — başka process kilitlediyse bekle
fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX)
print(f"🔒 Kilit alındı: {filepath}")
yield filepath
finally:
# Kilidi bırak
fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
lock_file.close()
# Lock dosyasını temizle (opsiyonel)
try:
os.remove(lock_path)
except OSError:
pass
print(f"🔓 Kilit bırakıldı: {filepath}")
# Kullanım
with file_lock("/tmp/shared_data.txt") as path:
with open(path, "a") as f:
f.write("Bu satır güvenle yazıldı\n")İç İçe Context Manager ve ExitStack
Birden fazla kaynağı aynı anda yönetmen gerektiğinde:
# İç içe with — basit ama derinleşince okunması zorlaşır
with open("input.txt", "r") as fin:
with open("output.txt", "w") as fout:
with open("log.txt", "a") as flog:
# üç dosya birden açık
data = fin.read()
fout.write(data.upper())
flog.write("Dönüşüm tamamlandı\n")
# Aynı satırda — Python 3.10+ parenthesized context managers
with (
open("input.txt", "r") as fin,
open("output.txt", "w") as fout,
open("log.txt", "a") as flog,
):
data = fin.read()
fout.write(data.upper())
flog.write("Dönüşüm tamamlandı\n")Peki ya kaç tane kaynak yöneteceğin çalışma zamanında belli oluyorsa? ExitStack tam bunu çözer:
from contextlib import ExitStack
def process_files(input_paths, output_dir):
"""Dinamik sayıda dosyayı güvenle yönet."""
with ExitStack() as stack:
# Kaç dosya olduğu runtime'da belli
input_files = [
stack.enter_context(open(path, "r"))
for path in input_paths
]
output_files = [
stack.enter_context(open(f"{output_dir}/{i}.txt", "w"))
for i in range(len(input_paths))
]
# Tüm dosyalar açık — işlemleri yap
for fin, fout in zip(input_files, output_files):
fout.write(fin.read().upper())
# ExitStack with bloğundan çıkınca TÜM dosyaları kapatır
# Açılma sırasının tersinde kapatır (LIFO — son açılan ilk kapanır)ExitStack ayrıca callback fonksiyonları da kayıt edebilir:
from contextlib import ExitStack
with ExitStack() as stack:
# Cleanup callback'i kaydet
stack.callback(print, "3. Bu en son çalışır")
stack.callback(print, "2. Bu ikinci çalışır")
stack.callback(print, "1. Bu ilk çalışır (LIFO)")
print("With bloğu içindeyiz")
# Çıktı:
# With bloğu içindeyiz
# 1. Bu ilk çalışır (LIFO)
# 2. Bu ikinci çalışır
# 3. Bu en son çalışırcontextlib'in Diğer Kullanışlı Araçları
suppress — Belirli Exception'ları Yut
from contextlib import suppress
import os
# ❌ Geleneksel yol
try:
os.remove("temp.txt")
except FileNotFoundError:
pass
# ✅ suppress ile — tek satır
with suppress(FileNotFoundError):
os.remove("temp.txt")redirect_stdout / redirect_stderr — Çıktıyı Yönlendir
from contextlib import redirect_stdout
import io
# stdout'u bir string buffer'a yönlendir
buffer = io.StringIO()
with redirect_stdout(buffer):
print("Bu ekrana değil buffer'a yazılır")
print("Bu da")
captured = buffer.getvalue()
print(f"Yakalanan çıktı: {captured}")
# Yakalanan çıktı: Bu ekrana değil buffer'a yazılır\nBu da\nclosing — close() metodu olan nesneleri with ile kullan
from contextlib import closing
from urllib.request import urlopen
# urlopen context manager değildir (eski Python versiyonlarında)
# closing() ile with kullanılabilir hale gelir
with closing(urlopen("https://example.com")) as page:
content = page.read()Async Context Manager (Python 3.10+)
Asenkron kodda da context manager kullanabilirsin. __aenter__ ve __aexit__ metodlarını implement et:
import aiohttp
import asyncio
from contextlib import asynccontextmanager
@asynccontextmanager
async def api_session(base_url, timeout=30):
"""Asenkron HTTP session yönetimi."""
timeout_config = aiohttp.ClientTimeout(total=timeout)
session = aiohttp.ClientSession(base_url, timeout=timeout_config)
try:
yield session
finally:
await session.close()
# Kullanım
async def fetch_data():
async with api_session("https://api.example.com") as session:
async with session.get("/users") as response:
data = await response.json()
return data
# asyncio.run(fetch_data())async with ifadesi, with ile aynı mantıkta çalışır — ama await ile asenkron kaynak yönetimi yapar.
Yaygın Hatalar ve Tuzaklar
1. __enter__'dan self Yerine Yanlış Değer Döndürmek
# ❌ __enter__ None döndürüyor
class BadContext:
def __enter__(self):
print("Girdim")
# return unutuldu — None döner!
def __exit__(self, *args):
print("Çıktım")
with BadContext() as ctx:
print(ctx) # None! — muhtemelen istediğin bu değil__enter__ her zaman as değişkenine atanacak değeri döndürmeli. Genellikle return self veya yönetilen kaynağın kendisi (cursor, file handle, vb.) döndürülür.
2. __exit__'te Exception Yutmak
# ❌ TEHLİKELİ — tüm exception'ları yutar!
class SilentContext:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type:
print(f"Hata oldu ama yuttum: {exc_val}")
return True # True = exception'ı bastır
with SilentContext():
raise ValueError("Kritik hata!")
# Bu hata sessizce yutulur — çok tehlikeli!
print("Hayat devam ediyor") # Bu satır çalışır — bug gizlenir__exit__'ten True döndürmek çok spesifik durumlar dışında yanlıştır. Hataları yutmak bug'ları gizler. Her zaman False veya None döndür, veya sadece belirli, bilinen exception türlerini bastır.
3. Generator Tabanlı Context Manager'da yield Sonrası Kodu finally'a Koymamak
from contextlib import contextmanager
# ❌ Exception olursa cleanup çalışmaz!
@contextmanager
def bad_resource():
resource = acquire_resource()
yield resource
release_resource(resource) # Exception fırlarsa buraya ulaşılmaz!
# ✅ finally ile garanti et
@contextmanager
def good_resource():
resource = acquire_resource()
try:
yield resource
finally:
release_resource(resource) # Her durumda çalışır@contextmanager kullanırken cleanup kodunu her zaman try/finally içine koy. yield'den sonraki düz kod, exception durumunda çalışmaz.
4. Context Manager'ı with Bloğu Dışında Kullanmak
# ❌ Anlamsız — with olmadan context manager'ın faydası yok
db = DatabaseConnection("app.db")
cursor = db.__enter__() # manuel çağrı — tehlikeli!
cursor.execute("SELECT 1")
# __exit__ çağrılmayabilir — kaynak sızıntısı!
# ✅ Her zaman with ile kullan
with DatabaseConnection("app.db") as cursor:
cursor.execute("SELECT 1")Best Practices
Her zaman `with` kullan. Dosya, veritabanı bağlantısı, kilit, HTTP session — eğer kapanması/bırakılması gereken bir kaynak varsa
withkullan.`contextlib` tercih et. Basit context manager'lar için sınıf yazmak yerine
@contextmanagerdecorator'ını kullan. Daha az kod, daha okunabilir.`__exit__`'ten `True` döndürme. Exception bastırmak neredeyse her zaman yanlıştır. Sadece çok bilinçli, spesifik durumlar için kullan. (Örneğin,
suppress()bile bunu sadece belirli exception türleri için yapar.)Cleanup kodunu `finally`'a koy. Generator tabanlı context manager'larda
yieldsonrası kodtry/finallyiçinde olmalı. Sınıf tabanlında__exit__zaten her durumda çağrılır.Dinamik kaynak sayısı için `ExitStack` kullan. Sabit sayıda kaynak için iç içe
withveya virgüllüwithyeterli. Dinamik durumlar içinExitStackçok daha temiz.`__enter__` anlamlı değer döndürsün.
return selfveya yönetilen kaynağın kendisi.Nonedöndürmek genelde bir bug.Async kod için `async with` kullan.
aiohttp,asyncpggibi async kütüphaneler context manager destekler.@asynccontextmanagerile kendi async context manager'ını yaz.
Sonuç
Python'un context manager'ları kaynak yönetiminin en zarif çözümü. Bu yazıda öğrendiklerini özetleyelim:
`with` bloğu
__enter__ve__exit__protokolünü kullanır — ne olursa olsun cleanup garanti edilirSınıf tabanlı context manager:
__enter__ve__exit__metotlarını implement etGenerator tabanlı:
@contextmanagerile daha az kodla context manager yaz`ExitStack` ile dinamik sayıda kaynağı güvenle yönet
`suppress`, `redirect_stdout`, `closing` gibi hazır araçları tanı
Async context manager ile asenkron kaynak yönetimi yap
Context manager yazmak Pythonic kodun temel taşlarından biri. try/finally ile uğraşmak yerine, kaynağı with bloğuyla sar ve Python'un sihrine güven. Bir sonraki adım olarak kendi projendeki kaynak yönetim pattern'lerini context manager'a dönüştürmeyi dene — veritabanı bağlantıları, API session'ları, geçici dosyalar, kilitleme mekanizmaları. Her birine bir with yakışır.
Bu yazıyı beğendiniz mi?
Bültene abone olun ve yeni yazılardan ilk siz haberdar olun. Spam yok, söz.
Bu konuyu derinlemesine öğrenmek ister misin?
Python Programlama: Sıfırdan İleri Seviyeye
İlgili Yazılar
Python Generators ve Iterators: Bellek Verimli Programlamanın Sırrı
Python'da generator ve iterator yapıları nasıl çalışır? Lazy evaluation, yield, generator expression, pipeline pattern v...
Python Decorators (Dekoratörler): Fonksiyonlarını Güçlendir
Python'da decorator'lar nasıl çalışır, nasıl yazılır ve gerçek projelerde nasıl kullanılır? Sıfırdan ileri seviyeye, kod...
Python Nedir? Neden Python Öğrenmelisiniz?
Python nedir, ne işe yarar? Yapay zeka, veri bilimi, web geliştirme ve otomasyon için neden Python? Kariyer fırsatları v...