Kita telah melihat pada Section Advanced Data Modeling (Chapter Abstract Base Classes) bahwa ABC memaksa class menandatangani kontrak lewat pewarisan — hubungan tipe dibuktikan dengan nominal: class menyatakan sendiri “saya turunan dari ini”. Namun falsafah asli Python berbeda: yang penting bukan garis keturunan, melainkan “bisa apa”. Function len() tidak peduli class kita mewarisi apa; yang penting method __len__ ada. typing.Protocol membawa duck typing tersebut ke dunia static checking — inilah structural typing.

Dua Gaya Pencocokan Tipe #

AspekNominal (ABC)Structural (Protocol)
Bukti kecocokanmewarisi class/interfacememiliki method yang dibutuhkan
Pendaftaraneksplisit (class Kucing(Hewan))otomatis, tanpa pendaftaran
Cocok untukkerangka class milik sendiribatas antar-library, plugin
Pemeriksaanchecker & runtime (issubclass)checker; runtime terbatas

Dengan Protocol, class tidak perlu tahu sedang dihitung cocok dengan siapa. Cukup bentuknya yang sama, checker mengatakannya cocok.

Mendefinisikan Protocol #

Protocol didefinisikan seperti class yang mewarisi typing.Protocol, berisi deklarasi method tanpa badan:

python
from typing import Protocol

class Penyapa(Protocol):
    def sapa(self, nama: str) -> str: ...

class Sopir:
    def sapa(self, nama: str) -> str:
        return f"Halo, {nama}! Saya sopir Anda."

class Pelayan:
    def sapa(self, nama: str) -> str:
        return f"Selamat datang, {nama}!"

def layani(tim: Penyapa, nama: str) -> str:
    return tim.sapa(nama)

print(layani(Sopir(), "Budi"))
print(layani(Pelayan(), "Siti"))

Output yang Dihasilkan:

text
Halo, Budi! Saya sopir Anda.
Selamat datang, Siti!

Mari kita telaah baris-barisnya:

  1. Baris 3-4 (class Penyapa(Protocol)): Penyapa hanya memuat deklarasi def sapa(self, nama: str) -> str: ... — tanda ... menandai “ini spesifikasi, bukan implementasi”.
  2. Baris 6 dan 10 (class Sopir, class Pelayan): keduanya tidak mewarisi Penyapa — tidak ada hubungan pewarisan sama sekali.
  3. Baris 15 (tim: Penyapa): parameter tim bertipe Protocol; pemanggil boleh mengirim object apa pun selama method yang dibutuhkan ada dan cocok bentuknya.
  4. Baris 18-19: Sopir dan Pelayan diterima karena keduanya memiliki method sapa dengan tanda tangan yang cocok — itulah seluruh bukti yang diminta checker.

Checker menolak object yang bentuknya tidak cocok, tanpa peduli asal-usul class-nya. Simpan sebagai kurir.py:

python
from typing import Protocol

class Penyapa(Protocol):
    def sapa(self, nama: str) -> str: ...

class Kurir:
    def antar(self, nama: str) -> str:
        return f"Paket untuk {nama}!"

def layani(tim: Penyapa, nama: str) -> str:
    return tim.sapa(nama)

print(layani(Kurir(), "Budi"))
bash
python3 -m mypy kurir.py

Output yang Dihasilkan:

text
kurir.py:13: error: Argument 1 to "layani" has incompatible type "Kurir"; expected "Penyapa"  [arg-type]
Found 1 error in 1 file (checked 1 source file)

Kurir punya method antar, bukan sapa — bentuknya tidak cocok dengan kontrak Penyapa, sehingga mypy menolak pemanggilan tersebut.

runtime_checkable: Protocol di Sisi Runtime #

Secara bawaan, Protocol hanya hidup di dunia checker. Dekorator @runtime_checkable menambahkan kemampuan satu hal: isinstance() terhadap Protocol:

python
from typing import Protocol, runtime_checkable

@runtime_checkable
class Penyapa(Protocol):
    def sapa(self, nama: str) -> str: ...

class Sopir:
    def sapa(self, nama: str) -> str:
        return f"Halo, {nama}!"

class Kunci:
    pass

print(isinstance(Sopir(), Penyapa))
print(isinstance(Kunci(), Penyapa))

Output yang Dihasilkan:

text
True
False

Object Sopir cocok karena method sapa ada; object Kunci ditolak karena tidak punya method tersebut.

Protocol, ABC, atau Interface Biasa? #

Pilih berdasarkan siapa yang mengendalikan class penggunanya. Untuk kerangka class internal milik project sendiri, ABC tetap tepat — hubungan eksplisit memudahkan berbagi perilaku. Untuk titik perluasan yang diisi pihak luar (plugin, integrasi library, dependency injection), Protocol unggul: pengguna cukup menulis class dengan method yang cocok tanpa mengimpor satu pun symbol milik kita. Pemeriksaan isinstance penuh di runtime bukan bidang keduanya — untuk itu gunakan ABC.

Dengan Protocol, kita menutup rangkaian alat untuk menulis kontrak. Tinggal satu pola lagi: satu nama function dengan beberapa tanda tangan berbeda — tugas chapter Overload berikutnya.