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:

python
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:

text
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:

python
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:

text
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:

  1. Baris 37 (print(proses(PenyimpananFile(), ...))): subclass lengkap — dua-duanya terimplementasi — bekerja normal; fungsi proses memanggil method tanpa peduli implementasi di baliknya, persis tema Chapter Polymorphism.
  2. Baris 38 (print(proses(PenyimpananMemory(), ...))): implementasi kedua menempuh jalan berbeda (dictionary di memori) dan tetap kompatibel dengan fungsi yang sama.
  3. Baris 47 (PenyimpananRusak()): subclass yang hanya mengimplementasikan simpan didefinisikan tanpa galat, tetapi instansiasinya ditolak dengan TypeError yang 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:

python
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:

text
rata-rata suhu: 29.5 derajat

Class 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:

python
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:

text
False
True
True
12.0

Sebelum 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.