Menu

Context manager di Python: pernyataan with dijelaskan

Apa yang sebenarnya dilakukan pernyataan with: pembersihan otomatis untuk file, lock, koneksi database, dan apa pun yang perlu ditutup dengan andal.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

Pernyataan yang membereskan dirinya sendiri

Setiap resource yang kamu buka di program, seperti file, koneksi jaringan, handle database, atau lock, perlu ditutup setelah selesai dipakai. Kalau lupa, memori bocor, lock yang masih dipegang menghalangi proses lain, atau file rusak saat program crash. Pernyataan with di Python menangani ini untukmu.

Pola yang pertama kali dipelajari semua orang adalah membaca file:

with open("notes.txt") as f:
    contents = f.read()
    print(contents)

Ada dua hal yang terjadi otomatis. Saat masuk, open() memberimu objek file yang diikat ke f. Saat keluar, entah bloknya selesai normal, return lebih awal, atau memunculkan exception, Python memanggil f.close() untukmu.

Hanya itu. Itulah inti dari with.

Apa yang digantikan oleh "with"

Sebelum ada context manager, kode aman yang setara adalah try/finally:

f = open("notes.txt")
try:
    contents = f.read()
    print(contents)
finally:
    f.close()

Lima baris hanya untuk "baca file lalu tutup setelah selesai." Kalikan dengan setiap open di program yang lebih besar, dan kamu akan paham daya tariknya. with lebih singkat, lebih sulit dipakai dengan salah, dan tidak mungkin lupa membereskannya.

Membuka beberapa resource

Kamu bisa mengikat beberapa context manager dalam satu with:

with open("input.txt") as src, open("output.txt", "w") as dst:
    dst.write(src.read().upper())

Kedua file dibuka saat masuk, dan keduanya ditutup saat keluar. Kalau open pertama berhasil lalu yang kedua memunculkan exception, Python tetap menutup yang pertama: mekanismenya menangani persiapan yang baru setengah jalan dengan benar.

Untuk daftar resource yang lebih panjang, bentuk dengan tanda kurung (Python 3.10+) lebih jelas:

with (
    open("a.txt") as a,
    open("b.txt") as b,
    open("c.txt") as c,
):
    ...

Apa sebenarnya context manager itu

Objek apa pun yang mendefinisikan __enter__ dan __exit__ adalah context manager. Protokolnya sangat sederhana:

  • __enter__(self) berjalan saat blok with dimulai. Nilai kembaliannya adalah apa yang diikat oleh as name.
  • __exit__(self, exc_type, exc_value, traceback) berjalan saat blok berakhir, apa pun caranya. Kalau keluarnya disebabkan oleh exception, info exception itu dikirim ke method ini, sehingga context manager bisa memeriksanya atau meredamnya.

Ini contoh minimal yang mengukur waktu blok yang dibungkusnya:

with Timer(): membuat objeknya, memanggil __enter__, menjalankan badan blok, lalu memanggil __exit__. Tidak ada file, tidak ada lock: hanya pembungkus kecil untuk "lakukan sesuatu, lalu ukur berapa lama waktunya."

Jalan pintas contextlib.contextmanager

Membuat class untuk setiap context manager terlalu berat. contextlib.contextmanager mengubah sebuah fungsi generator menjadi context manager, dengan satu yield sebagai pemisah antara "sebelum" dan "sesudah":

Semua yang ada sebelum yield adalah perilaku __enter__. Semua yang ada sesudahnya adalah __exit__. try/finally memastikan pembersihan tetap berjalan walaupun badan blok memunculkan exception.

Kebanyakan context manager buatan sendiri yang akan kamu tulis cocok dengan bentuk ini. Pakai dulu bentuk decorator ini; beralih ke class hanya kalau kamu butuh sesuatu yang tidak bisa diungkapkan bentuk generator.

Mengubah sesuatu untuk sementara

