Context manager adalah objek yang membungkus satu blok kode dengan langkah pembuka di awal dan langkah penutup (cleanup) di akhir, dan statement with menjamin langkah penutupnya selalu jalan, juga saat bloknya error. Bayangin laci kasir Warung Kopi Pak Darto yang pakai kunci per. Laci dibuka untuk menerima uang. Apa pun yang terjadi sesudahnya, penjualannya lancar atau uang pembelinya ternyata bermasalah, begitu Pak Darto melepas tangannya, lacinya terkunci sendiri. with adalah laci yang mengunci sendiri itu.
🧪 Tebakan cepat: di dalam
with laci_kasir():, barisbayar = int("15.000")gagal karena ada titiknya. Apakahlaci dikuncimasih tercetak? Danwarung jalan terusdi bawah blokwith? Tebak dulu, jawabannya ada di bagian "Kalau bloknya error".
Tanpa with, cleanup bisa terlewat
print("laci dibuka")
bayar = int("15.000")
print("terima", bayar)
print("laci dikunci")
laci dibuka
Traceback (most recent call last):
File "/home/darto/warung.py", line 2, in <module>
bayar = int("15.000")
ValueError: invalid literal for int() with base 10: '15.000'
Error di baris 2 menghentikan program, jadi laci dikunci gak pernah tercetak. Lacinya ditinggal terbuka. Dari pelajaran try-except kamu sudah kenal obatnya: taruh langkah penutup di finally, yang selalu jalan.
print("laci dibuka")
try:
bayar = int("15.000")
print("terima", bayar)
finally:
print("laci dikunci")
Sekarang laci dikunci tercetak sebelum traceback-nya. Context manager membungkus pola buka, try, finally, tutup ini jadi satu nama, supaya gak ditulis ulang tiap kali.
Membuat context manager dengan @contextmanager
from contextlib import contextmanager
@contextmanager
def laci_kasir():
print("laci dibuka") # langkah pembuka
try:
yield # di sini blok with dijalankan
finally:
print("laci dikunci") # langkah penutup, selalu jalan
with laci_kasir():
bayar = int("15000")
print("terima", bayar)
print("warung jalan terus")
| Langkah | Yang jalan | Tercetak |
|---|---|---|
masuk with laci_kasir(): |
isi fungsi sampai yield |
laci dibuka |
blok with |
bayar = int("15000"), lalu print |
terima 15000 |
| blok selesai | lanjut sesudah yield, masuk finally |
laci dikunci |
sesudah with |
baris berikutnya | warung jalan terus |
contextlibadalah module bawaan Python.from contextlib import contextmanagermengambil satu alatnya.@contextmanageryang ditulis tepat di atasdefdisebut decorator: tanda yang membungkus fungsi di bawahnya dengan kemampuan tambahan. Di sini ia mengubah fungsi generator jadi context manager.- Kode sebelum
yieldadalah langkah pembuka.yieldartinya "jalankan blokwithdi sini". Kode sesudahyieldadalah langkah penutup. - Fungsinya cuma boleh
yieldsatu kali. Ingat dari pelajaran generator: fungsi berhenti diyielddan lanjut dari situ nanti. with laci_kasir():memanggil fungsinya, menjalankan langkah pembuka, menjalankan bloknya yang menjorok, lalu langkah penutup.
Kalau bloknya error
Ganti int("15000") dengan int("15.000"):
laci dibuka
laci dikunci
Traceback (most recent call last):
File "/home/darto/warung.py", line 12, in <module>
bayar = int("15.000")
ValueError: invalid literal for int() with base 10: '15.000'
laci dikuncitetap tercetak: error-nya muncul di titikyield, danfinallymenangkap jalur itu. Langkah penutup selalu jalan.warung jalan terusgak tercetak:withgak menelan error. Setelah lacinya dikunci, error-nya tetap keluar dan programnya berhenti. Mau programnya lanjut? Bungkus dengantry/exceptseperti biasa. Itu jawaban tebakannya.
Tanpa try/finally di sekitar yield, ceritanya lain. Error-nya keluar dari yield, dan baris print("laci dikunci") sesudahnya gak pernah jalan:
laci dibuka
Traceback (most recent call last):
File "/home/darto/warung.py", line 10, in <module>
bayar = int("15.000")
ValueError: invalid literal for int() with base 10: '15.000'
Versi class: enter dan exit
Context manager juga bisa dibuat dari class dengan dua method khusus:
class LaciKasir:
def __enter__(self):
print("laci dibuka")
return self
def __exit__(self, exc_type, exc_value, traceback):
print("laci dikunci")
return False
with LaciKasir():
bayar = int("15.000")
print("terima", bayar)
print("warung jalan terus")
Output-nya sama dengan versi try/finally: laci dibuka, laci dikunci, lalu traceback ValueError.
__enter__dipanggil saat masuk blokwith. Nilai yang di-return-nya masuk ke nama sesudahas: denganwith LaciKasir() as laci:,laciberisi objeknya sendiri (self).__exit__dipanggil setiap kali blokwithselesai, baik selesai normal maupun karena error. Tiga parameternya berisi info error-nya: kalau gak ada error ketiganyaNone, kalau ada,exc_typeberisi<class 'ValueError'>danexc_valuepesan error-nya.return False(atau gak me-return apa pun) artinya error-nya diteruskan.return Trueartinya error-nya ditelan: programnya mencetakwarung jalan terusseakan gak ada apa-apa.
with open dan with bertumpuk
Context manager yang paling sering kamu pakai adalah file: with open("penjualan.txt", encoding="utf-8") as f:. open() membuka file, f adalah file itu, dan file-nya ditutup otomatis begitu blok selesai, juga saat ada error. Detailnya di pelajaran berikutnya, File I/O.
with juga bisa bertumpuk. Yang dibuka terakhir ditutup duluan:
from contextlib import contextmanager
@contextmanager
def bagian(nama):
print("buka", nama)
yield
print("tutup", nama)
with bagian("warung"):
with bagian("laci"):
print("terima 15000")
buka warung
buka laci
terima 15000
tutup laci
tutup warung
Blok di contoh ini gak bisa error, jadi tanpa try pun aman. Begitu bloknya bisa gagal, pakai try/finally. Latihan pelajaran ini memakai pola yang sama untuk tag HTML.
⚠️ Jebakan yang sering ketemu
- Lupa
@contextmanager. Fungsi denganyieldsaja cuma generator:with laci_kasir():gagal denganTypeError: 'generator' object does not support the context manager protocol. - Fungsinya masih
pass. Fungsi yang gak me-return apa-apa memberiNone, danwithmenolaknya:TypeError: 'NoneType' object does not support the context manager protocol. - Langkah penutup sesudah
yieldtanpafinally. Kalau bloknya error, langkah penutupnya terlewat. Bungkusyielddengantry/finally. __exit__me-returnTruetanpa sengaja. Semua error di bloknya hilang tanpa jejak. Me-returnFalseatau gak me-return apa pun.
TL;DR: with menjalankan langkah pembuka, bloknya, lalu langkah penutup, dan langkah penutup itu selalu jalan, juga saat bloknya error. Error-nya tetap keluar sesudahnya: with gak menelan error. Cara cepat membuatnya: @contextmanager dari contextlib di atas fungsi dengan satu yield. Sebelum yield pembuka, sesudahnya penutup, dan bungkus yield dengan try/finally supaya penutupnya tetap jalan saat error. Versi class: __enter__ jalan saat masuk dan nilai return-nya masuk ke as, __exit__ jalan setiap kali blok selesai. __exit__ yang me-return True menelan error. with bertumpuk ditutup dari yang terdalam. with open(...) as f: menutup file-nya otomatis.