Context Managers — Python

Context Managers & with Context manager adalah objek yang menjamin setup dan cleanup berjalan dengan benar — bahkan saat terjadi error. Kamu pasti sudah…

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():, baris bayar = int("15.000") gagal karena ada titiknya. Apakah laci dikunci masih tercetak? Dan warung jalan terus di bawah blok with? 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

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'

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.

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

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.

Yang akan kamu pelajari