Pendahuluan — dari “bisa quack” ke kontrak tertulis
Di Polymorphism (#45) kita andalkan antarmuka bersama: kalau object punya info(), loop tetap pendek. Duck typing fleksibel — tapi lupa method baru ketahuan saat runtime (AttributeError).
Abstraction di sini berarti: tulis kontrak “apa yang wajib bisa dilakukan”, lalu biarkan tiap class mengisi “bagaimana”-nya. Di Python, alat resmi untuk itu adalah ABC (Abstract Base Class) dari modul abc.
Prasyarat: Selesai Polymorphism (#45) (nyaman dengan loop
item.info(), duck typing, dan kapanisinstance). Fondasi: Inheritance (#44) · Encapsulation (#43) · Attribute (#42) · Class & Object (#41) · Mengenal OOP (#40).
Kenapa perlu kontrak?
Bayangkan sistem perpustakaan: beberapa tipe item bisa dipinjam. Tanpa kontrak, seorang rekan bisa menulis class baru yang “hampir cocok” tapi lupa kembalikan(). Loop peminjaman baru pecah di produksi.
ABC menjawab: “class ini harus mengimplementasikan method berikut — atau Python menolak menginstansiasinya.” Error muncul lebih awal (saat buat object), bukan di tengah malam saat user pinjam buku.
- Duck typing = percaya kemampuan muncul saat dipanggil
- ABC = tulis daftar kemampuan wajib + cek sebelum object hidup
Masalah tanpa ABC — lupa method
Tanpa kontrak formal, class “nyaris siap” tetap bisa dibuat. Bug baru muncul saat method dipanggil:
class BukuFisikSalah:
def __init__(self, judul):
self.judul = judul
self.dipinjam = False
def pinjam(self):
self.dipinjam = True
return f"pinjam {self.judul}"
# lupa kembalikan()
item = BukuFisikSalah("ESP32 Praktis")
print(item.pinjam())
# print(item.kembalikan()) # AttributeError: ... has no attribute 'kembalikan'
print("object hidup — bug method hilang belum terlihat")
Object sudah “hidup”. Di tim besar, itu terlalu terlambat.
ABC pertama — kontrak Pinjaman
Modul standar abc menyediakan ABC dan dekorator @abstractmethod. Class abstract mendefinisikan method tanpa isi penuh (sering pakai ... atau pass):
from abc import ABC, abstractmethod
class Pinjaman(ABC):
"""Kontrak: apa pun yang bisa dipinjam wajib punya pinjam & kembalikan."""
@abstractmethod
def pinjam(self):
...
@abstractmethod
def kembalikan(self):
...
print(Pinjaman.__name__)
# Pinjaman() # TypeError: Can't instantiate abstract class Pinjaman...
Poin penting: ABC sendiri tidak diinstansiasi. Ia cetakan kontrak, bukan object bisnis.
Subclass belum lengkap → TypeError
Warisi Pinjaman tapi tinggalkan satu method abstract — Python menolak membuat object:
from abc import ABC, abstractmethod
class Pinjaman(ABC):
@abstractmethod
def pinjam(self):
...
@abstractmethod
def kembalikan(self):
...
class BukuBelumSiap(Pinjaman):
def __init__(self, judul):
self.judul = judul
def pinjam(self):
return f"pinjam {self.judul}"
# kembalikan masih abstract
try:
BukuBelumSiap("ESP32 Praktis")
except TypeError as err:
print("TypeError:", err)
Output kurang lebih: TypeError: Can't instantiate abstract class BukuBelumSiap without an implementation for abstract method 'kembalikan' (teks bisa sedikit beda antar versi Python). Itu sinyal bagus: “kontrak belum dipenuhi.”
Isi kontrak — dua implementasi berbeda
Sekarang dua class memenuhi kontrak dengan cara masing-masing (polymorphism + abstraction). Di sini kita tidak mewarisi Buku dari artikel sebelumnya — fokusnya kemampuan “bisa dipinjam”, bukan silsilah katalog:
from abc import ABC, abstractmethod
class Pinjaman(ABC):
@abstractmethod
def pinjam(self):
...
@abstractmethod
def kembalikan(self):
...
class BukuFisik(Pinjaman):
def __init__(self, judul, stok=1):
self.judul = judul
self.stok = stok
def pinjam(self):
if self.stok < 1:
return f"stok habis: {self.judul}"
self.stok -= 1
return f"pinjam fisik {self.judul} · sisa {self.stok}"
def kembalikan(self):
self.stok += 1
return f"kembali fisik {self.judul} · stok {self.stok}"
class EbookLisensi(Pinjaman):
def __init__(self, judul, kuota=2):
self.judul = judul
self.kuota = kuota
def pinjam(self):
if self.kuota < 1:
return f"kuota habis: {self.judul}"
self.kuota -= 1
return f"aktifkan lisensi {self.judul} · sisa {self.kuota}"
def kembalikan(self):
self.kuota += 1
return f"nonaktifkan lisensi {self.judul} · kuota {self.kuota}"
b = BukuFisik("ESP32 Praktis", stok=2)
e = EbookLisensi("Belajar Python", kuota=1)
print(b.pinjam())
print(e.pinjam())
print(b.kembalikan())
Output kurang lebih:
pinjam fisik ESP32 Praktis · sisa 1
aktifkan lisensi Belajar Python · sisa 0
kembali fisik ESP32 Praktis · stok 2
Pemanggil hanya peduli kontrak pinjam/kembalikan. Detail stok vs kuota lisensi tetap di class masing-masing — selaras pilar Abstraction di Mengenal OOP (#40).
Loop aman lewat kontrak
Setelah semua item memenuhi Pinjaman, loop dari Polymorphism (#45) jadi lebih tenang. isinstance(item, Pinjaman) di sini bukan hutan cabang — melainkan cek “apakah object memegang kontrak?”:
from abc import ABC, abstractmethod
class Pinjaman(ABC):
@abstractmethod
def pinjam(self):
...
@abstractmethod
def kembalikan(self):
...
class BukuFisik(Pinjaman):
def __init__(self, judul, stok=1):
self.judul = judul
self.stok = stok
def pinjam(self):
if self.stok < 1:
return f"stok habis: {self.judul}"
self.stok -= 1
return f"pinjam fisik {self.judul} · sisa {self.stok}"
def kembalikan(self):
self.stok += 1
return f"kembali fisik {self.judul} · stok {self.stok}"
class EbookLisensi(Pinjaman):
def __init__(self, judul, kuota=2):
self.judul = judul
self.kuota = kuota
def pinjam(self):
if self.kuota < 1:
return f"kuota habis: {self.judul}"
self.kuota -= 1
return f"aktifkan lisensi {self.judul} · sisa {self.kuota}"
def kembalikan(self):
self.kuota += 1
return f"nonaktifkan lisensi {self.judul} · kuota {self.kuota}"
koleksi = [BukuFisik("ESP32 Praktis", stok=1), EbookLisensi("Belajar Python", kuota=1)]
for item in koleksi:
if isinstance(item, Pinjaman):
print(item.pinjam())
Output kurang lebih:
pinjam fisik ESP32 Praktis · sisa 0
aktifkan lisensi Belajar Python · sisa 0
Bedakan dengan anti-pola di Polymorphism (#45): hutan isinstance yang menyalin seluruh perilaku ke pemanggil. Di sini pemanggil tetap memanggil method kontrak; isinstance hanya menjaga batas API.
Satu perbedaan penting dari duck typing: object yang “kebetulan” punya method sama belum tentu lolos cek kontrak ABC:
from abc import ABC, abstractmethod
class Pinjaman(ABC):
@abstractmethod
def pinjam(self):
...
@abstractmethod
def kembalikan(self):
...
class BukuFisik(Pinjaman):
def __init__(self, judul):
self.judul = judul
def pinjam(self):
return f"pinjam {self.judul}"
def kembalikan(self):
return f"kembali {self.judul}"
class EntriDuck:
"""Punya pinjam/kembalikan — tapi bukan subclass Pinjaman."""
def __init__(self, judul):
self.judul = judul
def pinjam(self):
return f"duck pinjam {self.judul}"
def kembalikan(self):
return f"duck kembali {self.judul}"
formal = BukuFisik("ESP32 Praktis")
duck = EntriDuck("Datasheet DHT22")
print("isinstance formal:", isinstance(formal, Pinjaman))
print("isinstance duck:", isinstance(duck, Pinjaman))
print(duck.pinjam()) # tetap bisa dipanggil — duck typing
Output kurang lebih:
isinstance formal: True
isinstance duck: False
duck pinjam Datasheet DHT22
Jadi: duck typing = “bisa dipanggil”. ABC + isinstance = “terdaftar di kontrak resmi”. Pilih sesuai kebutuhan tim/API.
Method konkret di ABC — boleh, tapi hemat
ABC boleh punya method biasa (bukan abstract) untuk perilaku bersama. Tetap jaga agar kontrak tidak membengkak jadi “god class”:
from abc import ABC, abstractmethod
class Pinjaman(ABC):
@abstractmethod
def pinjam(self):
...
@abstractmethod
def kembalikan(self):
...
def label(self):
return self.__class__.__name__
class BukuFisik(Pinjaman):
def __init__(self, judul):
self.judul = judul
def pinjam(self):
return f"pinjam {self.judul}"
def kembalikan(self):
return f"kembali {self.judul}"
b = BukuFisik("Cerita Sensor")
print(b.label(), "->", b.pinjam())
Output: BukuFisik -> pinjam Cerita Sensor.
Ingat: method bersama di ABC berguna; kalau hampir semua logika pindah ke induk abstract, evaluasi ulang — mungkin inheritance biasa atau composition lebih jujur (artikel berikutnya).
Pola Dasar — merancang ABC
-
1
Sebut kemampuan wajib Mis. “bisa dipinjam” →
pinjam+kembalikan, bukan detail stok. -
2
Tulis class
(ABC)from abc import ABC, abstractmethodlalu dekorasi method wajib. -
3
Implementasi di anak Setiap subclass mengisi semua
@abstractmethodsebelum diinstansiasi. -
4
Loop lewat kontrak Panggil method kontrak yang sudah diisi;
isinstance(..., Pinjaman)hanya penjaga API. -
5
Jangan instansiasi ABC ABC = kontrak. Object bisnis = subclass konkret.
Kode lengkap — salin & jalankan
Simpan sebagai kontrak_pinjaman.py, lalu python kontrak_pinjaman.py:
from abc import ABC, abstractmethod
class Pinjaman(ABC):
@abstractmethod
def pinjam(self):
...
@abstractmethod
def kembalikan(self):
...
def label(self):
return self.__class__.__name__
class BukuFisik(Pinjaman):
def __init__(self, judul, stok=1):
self.judul = judul
self.stok = stok
def pinjam(self):
if self.stok < 1:
return f"stok habis: {self.judul}"
self.stok -= 1
return f"pinjam fisik {self.judul} · sisa {self.stok}"
def kembalikan(self):
self.stok += 1
return f"kembali fisik {self.judul} · stok {self.stok}"
class EbookLisensi(Pinjaman):
def __init__(self, judul, kuota=2):
self.judul = judul
self.kuota = kuota
def pinjam(self):
if self.kuota < 1:
return f"kuota habis: {self.judul}"
self.kuota -= 1
return f"aktifkan lisensi {self.judul} · sisa {self.kuota}"
def kembalikan(self):
self.kuota += 1
return f"nonaktifkan lisensi {self.judul} · kuota {self.kuota}"
koleksi = [
BukuFisik("ESP32 Praktis", stok=2),
EbookLisensi("Belajar Python", kuota=1),
]
for item in koleksi:
if isinstance(item, Pinjaman):
print(item.label(), "->", item.pinjam())
print(koleksi[0].kembalikan())
print("isinstance ebook->Pinjaman:", isinstance(koleksi[1], Pinjaman))
# Pinjaman() # TypeError — ABC tidak diinstansiasi
Output yang diharapkan (kurang lebih):
BukuFisik -> pinjam fisik ESP32 Praktis · sisa 1
EbookLisensi -> aktifkan lisensi Belajar Python · sisa 0
kembali fisik ESP32 Praktis · stok 2
isinstance ebook->Pinjaman: True
Kesalahan umum
| Gejala | Penyebab tipikal | Perbaikan |
|---|---|---|
TypeError: Can't instantiate abstract class... |
Subclass belum mengisi semua @abstractmethod |
Implementasikan method yang disebut di pesan error |
| Instansiasi ABC langsung | Mengira ABC adalah class bisnis | ABC = kontrak; buat subclass konkret |
Lupa from abc import ... |
Menulis class biasa tanpa mekanisme abstract | Pakai ABC + @abstractmethod |
| ABC berisi hampir semua logika | Kontrak membengkak jadi induk gemuk | Pindahkan detail ke anak; pertimbangkan composition |
| Bingung ABC vs Encapsulation | Menyamakan “sembunyikan detail” dengan “wajibkan method” | Encapsulation (#43) jaga akses data; ABC jaga daftar kemampuan |
Latihan singkat
- Tambah
class MajalahPinjam(Pinjaman)dengan edisi + stok; isi kedua abstract method; masukkan kekoleksi. - Sengaja hapus
kembalikandi satu subclass — amatiTypeError, lalu perbaiki. - Tambah method konkret
status()diPinjamanyang dibaca semua anak tanpa di-override.
FAQ singkat
Apakah ABC menggantikan duck typing?
Tidak. Duck typing tetap sah untuk kontrak kecil/lokal — object seperti EntriDuck tetap bisa dipanggil meski isinstance(..., Pinjaman) False. ABC berguna saat banyak class (atau tim) harus menjamin kemampuan yang sama lewat kontrak resmi.
Apa bedanya dengan Encapsulation (#43)?
Encapsulation mengatur akses state (@property, validasi). ABC mengatur daftar method wajib antar class.
Kenapa isinstance(x, Pinjaman) True untuk anak?
Karena pewarisan formal ke ABC — mirip isinstance(ebook, Buku) di Inheritance (#44). Di sini cek kontrak, bukan hutan perilaku.
Apakah wajib pakai ... di body abstract?
Tidak. pass atau raise NotImplementedError juga umum. Yang penting: dekorator @abstractmethod terpasang.
Cukup @abstractmethod tanpa mewarisi ABC?
Tidak. Dekorator saja tidak menegakkan kontrak. Class induk harus mewarisi ABC (seperti class Pinjaman(ABC)) agar Python menolak subclass yang belum lengkap.
Bagaimana dengan typing.Protocol?
Protocol (structural typing) mirip “duck typing bertipe”. Untuk seri ini kita fokus abc dulu — cukup untuk kontrak runtime yang jelas.
Kesimpulan & langkah berikutnya
Abstraction lewat ABC mengubah janji lisan (“harusnya ada pinjam”) menjadi kontrak yang ditegakkan Python. Gabungkan dengan polymorphism: loop memanggil method kontrak; tiap class mengisi caranya sendiri.
Artikel ini adalah #46 (ini) — langkah ketujuh Seri 3 setelah Polymorphism (#45).
Lanjut ke artikel berikutnya — Composition vs Inheritance: kapan “punya” (has-a) lebih jujur daripada “adalah” (is-a), dan anti-pattern inheritance hanya demi reuse. (Belum di-hyperlink sampai artikel itu live.)
Seri 3 progress: 7/10 artikel live. Kamu di langkah #46 (ini). Prasyarat: Polymorphism (#45) · Inheritance (#44) · Encapsulation (#43) · Attribute (#42) · Class & Object (#41) · Mengenal OOP (#40).