Ketika sebuah fungsi menerima objek penyimpanan data, fungsi tersebut diam-diam bersandar pada kontrak: objek pasti punya method simpan() dan muat(). Pewarisan biasa seperti yang dibahas pada Chapter Inheritance tidak pernah menuntut kontrak itu terpenuhi — subclass yang lupa mengimplementasikan method akan terus dibuat dan galat baru muncul jauh di dalam pemanggilan. Abstract Base Class (ABC) mengubah kesepakatan menjadi aturan yang ditegakkan interpreter Python: class induk bisa menyatakan method wajib, dan setiap subclass yang belum melengkapinya tidak bisa dibuat instance-nya.
Kontrak dengan ABC dan @abstractmethod #
Module abc menyediakan class ABC sebagai induk dan decorator @abstractmethod sebagai penanda method wajib:
from abc import ABC, abstractmethod
class Penyimpanan(ABC):
@abstractmethod
def simpan(self, data):
...
@abstractmethod
def muat(self, nama):
...
try:
Penyimpanan()
except TypeError as e:
print("TypeError:", e)Output yang Dihasilkan:
TypeError: Can't instantiate abstract class Penyimpanan without an implementation for abstract methods 'muat', 'simpan'Mekanismenya bekerja pada dua momen berbeda. Saat class Penyimpanan dibuat, class dasar ABC mencatat setiap method bertanda @abstractmethod ke dalam himpunan __abstractmethods__. Kemudian setiap kali constructor dipanggil, interpreter memeriksa himpunan tersebut: Penyimpanan() langsung ditolak dengan TypeError karena masih ada method abstrak yang belum terimplementasi. Class seperti ini berfungsi sebagai interface — cetak biru yang tidak dimaksudkan untuk diinstansiasi.
Subclass Wajib Mengimplementasikan #
Subclass yang lengkap boleh dibuat instance-nya; subclass yang tidak lengkap ditolak. Perhatikan bahwa penolakan terjadi pada instansiasi, bukan pada saat class didefinisikan:
from abc import ABC, abstractmethod
class Penyimpanan(ABC):
@abstractmethod
def simpan(self, data): ...
@abstractmethod
def muat(self, nama): ...
class PenyimpananFile(Penyimpanan):
def simpan(self, data):
print(f"menulis {data!r} ke file")
def muat(self, nama):
return f"isi file {nama}"
class PenyimpananMemory(Penyimpanan):
def __init__(self):
self.data = {}
def simpan(self, data):
self.data["terakhir"] = data
def muat(self, nama):
return self.data.get(nama, "tidak ditemukan")
def proses(penyimpanan, data):
"""Satu fungsi, banyak implementasi (polimorfisme)."""
penyimpanan.simpan(data)
return penyimpanan.muat("laporan")
print(proses(PenyimpananFile(), "laporan Q3"))
print(proses(PenyimpananMemory(), "laporan Q3"))
class PenyimpananRusak(Penyimpanan):
def simpan(self, data):
print("hanya simpan")
try:
PenyimpananRusak()
except TypeError as e:
print("TypeError:", e)Output yang Dihasilkan:
menulis 'laporan Q3' ke file
isi file laporan
tidak ditemukan
TypeError: Can't instantiate abstract class PenyimpananRusak without an implementation for abstract method 'muat'Mari kita amati tiga peristiwa penting pada output di atas:
- Baris 37 (
print(proses(PenyimpananFile(), ...))): subclass lengkap — dua-duanya terimplementasi — bekerja normal; fungsiprosesmemanggil method tanpa peduli implementasi di baliknya, persis tema Chapter Polymorphism. - Baris 38 (
print(proses(PenyimpananMemory(), ...))): implementasi kedua menempuh jalan berbeda (dictionary di memori) dan tetap kompatibel dengan fungsi yang sama. - Baris 47 (
PenyimpananRusak()): subclass yang hanya mengimplementasikansimpandidefinisikan tanpa galat, tetapi instansiasinya ditolak denganTypeErroryang menyebut method yang kurang —muat. Pesan galat ini menunjuk persis pekerjaan yang belum diselesaikan.
Method Konkret di dalam ABC #
ABC bukan sekadar daftar method kosong. Method yang tidak ditandai @abstractmethod didefinisikan secara normal dan diwariskan ke seluruh subclass — pola yang dikenal sebagai template method: ABC menyediakan algoritme umum, subclass tinggal melengkapi bagiannya:
from abc import ABC, abstractmethod
class RataRata(ABC):
def __init__(self, nilai):
self.nilai = list(nilai)
@abstractmethod
def tampilkan(self): ...
def hitung(self):
"""Method konkret: diwariskan ke seluruh subclass."""
return sum(self.nilai) / len(self.nilai)
class SuhuHarian(RataRata):
def tampilkan(self):
return f"rata-rata suhu: {self.hitung():.1f} derajat"
print(SuhuHarian([28, 30, 31, 29]).tampilkan())Output yang Dihasilkan:
rata-rata suhu: 29.5 derajatClass SuhuHarian hanya mengimplementasikan tampilkan, lalu memakai hitung yang ditulis sekali di class induk. Method konkret menjaga logika bersama tetap di satu tempat, sementara @abstractmethod memastikan setiap subclass tetap menutup celah yang tersisa. Campuran keduanya membuat ABC berfungsi sebagai kerangka setengah jadi, bukan hanya kontrak kosong.
Virtual Subclass dengan register() #
Terkadang class yang sudah ada — misalnya dari pustaka pihak ketiga — cocok dengan sebuah ABC tetapi tidak bisa mewarisinya. Method register() memasukkan class tersebut ke dalam keluarga ABC tanpa inheritance:
from abc import ABC, abstractmethod
class Bentuk(ABC):
@abstractmethod
def luas(self): ...
class Segitiga: # TIDAK mewarisi Bentuk
def __init__(self, alas, tinggi):
self.alas = alas
self.tinggi = tinggi
def luas(self):
return 0.5 * self.alas * self.tinggi
print(issubclass(Segitiga, Bentuk))
Bentuk.register(Segitiga) # virtual subclass
print(issubclass(Segitiga, Bentuk))
print(isinstance(Segitiga(6, 4), Bentuk))
print(Segitiga(6, 4).luas())Output yang Dihasilkan:
False
True
True
12.0Sebelum registrasi, issubclass(Segitiga, Bentuk) bernilai False. Setelah Bentuk.register(Segitiga), hubungan nominal terbentuk: issubclass dan isinstance melaporkan True, sehingga kode yang memeriksa tipe menerima objek Segitiga.