Bentuk yang sering muncul: atur sesuatu, pakai, lalu kembalikan. Context manager mengungkapkannya dengan rapi:

Semua pola "atur, lalu kembalikan", seperti environment variable, tingkat detail logging, feature flag, atau test fixture, cocok secara alami dengan context manager. Pemanggil tidak perlu ingat untuk mengembalikan apa pun.

Meredam exception

Method __exit__ bisa mengembalikan True untuk memberi tahu Python "aku sudah menangani exception ini; telan saja." Ini jarang dan biasanya pertanda ada yang kurang beres, tapi beginilah cara kerja contextlib.suppress:

suppress(FileNotFoundError) membuat FileNotFoundError tidak berpengaruh apa-apa. Pakai untuk operasi yang memang opsional, "coba ini, tidak masalah kalau gagal." Jangan pakai untuk membungkam exception yang belum kamu pikirkan.

Context manager lain yang akan kamu temui

Begitu kamu mulai memperhatikan, context manager muncul di banyak tempat dalam standard library:

import threading
from pathlib import Path

# Locks - guarantee release even if the critical section raises.
lock = threading.Lock()
with lock:
    ...

# tempfile - delete the temp file when you're done.
from tempfile import TemporaryDirectory
with TemporaryDirectory() as tmp:
    path = Path(tmp) / "scratch.txt"
    path.write_text("hello")

# Database connections - close the connection (or end the transaction).
import sqlite3
with sqlite3.connect(":memory:") as conn:
    conn.execute("CREATE TABLE t (x INTEGER)")

Library pihak ketiga mengikuti konvensi yang sama. Kalau kamu melihat with something as x:, hampir selalu artinya "pakai x selama blok ini, lalu bereskan setelahnya."

Kapan tidak memakai with

  • Kalau sebenarnya tidak ada persiapan dan pembersihan. Membungkus kode sembarangan dengan context manager tanpa alasan hanya menambah kebisingan.
  • Kalau kamu butuh resource itu di banyak blok yang tidak saling terkait. Menahan sebuah with terbuka sepanjang umur skrip yang panjang bisa menyembunyikan cakupan pembersihan yang sebenarnya. Pertimbangkan class yang memiliki resource itu.
  • Kalau decorator lebih cocok. Beberapa pola yang berulang (retry, log, pengukuran waktu) lebih enak dibaca sebagai @decorator pada fungsi daripada with ...: di dalamnya. Pilih yang lebih mudah dibaca di tempat pemanggilan.

Sebagian besar waktu, with adalah pilihan yang tepat. Pengecualiannya yang jarang itu mudah dikenali begitu kamu mencarinya.

Berikutnya: bekerja dengan file sungguhan

Sekarang kamu tahu mekanisme di balik with open(...) as f:, yaitu konteks tempat kamu akan memakainya sembilan puluh persen waktu. Bab berikutnya memakainya untuk membaca, menulis, dan menjelajahi file di disk.

Pertanyaan yang sering diajukan

Apa yang dilakukan with open di Python?

with open(path) as f: membuka file dan mengikatnya ke f selama blok berjalan. Saat blok selesai, baik secara normal maupun karena exception, Python otomatis menutup file itu. Kamu tidak perlu f.close(); pernyataan with menjaminnya.

Kenapa memakai with, bukan open() biasa?

Karena with tetap menutup file walaupun ada exception di tengah-tengah blok. Dengan open() biasa, kamu sendiri yang harus ingat memanggil close() di setiap jalur kode, termasuk jalur error. with lebih aman dan lebih singkat.

Bagaimana cara membuka beberapa file dengan satu pernyataan with?

Pisahkan context manager-nya dengan koma: with open('a.txt') as a, open('b.txt') as b:. Kedua file dibuka saat masuk dan ditutup saat keluar, dengan urutan terbalik. Cara ini menggantikan pernyataan with yang bersarang saat kamu butuh beberapa resource sekaligus.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai