Python'da with deyimi, bir kaynağın kullanım sonunda mutlaka serbest bırakılmasını sağlar: with open("veri.txt", encoding="utf-8") as f: yazdığınızda blok ister normal biter, ister bir istisnayla kesilir, dosya her durumda kapanır. Bu işi yapan nesneye bağlam yöneticisi (context manager) denir. Bir sınıfa __enter__() ve __exit__() metotlarını ekleyerek ya da contextlib.contextmanager dekoratörüyle tek bir üretici fonksiyon yazarak kendi bağlam yöneticinizi oluşturabilirsiniz.
Bu yazıda with deyiminin adım adım nasıl çalıştığını ve hangi try/finally yapısına denk geldiğini, sınıf tabanlı bağlam yöneticisi yazmayı, __exit__() metodunun hataları nasıl bastırabildiğini, birden fazla kaynağı tek satırda açmayı, @contextmanager kullanımını ve contextlib modülündeki suppress, closing, nullcontext, redirect_stdout, chdir ve ExitStack araçlarını Python 3.15.2'de çalıştırılmış örneklerle anlatıyoruz. Hata yakalamanın temeli için Python try except, dosya işlemleri için dosya okuma ve yazma yazılarına, tüm konular için Python öğrenme rehberi sayfasına bakabilirsiniz.
with deyimi neyi çözer?

Dosyalar, ağ bağlantıları, kilitler ve geçici değişiklikler ortak bir sorun taşır: bir yerde açılır ya da değiştirilir, başka bir yerde kapatılmalı ya da eski hâline getirilmelidir. Kapatma kodunu normal akışa yazarsanız, aradaki bir istisna o satıra hiç ulaşılmamasına yol açar. Resmî öğretici bunu for line in open("dosya.txt") örneğiyle anlatır: bu yazımda dosya, kod bittikten sonra belirsiz bir süre açık kalabilir. Klasik çözüm try/finally yazmaktır; with ise aynı güvenceyi daha kısa ve tekrar kullanılabilir biçimde sağlar:
with open("sayilar.txt", "w", encoding="utf-8") as f:
f.write("1\n2\nüç\n")
toplam = 0
try:
with open("sayilar.txt", encoding="utf-8") as f:
for satir in f:
toplam += int(satir)
except ValueError as e:
print("Hata:", e)
print("Toplam:", toplam, "| dosya kapalı mı:", f.closed)
# çıktı: Hata: invalid literal for int() with base 10: 'üç\n'
# çıktı: Toplam: 3 | dosya kapalı mı: TrueÜçüncü satırda int() hata verdi ve döngü yarıda kesildi, ama dosya yine de kapandı. Hata mesajında 'üç\n' görünmesinin nedeni satır sonu karakteridir; int() baştaki ve sondaki boşlukları kabul ettiği için 1 ve 2 sorunsuz dönüştü.
with deyimi adım adım nasıl çalışır?
Dil referansı, tek öğeli bir with deyiminin yürütülmesini şöyle tanımlar: önce bağlam ifadesi (with sözcüğünden sonraki ifade) değerlendirilip bağlam yöneticisi elde edilir. Sonra yöneticinin __enter__() ve __exit__() metotları sonradan kullanılmak üzere alınır ve __enter__() çağrılır. as yazılmışsa __enter__() dönüş değeri o hedefe atanır. Ardından blok çalışır ve en sonda __exit__() çağrılır. Blok bir istisnayla terk edildiyse __exit__() metoduna istisnanın türü, değeri ve traceback'i verilir; aksi hâlde üç argüman da None olur. Referans, bu davranışın aşağı yukarı şu koda denk geldiğini gösterir:
# with IFADE as HEDEF:
# BLOK
# deyiminin yaklaşık karşılığı:
manager = (IFADE)
enter = manager.__enter__
exit = manager.__exit__
value = enter()
hit_except = False
try:
HEDEF = value
BLOK
except:
hit_except = True
if not exit(*sys.exc_info()):
raise
finally:
if not hit_except:
exit(None, None, None)Buradan iki önemli kural çıkar. Birincisi, __enter__() hatasız dönerse __exit__() her durumda çağrılır; as hedefine atama sırasında çıkan bir hata bile bloğun içindeki hata gibi ele alınır. İkincisi, blok bir istisnayla bittiyse ve __exit__() yanlış (false) bir değer döndürürse istisna yeniden fırlatılır; doğru (true) bir değer döndürürse istisna bastırılır ve program with deyiminden sonraki satırdan devam eder. Blok istisna dışında bir nedenle (normal bitiş, return, break) terk edildiyse dönüş değeri yok sayılır.
Sınıf ile bağlam yöneticisi yazmak
Bir nesnenin with ile kullanılabilmesi için sınıfında __enter__(self) ve __exit__(self, exc_type, exc_value, traceback) metotları tanımlı olmalıdır. object sınıfının kendisi bu metotları sağlamaz. Aşağıdaki Baglanti sınıfı açılışta ve kapanışta mesaj yazıyor ve __exit__() içinde istisna olup olmadığını raporluyor:
class Baglanti:
def __init__(self, ad):
self.ad = ad
def __enter__(self):
print(f"{self.ad}: açıldı")
return self
def __exit__(self, exc_type, exc_value, traceback):
tur = exc_type.__name__ if exc_type else None
print(f"{self.ad}: kapandı (hata: {tur})")
return False
def sorgu(self, metin):
return f"{metin!r} çalıştırıldı"
with Baglanti("veritabanı") as b:
print(b.sorgu("SELECT 1"))
try:
with Baglanti("önbellek"):
raise ValueError("bozuk veri")
except ValueError as e:
print("dışarıda yakalandı:", e)
# çıktı: veritabanı: açıldı
# çıktı: 'SELECT 1' çalıştırıldı
# çıktı: veritabanı: kapandı (hata: None)
# çıktı: önbellek: açıldı
# çıktı: önbellek: kapandı (hata: ValueError)
# çıktı: dışarıda yakalandı: bozuk veri__exit__() False döndürdüğü için ValueError bastırılmadı ve dıştaki try onu yakaladı. Veri modeli belgesi bir ayrıntının altını çizer: __exit__() kendisine verilen istisnayı yeniden fırlatmamalıdır; istisnanın yayılmasına izin vermek için yanlış bir değer döndürmek yeterlidir, yeniden fırlatma çağıranın sorumluluğundadır. __enter__()'in self döndürmesi de bir zorunluluk değildir; as hedefine hangi nesnenin bağlanacağına siz karar verirsiniz. Sınıflar ve özel metotlar hakkında ayrıntılı bilgi için Python dunder metotları yazısına bakabilirsiniz.
with sözcüğünden sonra bağlam yöneticisi olmayan bir nesne yazarsanız Python 3.15.2 hatanın nedenini açıkça söyler:
try:
with 5:
pass
except TypeError as e:
print(e)
# çıktı: 'int' object does not support the context manager protocol (missed __exit__ method)__exit__ ile istisna bastırmak

