Kursus Python / Decorator, wraps, dan context manager untuk pembersihan

Decorator membungkus perilaku pemanggilan fungsi, sedangkan context manager mengatur persiapan dan pembersihan di sekitar sebuah blok. Keduanya membantu memisahkan pekerjaan umum yang berulang, tetapi nilai kembali dan penyebaran exception harus tetap sesuai kontrak yang diinginkan. Latihan ini menggabungkan pembungkus fungsi dengan list sementara yang dibersihkan setelah blok selesai.

Membedakan pembungkus fungsi dan pengelolaan umur

Penulisan @announce bukan komentar atau label dekoratif. Setelah fungsi didefinisikan, Python memberikan fungsi itu kepada announce dan menghubungkan namanya dengan hasil yang dikembalikan. Bentuk sederhananya setara dengan total = announce(total), sehingga pemanggilan berikutnya melewati wrapper.

Wrapper dapat mengerjakan sesuatu sebelum atau sesudah fungsi asli, misalnya mencatat awal pemanggilan. Ia perlu meneruskan argumen, mengembalikan hasil, dan menangani exception secara sengaja. Jika satu bagian hilang, perilaku pemanggil dapat berubah walaupun perhitungan fungsi asli benar.

Context manager memiliki tujuan berbeda: mengelola pekerjaan sebelum masuk dan setelah keluar dari blok with. Untuk resource nyata, pekerjaan keluar bisa menutup file atau mengakhiri koneksi. Contoh ini memakai list agar pembersihan mudah dilihat tanpa membuat file atau membuka layanan luar.

functools.wraps membantu mempertahankan metadata seperti nama dan dokumentasi fungsi yang dibungkus. Ia tidak otomatis membuktikan bahwa semua kontrak tipe, validasi, dan keamanan tetap benar. Gunakan alat ini untuk menjaga identitas informasi, sambil tetap menguji perilaku wrapper.

Menjalankan contoh lengkap

Buat folder latihan baru dan simpan seluruh kode berikut sebagai wrappers.py dengan UTF-8. Gunakan Python 3.12 atau lebih baru; modul yang diperlukan tersedia dalam pustaka standar. Jalankan python wrappers.py pada terminal lingkungan proyek, atau gunakan python3 jika itulah nama interpreter Anda.

from functools import wraps
from contextlib import contextmanager

def announce(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("start")
        return func(*args, **kwargs)
    return wrapper

@contextmanager
def temporary_items():
    items = []
    try:
        yield items
    finally:
        items.clear()
        print("cleared")

@announce
def total(amounts):
    return sum(amounts)

with temporary_items() as items:
    items.extend([3500, 8000])
    print(total(items))
print(total.__name__)
print(items)

Hasil yang diharapkan:

start
11500
cleared
total
[]

Wrapper mencetak start lalu mengembalikan total 11500. Saat blok with selesai, bagian finally membersihkan list dan mencetak cleared. Nama fungsi tetap total karena metadata dipertahankan oleh wraps. Variabel items masih menunjuk list yang sama setelah blok, tetapi isinya sudah kosong.

Dengan kata lain, with tidak otomatis menghapus nama variabel atau membuat salinan list. Pembersihan terjadi karena program secara eksplisit menjalankan items.clear(). Jika referensi lain menunjuk list yang sama, referensi itu juga akan melihat list kosong setelah pembersihan.

Menelusuri nilai dan exception melalui kedua lapisan

Fungsi generator yang diberi @contextmanager harus yield tepat sekali. Kode sebelum yield berjalan saat masuk; nilai yang di-yield menjadi objek sesudah kata as. Setelah isi with selesai normal atau menimbulkan exception, kontrol kembali ke jalur keluar generator. Menaruh pembersihan pada finally membuatnya tetap dijalankan ketika isi blok gagal.

Jika exception ditangkap di sekitar yield lalu tidak dilempar lagi, context manager dapat menganggapnya telah ditangani. Karena itu, jangan menelan kegagalan sekadar untuk mencetak log. Jika tujuannya hanya pencatatan atau pembersihan, teruskan exception supaya pemanggil mengetahui pekerjaan tidak berhasil. Rincian tersedia pada contextlib.contextmanager.

Pada decorator, return func(*args, **kwargs) mempertahankan nilai kembali fungsi asli dan meneruskan argumen posisi maupun keyword. Jika return dihapus tetapi pemanggilan func tetap ada, perhitungan masih dijalankan namun wrapper mengembalikan None. Pesan start saja tidak cukup untuk membuktikan hasil fungsi dipertahankan.

Perhatikan juga titik kegagalan. Pada contoh announce, fungsi asli yang melempar exception akan meneruskannya keluar karena wrapper tidak menangkapnya. Context manager di lapisan luar tetap membersihkan list. Ini menghubungkan kontrak wrapper dengan try, finally, dan penyebaran exception.

Menguji pembersihan dan memilih abstraksi yang sesuai

  1. Di dalam blok with, setelah items diisi, tambahkan sementara raise ValueError("demo"). Jalankan dan periksa bahwa cleared tetap dicetak sebelum traceback. Baris setelah with tidak dijalankan jika exception tidak ditangkap.
  2. Untuk memeriksa list sesudah kegagalan, tangkap ValueError di luar blok with pada salinan latihan, lalu periksa list yang sama sudah kosong. Jangan menangkapnya di dalam context manager bila tujuannya menguji propagasi.
  3. Pada wrapper, hapus sementara kata return di depan pemanggilan func. Bandingkan keluaran: nilai total yang dicetak menjadi None walaupun start dan cleared tetap muncul.
  4. Pulihkan return dan hapus exception percobaan. Jalankan ulang sampai lima baris keluaran kembali sama dengan hasil awal.

Bandingkan juga pemanggilan total(amounts=[3500, 8000]) dengan bentuk posisi pada contoh. Hasil keduanya harus sama. Nama fungsi dan atribut __wrapped__ dapat diperiksa untuk memastikan hubungan wrapper dengan fungsi asal; metadata yang benar tetap perlu dilengkapi pemeriksaan nilai dan exception. Lihat functools.wraps.

Context manager berbasis generator dibuat kembali melalui pemanggilan fungsi untuk setiap penggunaan. Jangan memakai ulang satu instance yang sudah selesai seolah-olah selalu dapat dimasuki lagi. Jika beberapa operasi membutuhkan resource terpisah, buat konteks baru dan tetapkan urutan pembersihannya secara jelas.

Decorator percobaan ulang dapat mengulang permintaan yang mengubah data dan membuat transaksi ganda. Kebijakan retry memerlukan pemahaman identitas dan apakah operasi aman diulang; itu berbeda dari membersihkan resource. Untuk pekerjaan kecil yang hanya muncul sekali, kode langsung dengan try/finally dapat lebih mudah dibaca daripada abstraksi baru.