Program
Motivasi
Anda mungkin sudah familiar dengan pernyataan with, cara ringkas untuk menangani sumber daya di Python. Namun, pernahkah Anda bertanya-tanya bagaimana cara kerjanya di balik layar? Kekayaan dari kata kunci with berasal dari context manager.
Context manager adalah pola desain fundamental di Python yang menyediakan pendekatan terstruktur untuk manajemen sumber daya. Pola ini memastikan sumber daya diakuisisi, digunakan dengan benar, lalu akhirnya dilepas atau dibersihkan, bahkan saat terjadi error atau exception.
Hal ini menjadi sangat penting saat berurusan dengan sumber daya seperti file, koneksi jaringan, atau handle basis data. Dengan menggunakan context manager, kita dapat menulis kode yang lebih bersih dan andal, membebaskan diri dari kekhawatiran lupa menutup file atau melepas lock.
Dalam tutorial ini, kita akan melampaui context manager bawaan Python dan belajar menulis milik kita sendiri.
Memahami Context Manager di Python
Di balik layar, context manager adalah objek yang mendefinisikan dua metode khusus: __enter__ dan __exit__. Metode __enter__ dipanggil saat Anda memasuki blok with, dan nilai kembalinya ditetapkan ke sebuah variabel di dalam blok tersebut. Di sisi lain, metode __exit__ dipanggil saat blok with keluar, terlepas dari apakah selesai normal atau dengan exception.
Struktur ini memastikan penanganan sumber daya yang tepat. Mari lihat beberapa context manager bawaan di Python untuk memahaminya lebih lanjut:
Ambil contoh klasik membuka file:
with open('file.txt', 'r') as file:
data = file.read()
Di sini, open('file.txt', 'r') bertindak sebagai context manager. Saat Anda memasuki blok with, metode enter dari objek file dipanggil, membuka file dan menugaskannya ke variabel file. Anda kemudian dapat menggunakan file.read() untuk mengakses konten file.
Berikutnya, metode __exit__ dari objek file dijamin dipanggil saat blok with keluar, bahkan jika terjadi exception. Metode ini menangani penutupan file, memastikan Anda tidak meninggalkan handle file yang tetap terbuka.
2. Penguncian Thread
Melebihi file, context manager juga dapat digunakan untuk sinkronisasi thread menggunakan threading.Lock():
import threading
lock = threading.Lock()
with lock:
# Critical section
print("This code is executed under lock protection.")
Di sini, lock adalah objek threading.Lock, context manager lainnya. Saat Anda memasuki blok with, metode __enter__ memperoleh lock, memastikan hanya satu thread yang dapat mengeksekusi critical section pada satu waktu. Terakhir, metode __exit__ melepas lock saat keluar dari blok with, memungkinkan thread lain untuk melanjutkan.
3. Koneksi Basis Data
Demikian pula, context manager dapat mengelola koneksi basis data:
import sqlite3
with sqlite3.connect('database.db') as connection:
cursor = connection.cursor()
cursor.execute("SELECT * FROM table")
rows = cursor.fetchall()
Panggilan sqlite3.connect('database.db') adalah context manager. Memasuki blok with membangun koneksi ke basis data, menugaskannya ke connection. Anda kemudian dapat menggunakan cursor untuk berinteraksi dengan basis data. Metode __exit__ menjamin koneksi ditutup saat blok with keluar, mencegah kebocoran sumber daya.
4. Soket Jaringan
Context manager bahkan dapat menangani komunikasi jaringan:
import socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(('localhost', 8080))
s.sendall(b'Hello, world')
data = s.recv(1024)
Di sini, socket.socket(socket.AF_INET, socket.SOCK_STREAM) membuat objek soket yang bertindak sebagai context manager. Metode __enter__ membuat soket, dan di dalam blok with, Anda dapat tersambung, mengirim data, dan menerima data. Metode __exit__ memastikan soket ditutup dengan benar saat selesai.
5. Pemindaian Direktori
Kali ini, os.scandir('.') menyediakan cara untuk mengiterasi entri direktori:
with os.scandir('.') as entries:
for entry in entries:
print(entry.name)
os.scandir('.') bertindak sebagai context manager di sini. Metode __enter__ membuka pemindaian direktori, dan Anda dapat mengiterasi entri di dalam blok with. Metode __exit__ membersihkan pemindaian direktori saat keluar.
Seperti yang Anda lihat, pernyataan with yang didukung context manager menyederhanakan manajemen sumber daya dengan menangani alokasi dan dealokasi secara otomatis.
Menulis context manager kustom di Python
Sekarang, mari akhirnya lihat cara menulis manager kustom Anda sendiri untuk mendapatkan kontrol yang lebih rinci atas manajemen sumber daya.
Ada dua pendekatan utama untuk menulis context manager kustom: berbasis kelas dan berbasis fungsi.
Pendekatan Berbasis Kelas
Pendekatan berbasis kelas adalah metode paling terstruktur dan fleksibel untuk menulis context manager. Di sini, Anda mendefinisikan kelas yang mengimplementasikan metode khusus __enter__ dan __exit__.
Mari lihat contoh kelas Timer yang mengukur waktu eksekusi:
import time
class Timer:
def __enter__(self):
self.start_time = time.time()
return self
def __exit__(self, exc_type, exc_value, traceback):
self.end_time = time.time()
elapsed_time = self.end_time - self.start_time
print(f"Elapsed time: {elapsed_time} seconds")
# Example usage
if __name__ == "__main__":
with Timer() as timer:
# Code block to measure the execution time
time.sleep(2) # Simulate some time-consuming operation
Elapsed time: 2.002082347869873 seconds
Di sini, kelas Timer mendefinisikan metode __enter__ untuk menangkap waktu mulai saat Anda memasuki blok with. Ia mengembalikan self agar objek dapat diakses di dalam blok. Metode __exit__ menghitung waktu berlalu saat keluar dari blok with dan mencetaknya.
Pendekatan Berbasis Fungsi
Jika Anda lebih menyukai pendekatan yang lebih ringkas meski mungkin mengorbankan fleksibilitas, pendekatan berbasis fungsi bisa menjadi pilihan yang baik. Di sini, Anda menggunakan dekorator contextmanager dari modul contextlib untuk mengonversi fungsi apa pun menjadi context manager.
Mari lihat cara kerjanya:
import time
from contextlib import contextmanager
@contextmanager
def timer():
start_time = time.time()
yield
end_time = time.time()
elapsed_time = end_time - start_time
print(f"Elapsed time: {elapsed_time} seconds")
# Example usage
if __name__ == "__main__":
with timer():
time.sleep(2)
Elapsed time: 2.0020740032196045 seconds
Dekorator @contextmanager mengubah fungsi timer menjadi context manager. Di dalam fungsi, start_time ditangkap, dan pernyataan yield menjeda eksekusi, memungkinkan kode di dalam blok with dijalankan.
Terakhir, fungsionalitas __exit__ dicapai dengan menangkap waktu akhir dan mencetak waktu berlalu.
Intinya, Anda menulis logika untuk __enter__ sebelum kata kunci yield sedangkan logika untuk __exit__ berada setelahnya. Kedua pendekatan mencapai hasil yang sama, tetapi pilihan bergantung pada preferensi Anda terhadap struktur dan keterbacaan.
Contoh Praktis Context Manager
Sekarang setelah Anda memahami cara menulis context manager kustom di Python, mari lihat beberapa contoh praktis di mana ini sangat berguna.
Mengelola Operasi File
Operasi file adalah tugas umum di banyak aplikasi. Baik membaca maupun menulis ke file, pengelolaan yang tepat memastikan integritas data dan efisiensi sumber daya. Mari pertimbangkan skenario di mana kita ingin menulis teks ke file menggunakan context manager kustom.
class FileManager:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_value, traceback):
if self.file:
self.file.close()
# Example usage
if __name__ == "__main__":
with FileManager("example.txt", "w") as file:
file.write("Hello, world!\n")
Dalam contoh ini, kita mendefinisikan kelas FileManager untuk meniru fungsi open di Python yang menangani operasi file. Mari uraikan apa yang dilakukan setiap metode:
__init__(self, filename, mode): Metode ini adalah konstruktor kelas. Ia menginisialisasi objekFileManagerdengan nama file dan mode yang diberikan, yang menentukan file yang akan dibuka dan mode pembukaannya (misalnya, baca, tulis, tambah).__enter__(self): Metode ini dipanggil saat memasuki blokwithseperti biasa. Ia membuka file yang ditentukan olehfilenamedalam mode yang ditentukan olehmode. Objek file yang dibuka ditetapkan keself.filedan dikembalikan untuk digunakan di dalam blokwith.__exit__(self, exc_type, exc_value, traceback): Metode ini dipanggil saat keluar dari blok with, terlepas dari apakah terjadi exception di dalam blok. Ia memastikan file ditutup dengan benar dengan memanggilself.file.close()jika self.file bukanNone. Ini mencegah kebocoran sumber daya dan memastikan pembersihan yang tepat.
Jika Anda memeriksa isi example.txt setelah mengeksekusi kode di atas, Anda akan melihat teks yang tertulis:
$ cat example.txt
Hello, world!
Tentu saja, keuntungan kelas FileManager adalah Anda dapat mengimplementasikan logika kustom apa pun untuk bekerja dengan file. Misalnya, Anda dapat memodifikasinya agar kelas menyimpan log file mana yang dibuka atau ditutup ke sebuah file teks.
import datetime
class FileManager:
def __init__(self, filename, mode, log_filename="file_log.txt"):
self.filename = filename
self.mode = mode
self.log_filename = log_filename
self.file = None
def log_action(self, action):
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with open(self.log_filename, "a") as log_file:
log_file.write(f"{timestamp} - {action}: {self.filename}\n")
def __enter__(self):
self.file = open(self.filename, self.mode)
self.log_action("Opened")
return self.file
def __exit__(self, exc_type, exc_value, traceback):
if self.file:
self.file.close()
self.log_action("Closed")
# Example Usage
with FileManager("example.txt", "r") as file:
content = file.read()
print(content)
Hello, world!
Dalam versi yang dimodifikasi ini:
- Modul
datetimediimpor untuk bekerja dengan stempel waktu. - Metode
log_actionditambahkan untuk mencatat aksi file. - Parameter
log_filenameditambahkan untuk menentukan nama file untuk file log. Nilainya default ke'file_log.txt'. - Nama file dan aksi (dibuka atau ditutup) ditambahkan ke file log menggunakan metode
log_actiondi dalam metode__enter__dan__exit__. - Di dalam metode
log_action,datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")digunakan untuk mendapatkan stempel waktu saat ini dalam format"YYYY-MM-DD HH:MM:SS". - Stempel waktu kemudian disertakan dalam log bersama nama file dan aksinya.
Sekarang, jika kita mencetak isi file_log.txt, kita akan melihat lognya:
$ cat file_log.txt
2024-04-03 12:37:02 - Opened: example.txt
2024-04-03 12:37:02 - Closed: example.txt
Contoh penggunaan praktis lainnya yang dapat kita implementasikan sebagai context manager kustom adalah alat manajemen basis data SQLite:
import sqlite3
class DatabaseConnection:
def __init__(self, database_name):
self.database_name = database_name
self.connection = None
def __enter__(self):
self.connection = sqlite3.connect(self.database_name)
return self.connection
def __exit__(self, exc_type, exc_value, traceback):
if self.connection:
self.connection.close()
Sekali lagi, ini meniru fungsi sqlite3.connect, tetapi dapat ditingkatkan lebih lanjut dengan menambahkan logika manajemen basis data kustom. Mari periksa apakah manager bekerja dengan beberapa fungsi contoh:
# Example usage
def create_table():
with DatabaseConnection("example.db") as connection:
cursor = connection.cursor()
cursor.execute(
"""CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
username TEXT,
email TEXT)"""
)
def insert_data(username, email):
with DatabaseConnection("example.db") as connection:
cursor = connection.cursor()
cursor.execute(
"INSERT INTO users (username, email) VALUES (?, ?)", (username, email)
)
connection.commit()
def fetch_data():
with DatabaseConnection("example.db") as connection:
cursor = connection.cursor()
cursor.execute("SELECT * FROM users")
return cursor.fetchall()
if __name__ == "__main__":
create_table()
insert_data("john_doe", "john@example.com")
insert_data("jane_doe", "jane@example.com")
users = fetch_data()
print("Users in the database:")
for user in users:
print(user)
Users in the database:
(1, 'john_doe', 'john@example.com')
(2, 'jane_doe', 'jane@example.com')
(3, 'john_doe', 'john@example.com')
(4, 'jane_doe', 'jane@example.com')
Kita dapat menulis kode yang jauh lebih mudah dipelihara dengan memecah operasi basis data menjadi fungsi-fungsi dan menggunakan manager kustom yang baru saja kita buat.
Topik Lanjutan Terkait Context Manager di Python
Di bagian ini, kita akan membahas topik lanjutan terkait context manager di Python. Ini mencakup penanganan error dan nesting context manager.
Penanganan error
Penanganan error adalah aspek penting pemrograman, dan context manager di Python menyediakan cara yang nyaman untuk mengelola error dalam operasi manajemen sumber daya.
Dalam contoh kelas FileManager, penanganan error dapat diimplementasikan di dalam metode __exit__ untuk memastikan pembersihan yang tepat bahkan jika terjadi exception. Misalnya, jika terjadi error saat menulis data ke file (ketika berada di dalam blok with), kita tetap ingin memastikan file ditutup untuk mencegah kebocoran sumber daya. Kita dapat memodifikasi metode __exit__ sebagai berikut:
def __exit__(self, exc_type, exc_value, traceback):
if self.file:
self.file.close()
if exc_type is not None:
print(f"An error occurred: {exc_value}")
Di sini, kita terlebih dahulu menutup file menggunakan self.file.close() untuk memastikan pembersihan yang tepat. Lalu, kita periksa apakah terjadi exception (exc_type is not None). Jika ya, kita mencetak pesan error untuk memberikan umpan balik tentang apa yang salah.
Berikut ini:
exc_typeadalah kelas exception, sepertiAttributeErrordanIndexError.exc_valueadalah instance exception dengan pesannya.tracebackadalah traceback lengkap dari error.
Mari jalankan kelas FileManager dengan kode yang sengaja salah:
with FileManager("example.txt", "r") as file:
# Simulate a sample error
content = file.read()
print(content[150])
An error occurred: string index out of range
Jadi, untuk menerapkan logika kustom saat menangani exception di manager kustom Anda, berikut alur contoh yang dapat Anda ikuti:
def __exit__(self, exc_type, exc_value, traceback):
# Perform cleanup
...
# Handle the exception
if isinstance(exc_value, AttributeError):
# Handle AttributeError here
print(f"Exception message: {exc_value}")
# Handle another exception
elif isinstance(exc_value, AnotherError):
...
Menyusun (nesting) context manager
Menyusun context manager memungkinkan Anda mengelola beberapa sumber daya secara bersamaan, masing-masing dengan context manager-nya sendiri, dalam satu pernyataan with. Ini sangat berguna saat menangani operasi kompleks yang melibatkan banyak sumber daya. Mari lihat bagaimana kita dapat menyusun context manager dalam contoh kita.
Pertimbangkan skenario di mana kita ingin membuka file dan melakukan operasi basis data dalam konteks yang sama. Mungkin, kita ingin memasukkan konten file CSV sebagai baris ke dalam basis data. Kita dapat mencapainya dengan menyusun context manager FileManager dan DatabaseConnection dalam satu pernyataan with:
if __name__ == "__main__":
with FileManager("example.txt", "w") as file, DatabaseConnection("example.db") as connection:
...
Dalam contoh ini, context manager FileManager dan DatabaseConnection disusun dalam pernyataan with yang sama. Ini memungkinkan kita untuk membuka file dan membangun koneksi basis data secara bersamaan. Operasi penulisan file dan basis data selanjutnya dilakukan dalam konteks gabungan ini.
Satu hal yang perlu diingat adalah keterbacaan kode. Saya menyarankan jumlah konteks simultan tidak melebihi dua.
Kesimpulan
Context manager di Python menawarkan pendekatan terstruktur untuk manajemen sumber daya di dalam pernyataan with, memastikan alokasi dan dealokasi sumber daya yang tepat.
Sepanjang tutorial ini, kita telah mempelajari dasar-dasar context manager, meninjau contoh praktis, dan membahas topik lanjutan seperti penanganan error dan penyusunan. Dengan menguasai penulisan context manager kustom, Anda dapat menulis kode yang lebih bersih dan andal, meningkatkan penanganan error, dan mengelola sumber daya secara efisien.
Topik penulisan context manager kustom berada di bawah payung kategori “Advanced Python”. Berikut beberapa sumber terkait untuk meningkatkan keterampilan Anda:
Saya adalah kreator konten data science dengan pengalaman lebih dari 2 tahun dan salah satu dengan jumlah pengikut terbesar di Medium. Saya suka menulis artikel mendalam tentang AI dan ML dengan sedikit gaya sarkastik—karena harus ada sesuatu untuk membuatnya tidak terlalu membosankan. Saya telah menghasilkan lebih dari 130 artikel dan satu kursus di DataCamp, dengan satu lagi sedang dalam proses. Konten saya telah dilihat oleh lebih dari 5 juta pasang mata, dan 20 ribu di antaranya menjadi pengikut di Medium dan LinkedIn.