__exit__() metodundan True döndürmek, bloktaki istisnayı yutar. Bu güçlü ama tehlikeli bir özelliktir; yalnızca belirli ve beklenen hata türleri için kullanılmalıdır. Aşağıdaki sınıf kendisine verilen türleri bastırıyor ve bastırdığı hatayı kaydediyor:
class Yut:
def __init__(self, *turler):
self.turler = turler
self.yakalanan = None
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
if exc_type is not None and issubclass(exc_type, self.turler):
self.yakalanan = exc_value
return True
return False
with Yut(KeyError) as y:
{}["eksik"]
print("bu satır çalışmaz")
print("devam ediliyor, yakalanan:", repr(y.yakalanan))
# çıktı: devam ediliyor, yakalanan: KeyError('eksik')Blok, istisnanın oluştuğu satırda bitti ve program with deyiminden sonra devam etti. Standart kütüphane bu işi zaten contextlib.suppress() ile yapar; kendi sınıfınızı yazmanız genellikle gerekmez.
Birden fazla bağlam yöneticisi
Tek bir with satırına virgülle ayrılmış birden fazla öğe yazabilirsiniz; bu, iç içe yazılmış with deyimleriyle tamamen aynıdır. Python 3.10'dan beri öğeleri parantez içine alıp birden fazla satıra bölmek de mümkündür. Kapanış sırası açılış sırasının tersidir:
class Baglanti:
def __init__(self, ad):
self.ad = ad
def __enter__(self):
print(f"{self.ad}: açıldı")
return self
def __exit__(self, exc_type, exc_value, traceback):
tur = exc_type.__name__ if exc_type else None
print(f"{self.ad}: kapandı (hata: {tur})")
return False
def sorgu(self, metin):
return f"{metin!r} çalıştırıldı"
with (
Baglanti("A") as a,
Baglanti("B") as b,
):
print("ikisi de açık")
# çıktı: A: açıldı
# çıktı: B: açıldı
# çıktı: ikisi de açık
# çıktı: B: kapandı (hata: None)
# çıktı: A: kapandı (hata: None)Bir dosyadan okuyup diğerine yazmak bu yazımın en yaygın kullanımıdır: with open("girdi.txt", encoding="utf-8") as kaynak, open("cikti.txt", "w", encoding="utf-8") as hedef:. İkinci dosya açılırken hata çıkarsa birincisi yine kapanır.
@contextmanager: üretici fonksiyonla bağlam yöneticisi
Her seferinde sınıf yazmak yerine contextlib.contextmanager dekoratörünü kullanabilirsiniz. Dekore edilen fonksiyon bir üretici (generator) olmalı ve tam olarak bir kez yield etmelidir. yield öncesindeki kod __enter__(), sonrası __exit__() görevini görür; yield edilen değer as hedefine bağlanır. Bloğun içinde bir istisna oluşursa Python onu üreticinin içinde, yield satırında yeniden fırlatır. Bu yüzden temizlik kodunu try/finally içine almak şarttır:
from contextlib import contextmanager
ayarlar = {"hata_ayiklama": False}
@contextmanager
def gecici_ayar(anahtar, deger):
eski = ayarlar[anahtar]
ayarlar[anahtar] = deger
try:
yield ayarlar
finally:
ayarlar[anahtar] = eski
with gecici_ayar("hata_ayiklama", True) as a:
print("içeride:", a["hata_ayiklama"])
print("dışarıda:", ayarlar["hata_ayiklama"])
try:
with gecici_ayar("hata_ayiklama", True):
raise RuntimeError("test")
except RuntimeError:
pass
print("hatadan sonra:", ayarlar["hata_ayiklama"])
# çıktı: içeride: True
# çıktı: dışarıda: False
# çıktı: hatadan sonra: Falsetry/finally olmasaydı ikinci with bloğundaki RuntimeError üreticinin yield satırında fırlatılır, ayarlar[anahtar] = eski satırı hiç çalışmaz ve ayar True olarak kalırdı. Belgeler bir kuralı daha hatırlatır: üretici istisnayı yalnızca kaydetmek için yakalıyorsa onu yeniden fırlatmalıdır; aksi hâlde with deyimine istisnanın ele alındığı bildirilir ve hata bastırılmış olur.
@contextmanager ile oluşturulan nesneler tek kullanımlıktır: her with için fonksiyonu yeniden çağırmanız gerekir. Belgeler bu yüzden bağlam yöneticilerinin doğrudan with satırında oluşturulmasını önerir. Aynı nesneyi ikinci kez kullanmayı denediğimizde Python 3.15.2 bir hata verdi; belgelerdeki örnek bu durumda RuntimeError: generator didn't yield gösterse de bizim denememizde çıkan hata farklıydı:
from contextlib import contextmanager
@contextmanager
def tek_seferlik():
print("önce")
yield
print("sonra")
cm = tek_seferlik()
with cm:
pass
try:
with cm:
pass
except Exception as e:
print(type(e).__name__, "-", e)
# çıktı: önce
# çıktı: sonra
# çıktı: AttributeError - '_GeneratorContextManager' object has no attribute 'args'Hangi hatayı alırsanız alın ders aynıdır: cm = tek_seferlik() gibi bir nesneyi saklayıp yeniden kullanmayın, with tek_seferlik(): yazın. Üreticilerin nasıl çalıştığını ayrıntılı öğrenmek için Python generator ve yield yazısına bakabilirsiniz.
Dekoratör olarak kullanmak ve Python 3.15 değişikliği
@contextmanager ile yazılan yöneticiler ContextDecorator tabanlıdır, yani bir fonksiyonun tamamını sarmak için dekoratör olarak da kullanılabilir. Her çağrıda yeni bir üretici örneği oluşturulduğu için tek kullanımlık olma sorunu bu kullanımda ortaya çıkmaz. Python 3.15 burada önemli bir davranışı düzeltti: önceden bir üretici fonksiyonu ya da eşyordam (coroutine) fonksiyonu bu şekilde dekore edildiğinde bağlam, üretici nesnesi oluşturulur oluşturulmaz kapanıyordu. 3.15 ile bağlam yöneticisi yineleme ya da await süresince açık kalır:
from contextlib import contextmanager
@contextmanager
def bolum(ad):
print("başla:", ad)
yield
print("bitir:", ad)
@bolum("rapor")
def satirlar():
yield "satır 1"
yield "satır 2"
g = satirlar()
print("üretici oluşturuldu")
for s in g:
print(s)
# çıktı: üretici oluşturuldu
# çıktı: başla: rapor
# çıktı: satır 1
# çıktı: satır 2
# çıktı: bitir: raporPython 3.14 ve öncesinde bu örnekte "başla" ve "bitir" satırları yinelemeden önce, üretici oluşturulurken basılırdı. Dekoratör yazımı için Python decorator yazısına bakabilirsiniz.
contextlib araçları

contextlib modülü sık ihtiyaç duyulan bağlam yöneticilerini hazır sunar. Hepsi aynı ilkeye dayanır: girişte bir şey yapar, çıkışta onu geri alır ya da tamamlar.
| Araç | Ne yapar | Tipik kullanım |
|---|---|---|
| suppress(*türler) | Belirtilen istisnaları bastırır | Olmayabilecek dosyayı silmek |
| closing(nesne) | Blok sonunda nesne.close() çağırır | with desteklemeyen ama close() metodu olan nesneler |
| nullcontext(değer) | Hiçbir şey yapmaz, değeri döndürür | İsteğe bağlı bir bağlam yöneticisinin yerini tutmak |
| redirect_stdout(hedef) | sys.stdout akışını geçici olarak yönlendirir | print çıktısını yakalamak |
| redirect_stderr(hedef) | sys.stderr akışını yönlendirir | Hata çıktısını yakalamak |
| chdir(yol) | Çalışma klasörünü geçici olarak değiştirir (3.11+) | Belirli bir klasörde komut çalıştırmak |
| ExitStack() | Değişken sayıda yöneticiyi ve geri çağırmayı yönetir | Sayısı çalışma anında belli olan dosyalar |
suppress, try/except/pass yapısının kısa yazımıdır. Belgeler, hataları tamamen bastıran her mekanizma gibi bunun da yalnızca sessizce devam etmenin doğru olduğu bilinen dar durumlarda kullanılmasını önerir. closing ve nullcontext ise sırasıyla close() çağrısını garanti etmek ve "bazen yönetici var, bazen yok" durumunu tek bir with ile yazmak için kullanılır:
import os
from contextlib import suppress, closing, nullcontext
with suppress(FileNotFoundError):
os.remove("olmayan_dosya.tmp")
print("silme denendi, hata yok")
class Akis:
def oku(self):
return "veri"
def close(self):
print("akış kapatıldı")
with closing(Akis()) as a:
print(a.oku())
def isle(dosya_ya_da_yol):
if isinstance(dosya_ya_da_yol, str):
cm = open(dosya_ya_da_yol, encoding="utf-8")
else:
cm = nullcontext(dosya_ya_da_yol)
with cm as f:
return f.readline().strip()
with open("baslik.txt", "w", encoding="utf-8") as f:
f.write("Başlık satırı\n")
print(isle("baslik.txt"))
with open("baslik.txt", encoding="utf-8") as acik:
print(isle(acik), acik.closed)
# çıktı: silme denendi, hata yok
# çıktı: veri
# çıktı: akış kapatıldı
# çıktı: Başlık satırı
# çıktı: Başlık satırı FalseSon satırda dışarıdan verilen açık dosya isle() içinde kapatılmadı: nullcontext çıkışta hiçbir şey yapmadığı için dosyanın sahibi çağıran kod olarak kaldı. redirect_stdout çıktıyı yakalamak için, chdir ise çalışma klasörünü geçici değiştirmek için kullanılır. Belgelere göre ikisi de genel (global) durumu değiştirdiği için kütüphane kodunda ve çok iş parçacıklı uygulamalarda uygun değildir, ama küçük betiklerde kullanışlıdır:
import io
import os
from contextlib import redirect_stdout, chdir
from pathlib import Path
tampon = io.StringIO()
with redirect_stdout(tampon):
print("bu satır ekrana değil tampona gider")
print("yakalanan:", repr(tampon.getvalue()))
Path("alt").mkdir(exist_ok=True)
once = os.getcwd()
with chdir("alt"):
print("içeride:", Path.cwd().name)
print("geri döndü mü:", os.getcwd() == once)
# çıktı: yakalanan: 'bu satır ekrana değil tampona gider\n'
# çıktı: içeride: alt
# çıktı: geri döndü mü: TrueExitStack: değişken sayıda kaynak
Açılacak dosyaların sayısı ancak çalışma anında belliyse iç içe with yazamazsınız. ExitStack bu sorunu çözer: enter_context(yönetici) bir bağlam yöneticisine girer ve çıkışını yığına ekler, callback(fonksiyon, *args) çıkışta çalışacak sıradan bir fonksiyon kaydeder. Yığın kapanırken kayıtlar ters sırayla çalışır, bu da iç içe with deyimleriyle aynı davranıştır. Listedeki dosyalardan biri açılamazsa daha önce açılanlar yine kapatılır:
from contextlib import ExitStack
adlar = ["ocak.txt", "subat.txt", "mart.txt"]
for i, ad in enumerate(adlar, start=1):
with open(ad, "w", encoding="utf-8") as f:
f.write(f"{i * 100}\n")
with ExitStack() as yigin:
dosyalar = [yigin.enter_context(open(ad, encoding="utf-8")) for ad in adlar]
yigin.callback(print, "tüm dosyalar kapatılıyor")
print(sum(int(f.read()) for f in dosyalar))
print([f.closed for f in dosyalar])
# çıktı: 600
# çıktı: tüm dosyalar kapatılıyor
# çıktı: [True, True, True]pop_all() metodu yığındaki kayıtları yeni bir ExitStack'e aktarır ve hiçbirini çalıştırmaz; belgelerdeki tarif, bir grup dosyayı "ya hepsi ya hiçbiri" mantığıyla açmak için bunu kullanır. ExitStack nesneleri çöp toplayıcı tarafından silindiğinde kayıtlı geri çağırmaları kendiliğinden çalıştırmaz; kapanışı with ya da close() ile tetiklemeniz gerekir. Python 3.15 ile ExitStack ve AsyncExitStack, with deyimiyle tutarlı olacak şekilde __enter__() ve __exit__() metotları keyfi tanımlayıcılarla (descriptor) tanımlanmış nesneleri de kabul etmeye başladı. Bağlam yöneticisi olmayan bir nesne verirseniz 3.11'den beri TypeError alırsınız.
async with
Eşzamansız (asynchronous) kodda aynı fikir async with ile uygulanır; yöneticinin __aenter__() ve __aexit__() metotları await edilir. contextlib.asynccontextmanager, aclosing() ve AsyncExitStack bu tarafın karşılıklarıdır. Ayrıntılar için Python asyncio yazısına bakabilirsiniz.
Sık yapılan hatalar
- @contextmanager içinde try/finally unutmak: Bloktaki bir istisna
yieldsatırında fırlatılır ve temizlik kodu hiç çalışmaz. - Üreticide istisnayı yakalayıp yeniden fırlatmamak: Hata sessizce bastırılır ve program yanlış durumda devam eder.
- __exit__ metodunda istemeden True döndürmek: Örneğin bir değişkeni döndürürken onun doğru (truthy) olması tüm hataları yutar; açıkça
Falseya daNonedöndürün. - Tek kullanımlık yöneticiyi saklayıp yeniden kullanmak: Dosyalar ve
@contextmanagernesneleri ikinciwith'te çalışmaz; yöneticiyiwithsatırında oluşturun. - with bloğundan sonra dosyayı kullanmak: Blok bittiğinde dosya kapanır;
f.read()çağrısıValueError: I/O operation on closed file.verir. - suppress ile geniş türleri bastırmak:
suppress(Exception)gerçek hataları da gizler; yalnızca beklediğiniz dar türleri yazın. - redirect_stdout ve chdir'i kütüphane kodunda kullanmak: İkisi de genel durumu değiştirir ve iş parçacıklarıyla güvenli değildir.
Sık Sorulan Sorular
with open kullanınca close() çağırmak gerekir mi?
Hayır. with bloğu bittiğinde dosya otomatik olarak kapanır; bloğun içinde istisna çıksa bile. Bloktan sonra f.closed değeri True olur.
Context manager ne demek?
with deyimiyle kullanılabilen, yani __enter__() ve __exit__() metotlarını tanımlayan nesnedir. Girişte bir kaynağı hazırlar, çıkışta onu serbest bırakır ya da önceki duruma döndürür. Dosyalar, kilitler ve veritabanı bağlantıları tipik örneklerdir.
Sınıf mı yazmalıyım, @contextmanager mı kullanmalıyım?
Basit "önce şunu yap, sonra geri al" senaryoları için @contextmanager daha kısadır. Yöneticinin durum taşıması, birden fazla kez girilebilmesi (yeniden kullanılabilir olması) ya da başka metotlar sunması gerekiyorsa sınıf yazmak daha uygundur.
with içinde return yazarsam kaynak kapanır mı?
Evet. Blok return, break veya continue ile terk edildiğinde de __exit__() çağrılır; bu durumda üç argüman da None olur ve dönüş değeri yok sayılır.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Python öğrenme rehberi · Python try except · Python dosya okuma ve yazma · Python generator ve yield · Python decorator · Python dunder metotları · Python asyncio