- Pengenalan Metaclasses
- Fungsi type() dan Pembuatan Kelas
- Membuat Metaclass Dasar
- Metode __new__ dan __init__
- __init_subclass__ untuk Kontrol Pewarisan
- ABCMeta dan Abstract Base Class
- Use Cases Metaclasses
- Pola Singleton dengan Metaclass
- Membangun ORM Sederhana
- Validasi Atribut Otomatis
- Best Practices dan Alternatif
- Quiz Pemahaman
1. Pengenalan Metaclasses
Di Python, semua adalah objek — termasuk kelas itu sendiri. Ketika Anda mendefinisikan sebuah kelas dengan class Foo: pass, Python sebenarnya membuat sebuah objek yang merupakan instance dari metaclass. Secara default, metaclass untuk semua kelas di Python adalah type.
Metaclass adalah "kelas dari kelas" — yaitu kelas yang mendefinisikan bagaimana kelas-kelas lain dibuat. Bayangkan seperti cetakan kue: kelas adalah cetakan untuk objek, sedangkan metaclass adalah cetakan untuk kelas itu sendiri.
Konsep Dasar: Objek dan Kelas
# Semua di Python adalah objek
print(type(42)) # <class 'int'>
print(type("hello")) # <class 'str'>
print(type([1, 2, 3])) # <class 'list'>
# Kelas juga objek!
class Hewan:
pass
kucing = Hewan()
print(type(kucing)) # <class 'Hewan'> — instance dari Hewan
print(type(Hewan)) # <class 'type'> — Hewan adalah instance dari type!
# type adalah metaclass default
print(type(type)) # <class 'type'> — type adalah instance dari dirinya sendiri!
# Hubungan hierarki:
# type → membuat → class → membuat → instance
# type(type) = type (self-referencing)
┌─────────────────────────────────────────────────────────────┐ │ HIERARKI METACLASS │ │ │ │ ┌──────────┐ │ │ │ type │ ← metaclass │ │ │ (kelas │ (kelas dari semua kelas) │ │ │ dari │ │ │ │ semua) │ │ │ └────┬─────┘ │ │ │ isinstance / creates │ │ ┌──────────┼──────────┐ │ │ │ │ │ │ │ ┌────┴───┐ ┌────┴───┐ ┌───┴────┐ │ │ │ int │ │ str │ │ list │ ← kelas │ │ └────┬───┘ └────┬───┘ └───┬────┘ (instance type) │ │ │ │ │ │ │ ┌──┴──┐ ┌───┴──┐ ┌───┴──┐ │ │ │ 42 │ │ "hi" │ │[1,2] │ ← instance │ │ └─────┘ └──────┘ └──────┘ │ └─────────────────────────────────────────────────────────────┘
Tim Peters (penulis Zen of Python) pernah berkata: "Metaclasses are deeper magic than 99% of users should ever worry about." Namun, memahami metaclass sangat penting untuk memahami cara kerja framework seperti Django, SQLAlchemy, dan ABC.
2. Fungsi type() dan Pembuatan Kelas
Fungsi type() memiliki dua kegunaan: (1) mengecek tipe objek, dan (2) membuat kelas secara dinamis. Kegunaan kedua inilah yang menjadi fondasi metaclass.
Mengecek Tipe Objek
# type() untuk mengecek tipe
print(type(42)) # <class 'int'>
print(type("Halo")) # <class 'str'>
print(type([1, 2, 3])) # <class 'list'>
print(type(None)) # <class 'NoneType'>
# Membedakan type() vs isinstance()
class Hewan:
pass
class Kucing(Hewan):
pass
k = Kucing()
print(type(k) == Hewan) # False — type() exact match
print(isinstance(k, Hewan)) # True — isinstance() cek parent juga
Membuat Kelas Dinamis dengan type()
# Syntax type(): type(nama_kelas, (parent_kelas,), {atribut})
# Ini sama persis dengan: class nama_kelas(parent_kelas): atribut
# Contoh 1: Kelas sederhana
# Menulis:
class Mobil:
roda = 4
def klakson(self):
return "Tin tin!"
# Sama dengan:
Mobil = type('Mobil', (), {'roda': 4, 'klakson': lambda self: "Tin tin!"})
m = Mobil()
print(m.roda) # 4
print(m.klakson()) # Tin tin!
print(type(m)) # <class '__main__.Mobil'>
# Contoh 2: Kelas dengan inheritance
class Kendaraan:
def bergerak(self):
return "Bergerak..."
# Membuat kelas Mobil yang mewarisi Kendaraan
Mobil = type('Mobil', (Kendaraan,), {
'roda': 4,
'klakson': lambda self: "Tin tin!"
})
m = Mobil()
print(m.bergerak()) # Bergerak... (dari Kendaraan)
print(m.roda) # 4
# Contoh 3: Menambah metode dari fungsi terpisah
def info_mobil(self):
return f"Mobil dengan {self.roda} roda"
Mobil.info = info_mobil
print(m.info()) # Mobil dengan 4 roda
Sintaks class vs type()
| Sintaks class | Sintaks type() | Keterangan |
|---|---|---|
class Foo: pass | type('Foo', (), {}) | Kelas tanpa parent |
class Foo(Bar): pass | type('Foo', (Bar,), {}) | Kelas dengan parent |
class Foo: x = 5 | type('Foo', (), {'x': 5}) | Kelas dengan atribut |
3. Membuat Metaclass Dasar
Metaclass adalah kelas yang mewarisi dari type. Untuk menggunakan metaclass, kita menentukannya dengan parameter metaclass pada definisi kelas.
Metaclass Pertama
# Membuat metaclass sederhana
class MetaclassKustom(type):
"""Metaclass yang memodifikasi kelas yang dibuat"""
def __new__(mcs, name, bases, namespace):
# mcs = metaclass itu sendiri (seperti self, tapi untuk metaclass)
# name = nama kelas yang akan dibuat
# bases = tuple parent classes
# namespace = dictionary atribut dan metode
print(f"Membuat kelas: {name}")
print(f"Parent classes: {bases}")
print(f"Atribut: {list(namespace.keys())}")
# Buat kelas seperti biasa menggunakan type.__new__
return super().__new__(mcs, name, bases, namespace)
# Menggunakan metaclass
class Hewan(metaclass=MetaclassKustom):
def suara(self):
return "..."
# Output saat class didefinisikan:
# Membuat kelas: Hewan
# Parent classes: ()
# Atribut: ['__module__', '__qualname__', 'suara']
class Kucing(Hewan):
def suara(self):
return "Meong!"
# Output:
# Membuat kelas: Kucing
# Parent classes: (<class 'Hewan'>,)
# Atribut: ['__module__', '__qualname__', 'suara']
k = Kucing()
print(k.suara()) # Meong!
Cara Python Membuat Kelas (Langkah demi Langkah)
- Python menemukan definisi
class - Python mengidentifikasi metaclass (default:
type) - Python memanggil
metaclass.__prepare__()untuk mendapatkan namespace - Body kelas dieksekusi dalam namespace tersebut
- Python memanggil
metaclass.__new__()untuk membuat objek kelas - Python memanggil
metaclass.__init__()untuk menginisialisasi kelas
# Metaclass dengan __prepare__
class MetaclassLengkap(type):
@classmethod
def __prepare__(mcs, name, bases, **kwargs):
"""Dipanggil sebelum body kelas dieksekusi.
Harus mengembalikan mapping (dict-like)."""
print(f"__prepare__ dipanggil untuk '{name}'")
return super().__prepare__(name, bases, **kwargs)
def __new__(mcs, name, bases, namespace, **kwargs):
"""Membuat objek kelas baru."""
print(f"__new__ dipanggil untuk '{name}'")
return super().__new__(mcs, name, bases, namespace)
def __init__(cls, name, bases, namespace, **kwargs):
"""Menginisialisasi kelas yang sudah dibuat."""
print(f"__init__ dipanggil untuk '{name}'")
super().__init__(name, bases, namespace)
class Contoh(metaclass=MetaclassLengkap):
x = 42
def method(self):
return self.x
# Output:
# __prepare__ dipanggil untuk 'Contoh'
# __new__ dipanggil untuk 'Contoh'
# __init__ dipanggil untuk 'Contoh'
c = Contoh()
print(c.method()) # 42
4. Metode __new__ dan __init__
__new__ dan __init__ adalah dua metode krusial dalam metaclass. __new__ bertanggung jawab untuk membuat objek kelas, sedangkan __init__ bertanggung jawab untuk menginisialisasi-nya.
Perbedaan __new__ dan __init__
| Metode | Fungsi | Parameter Pertama | Mengembalikan |
|---|---|---|---|
__new__ | Membuat instance baru | cls (kelas) | Instance baru |
__init__ | Menginisialisasi instance | self (instance) | None |
# __new__ dipanggil SEBELUM __init__
# __new__ MENGEMBALIKAN instance
# __init__ hanya mengkonfigurasi instance yang sudah ada
class LoggerMeta(type):
"""Metaclass yang menambahkan logging ke semua metode."""
def __new__(mcs, name, bases, namespace):
# Wrap setiap metode dengan logging
for attr_name, attr_value in namespace.items():
if callable(attr_value) and not attr_name.startswith('_'):
namespace[attr_name] = LoggerMeta._tambah_log(attr_value)
return super().__new__(mcs, name, bases, namespace)
@staticmethod
def _tambah_log(func):
def wrapper(*args, **kwargs):
print(f" [LOG] Memanggil: {func.__name__}()")
hasil = func(*args, **kwargs)
print(f" [LOG] {func.__name__}() selesai → {hasil}")
return hasil
wrapper.__name__ = func.__name__
return wrapper
class Kalkulator(metaclass=LoggerMeta):
def tambah(self, a, b):
return a + b
def kali(self, a, b):
return a * b
calc = Kalkulator()
hasil = calc.tambah(3, 5)
# Output:
# [LOG] Memanggil: tambah()
# [LOG] tambah() selesai → 8
hasil = calc.kali(4, 7)
# Output:
# [LOG] Memanggil: kali()
# [LOG] kali() selesai → 28
Modifikasi __new__ pada Instance Level
# __new__ juga bisa di-override di level kelas (bukan metaclass)
# Berguna untuk immutable types dan singleton
class AngkaPositif:
"""Kelas yang hanya menyimpan angka positif."""
def __new__(cls, nilai):
if nilai < 0:
raise ValueError(f"Nilai harus positif, dapat: {nilai}")
instance = super().__new__(cls)
instance._nilai = nilai # Set atribut di __new__
return instance
def __init__(self, nilai):
# _nilai sudah di-set di __new__
# __init__ bisa tambah inisialisasi lain
self._label = f"Angka({self._nilai})"
def __repr__(self):
return self._label
a = AngkaPositif(42)
print(a) # Angka(42)
try:
b = AngkaPositif(-5)
except ValueError as e:
print(f"Error: {e}") # Error: Nilai harus positif, dapat: -5
# Contoh: Singleton menggunakan __new__
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
if not hasattr(self, 'data'):
self.data = "Halo dari Singleton!"
s1 = Singleton()
s2 = Singleton()
print(s1 is s2) # True — objek yang sama!
print(s1.data) # Halo dari Singleton!
5. __init_subclass__ untuk Kontrol Pewarisan
__init_subclass__ diperkenalkan di Python 3.6 sebagai alternatif yang lebih sederhana untuk metaclass. Metode ini dipanggil setiap kali sebuah kelas turunan (subclass) dibuat.
Perbandingan: __init_subclass__ vs Metaclass
# SEBELUM (Python 3.5) — harus pakai metaclass
class PluginMeta(type):
_plugins = {}
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
if name != 'Plugin':
mcs._plugins[name.lower()] = cls
return cls
class Plugin(metaclass=PluginMeta):
pass
# SESUDAH (Python 3.6+) — cukup __init_subclass__
class Plugin:
_plugins = {}
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
Plugin._plugins[cls.__name__.lower()] = cls
# Kedua cara menghasilkan efek yang sama:
class AuthPlugin(Plugin):
pass
class CachePlugin(Plugin):
pass
print(Plugin._plugins)
# {'authplugin': <class 'AuthPlugin'>, 'cacheplugin': <class 'CachePlugin'>}
__init_subclass__ dengan Parameter Kustom
# __init_subclass__ bisa menerima keyword arguments
class Model:
_tabel = {}
def __init_subclass__(cls, nama_tabel=None, **kwargs):
super().__init_subclass__(**kwargs)
if nama_tabel:
cls._nama_tabel = nama_tabel
Model._tabel[nama_tabel] = cls
else:
cls._nama_tabel = cls.__name__.lower()
class User(Model, nama_tabel="users"):
kolom = ["id", "nama", "email"]
class Product(Model, nama_tabel="products"):
kolom = ["id", "nama_produk", "harga"]
print(User._nama_tabel) # users
print(Product._nama_tabel) # products
print(Model._tabel) # {'users': User, 'products': Product}
# Validasi otomatis
class ValidatedModel:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
# Pastikan semua subclass punya metode 'validate'
if 'validate' not in cls.__dict__:
raise TypeError(
f"{cls.__name__} harus mendefinisikan metode validate()"
)
class Formulir(ValidatedModel):
def validate(self):
return True
# class Broken(ValidatedModel):
# pass
# → TypeError: Broken harus mendefinisikan metode validate()
__init_subclass__ hanya dipanggil untuk subclass langsung atau subclass turunan (dengan perubahan Python 3.6.1+). Namun, parameter kustom hanya berlaku untuk subclass langsung. Untuk kebutuhan yang lebih kompleks, tetap gunakan metaclass.
6. ABCMeta dan Abstract Base Class
ABCMeta adalah metaclass yang disediakan oleh modul abc (Abstract Base Classes). ABCMeta memungkinkan Anda mendefinisikan kelas abstrak yang tidak bisa diinstansiasi dan memaksa subclass untuk mengimplementasikan metode tertentu.
Menggunakan ABCMeta
from abc import ABCMeta, abstractmethod
# Cara 1: Menggunakan metaclass secara eksplisit
class Shape(metaclass=ABCMeta):
@abstractmethod
def luas(self):
"""Hitung luas bentuk."""
pass
@abstractmethod
def keliling(self):
"""Hitung keliling bentuk."""
pass
def info(self):
"""Metode konkret — bisa langsung dipakai."""
return f"Luas: {self.luas():.2f}, Keliling: {self.keliling():.2f}"
# shape = Shape() → TypeError: Can't instantiate abstract class Shape
# with abstract methods keliling, luas
class Lingkaran(Shape):
def __init__(self, jari_jari):
self.r = jari_jari
def luas(self):
import math
return math.pi * self.r ** 2
def keliling(self):
import math
return 2 * math.pi * self.r
class Persegi(Shape):
def __init__(self, sisi):
self.sisi = sisi
def luas(self):
return self.sisi ** 2
def keliling(self):
return 4 * self.sisi
# Sekarang bisa diinstansiasi
lingkaran = Lingkaran(5)
print(lingkaran.info()) # Luas: 78.54, Keliling: 31.42
persegi = Persegi(4)
print(persegi.info()) # Luas: 16.00, Keliling: 16.00
# class Segitiga(Shape):
# def luas(self):
# return 0.5 * self.alas * self.tinggi
# → TypeError: Can't instantiate abstract class Segitiga
# with abstract method keliling (belum implement keliling!)
Cara 2: Menggunakan ABC (Shortcut)
from abc import ABC, abstractmethod
# ABC = convenience class yang sudah set metaclass=ABCMeta
class Serializable(ABC):
@abstractmethod
def to_dict(self):
pass
@abstractmethod
def from_dict(cls, data):
pass
class User(Serializable):
def __init__(self, nama, email):
self.nama = nama
self.email = email
def to_dict(self):
return {"nama": self.nama, "email": self.email}
@classmethod
def from_dict(cls, data):
return cls(data["nama"], data["email"])
user = User("Budi", "budi@email.com")
data = user.to_dict()
print(data) # {'nama': 'Budi', 'email': 'budi@email.com'}
user2 = User.from_dict(data)
print(user2.nama) # Budi
Properti Abstrak dan Classmethod
from abc import ABC, abstractmethod
class BaseConfig(ABC):
@property
@abstractmethod
def nama_aplikasi(self):
"""Harus diimplementasikan oleh subclass."""
pass
@classmethod
@abstractmethod
def dari_env(cls):
"""Harus diimplementasikan oleh subclass."""
pass
class DevConfig(BaseConfig):
@property
def nama_aplikasi(self):
return "MyApp-Dev"
@classmethod
def dari_env(cls):
return cls()
cfg = DevConfig()
print(cfg.nama_aplikasi) # MyApp-Dev
7. Use Cases Metaclasses
Meskipun metaclasses sering dianggap "terlalu kompleks", ada beberapa skenario di mana metaclasses sangat berguna dan bahkan diperlukan.
Aplikasi Umum Metaclasses
| Use Case | Deskripsi | Contoh di Dunia Nyata |
|---|---|---|
| API/ORM | Mapping class ke database table | Django Models, SQLAlchemy |
| Singleton | Memastikan hanya 1 instance | Database connections, Config |
| Validasi | Validasi atribut saat class dibuat | Pydantic, Marshmallow |
| Registration | Auto-register plugin/handler | pytest, Django admin |
| Proxy/Wrapping | Modifikasi metode otomatis | RPC frameworks, Mocking |
8. Pola Singleton dengan Metaclass
Pola Singleton memastikan bahwa sebuah kelas hanya memiliki satu instance di seluruh aplikasi. Metaclass adalah cara paling elegan untuk mengimplementasikan pola ini di Python.
class SingletonMeta(type):
"""Metaclass yang membuat kelas hanya punya satu instance."""
_instances = {}
def __call__(cls, *args, **kwargs):
# __call__ dipanggil saat Anda memanggil ClassName()
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class Database(metaclass=SingletonMeta):
def __init__(self):
print("Menghubungkan ke database...")
self.connection = "PostgreSQL://localhost/mydb"
self.query_count = 0
def query(self, sql):
self.query_count += 1
return f"[{self.connection}] Executing: {sql}"
# Semua Database() mengembalikan instance yang sama
db1 = Database()
db2 = Database()
print(db1 is db2) # True — singleton!
db1.query("SELECT * FROM users")
print(db2.query_count) # 1 — db2 adalah db1 yang sama!
print(db1 is db2) # True
Thread-Safe Singleton
import threading
class ThreadSafeSingletonMeta(type):
"""Singleton yang aman untuk multi-threading."""
_instances = {}
_lock = threading.Lock()
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
with cls._lock: # Double-checked locking
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class Config(metaclass=ThreadSafeSingletonMeta):
def __init__(self):
self.debug = True
self.port = 8080
# Di multi-thread sekalipun, hanya 1 instance
config = Config()
9. Membangun ORM Sederhana
Metaclass adalah fondasi dari ORM (Object-Relational Mapping) seperti Django Models dan SQLAlchemy. Di bagian ini kita akan membangun mini ORM menggunakan metaclass.
class Field:
"""Deskripsi satu kolom di tabel."""
def __init__(self, tipe, pk=False):
self.tipe = tipe
self.pk = pk
def __repr__(self):
return f"Field({self.tipe.__name__}, pk={self.pk})"
class ModelMeta(type):
"""Metaclass untuk mini ORM."""
def __new__(mcs, name, bases, namespace):
# Skip base class Model
fields = {}
for key, value in namespace.items():
if isinstance(value, Field):
fields[key] = value
# Buat _table_name otomatis dari nama kelas
namespace['_table_name'] = name.lower() + 's'
namespace['_fields'] = fields
cls = super().__new__(mcs, name, bases, namespace)
return cls
class Model(metaclass=ModelMeta):
"""Base class untuk semua model."""
def __init__(self, **kwargs):
for field_name, field_obj in self._fields.items():
value = kwargs.get(field_name)
setattr(self, field_name, value)
def save(self):
cols = ', '.join(self._fields.keys())
vals = ', '.join(repr(getattr(self, f)) for f in self._fields)
return f"INSERT INTO {self._table_name} ({cols}) VALUES ({vals})"
def __repr__(self):
attrs = ', '.join(
f"{k}={getattr(self, k)!r}" for k in self._fields
)
return f"{type(self).__name__}({attrs})"
# Definisikan model — seperti Django!
class User(Model):
id = Field(int, pk=True)
nama = Field(str)
email = Field(str)
umur = Field(int)
class Product(Model):
id = Field(int, pk=True)
nama_produk = Field(str)
harga = Field(float)
# Gunakan model
user = User(id=1, nama='Budi Santoso', email='budi***@email.com', umur=25)
print(user) # User(id=1, nama='Budi Santoso', email='budi@email.com', umur=25)
print(user.save()) # [SQL] INSERT INTO users ...
# # "INSERT INTO users (id, nama, email, umur) VALUES (1, 'Budi Santoso', 'budi@email.com', 25)"
product = Product(id=1, nama_produk="Laptop", harga=15000000)
print(product) # Product(id=1, nama_produk='Laptop', harga=15000000.0)
print(product.save())
# Akses metadata
print(User._table_name) # users
print(User._fields) # {'id': Field(int, pk=True), 'nama': Field(str), ...}
print(Product._table_name) # products
9. Membangun ORM Sederhana
Salah satu contoh terbaik penggunaan metaclass adalah dalam ORM (Object-Relational Mapping). Framework seperti Django ORM dan SQLAlchemy menggunakan metaclass untuk memetakan kelas Python ke tabel database.
class Field:
"""Representasi kolom database."""
def __init__(self, field_type, primary_key=False, default=None):
self.field_type = field_type
self.primary_key = primary_key
self.default = default
self.name = None # Akan di-set oleh metaclass
def __repr__(self):
return f"Field({self.field_type.__name__}, pk={self.primary_key})"
class ModelMeta(type):
"""Metaclass untuk mini ORM."""
def __new__(mcs, name, bases, namespace):
# Jangan proses base class Model itu sendiri
if name == 'Model':
return super().__new__(mcs, name, bases, namespace)
# Kumpulkan fields
fields = {}
for key, value in namespace.items():
if isinstance(value, Field):
value.name = key
fields[key] = value
namespace['_fields'] = fields
namespace['_table_name'] = name.lower() + 's'
return super().__new__(mcs, name, bases, namespace)
class Model(metaclass=ModelMeta):
"""Base model untuk mini ORM."""
def __init__(self, **kwargs):
for field_name, field in self._fields.items():
value = kwargs.get(field_name, field.default)
setattr(self, field_name, value)
def save(self):
columns = ', '.join(self._fields.keys())
values = ', '.join(repr(getattr(self, f)) for f in self._fields)
sql = f"INSERT INTO {self._table_name} ({columns}) VALUES ({values})"
print(f" [SQL] {sql}")
return sql
def __repr__(self):
attrs = ', '.join(f"{k}={getattr(self, k)!r}" for k in self._fields)
return f"{self.__class__.__name__}({attrs})"
# Definisikan model — metaclass otomatis memproses Fields
class User(Model):
id = Field(int, primary_key=True)
nama = Field(str)
email = Field(str)
umur = Field(int, default=0)
class Product(Model):
id = Field(int, primary_key=True)
nama_produk = Field(str)
harga = Field(float)
# Menggunakan
user = User(id=1, nama="Budi Santoso", email="budi@email.com", umur=25)
print(user) # User(id=1, nama='Budi Santoso', email='budi@email.com', umur=25)
print(user.save()) # [SQL] INSERT INTO users ...
# # "INSERT INTO users (id, nama, email, umur) VALUES (1, 'Budi Santoso', 'budi@email.com', 25)"
product = Product(id=1, nama_produk="Laptop", harga=15000000)
print(product) # Product(id=1, nama_produk='Laptop', harga=15000000.0)
print(product.save())
# Akses metadata
print(User._table_name) # users
print(User._fields) # {'id': Field(int, pk=True), 'nama': Field(str), ...}
print(Product._table_name) # products
┌───────────────────────────────────────────────────────────────┐
│ MINI ORM DENGAN METACLASS │
│ │
│ ┌─────────────────┐ │
│ │ class User │ ← Python Code │
│ │ id = Field() │ │
│ │ nama = Field() │ │
│ └────────┬────────┘ │
│ │ ModelMeta.__new__() │
│ ▼ │
│ ┌─────────────────┐ ┌──────────────────────┐ │
│ │ User._fields │ │ User._table_name │ │
│ │ {id: ..., │ → │ = "users" │ │
│ │ nama: ...} │ └──────────────────────┘ │
│ └────────┬────────┘ │
│ │ user.save() │
│ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ INSERT INTO users (id, nama) VALUES (1, 'Budi')│ → SQL │
│ └─────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────┘
10. Validasi Atribut Otomatis
Metaclass bisa digunakan untuk memvalidasi atribut kelas secara otomatis saat kelas dibuat, bukan saat instance dibuat. Ini memungkinkan error ditemukan lebih awal.
class ValidatedMeta(type):
"""Metaclass yang memvalidasi atribut class."""
def __new__(mcs, name, bases, namespace):
# Validasi: semua atribut harus diberi type annotation
annotations = namespace.get('__annotations__', {})
for attr_name, attr_value in namespace.items():
if attr_name.startswith('_') or callable(attr_value):
continue
if attr_name not in annotations:
raise TypeError(
f"{name}.{attr_name} = {attr_value!r} tidak punya type annotation! "
f"Tambahkan: {attr_name}: tipe = ..."
)
return super().__new__(mcs, name, bases, namespace)
class Config(metaclass=ValidatedMeta):
nama: str = "MyApp"
port: int = 8080
debug: bool = False
print(Config.nama) # MyApp — valid!
# class BrokenConfig(metaclass=ValidatedMeta):
# host = "localhost" # ← Tidak punya type annotation!
# → TypeError: BrokenConfig.host = 'localhost' tidak punya type annotation!
# Contoh: Singleton + Validasi
class AutoRegisterMeta(type):
"""Metaclass yang auto-register + validasi."""
_registry = {}
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
# Skip base class
if name != 'Base':
# Validasi: harus punya 'name' attribute
if not hasattr(cls, 'name') or not cls.name:
raise ValueError(f"{name} harus mendefinisikan atribut 'name'")
mcs._registry[cls.name] = cls
print(f" Registered: {cls.name} → {name}")
return cls
class Base(metaclass=AutoRegisterMeta):
pass
class HTTPHandler(Base):
name = "http"
class WebSocketHandler(Base):
name = "websocket"
print(AutoRegisterMeta._registry)
# {'http': HTTPHandler, 'websocket': WebSocketHandler}
11. Best Practices dan Alternatif
Kapan Menggunakan Metaclass
Gunakan metaclass hanya ketika tidak ada alternatif yang lebih sederhana. Python menyediakan banyak mekanisme yang bisa menggantikan metaclass untuk kasus yang lebih umum.
| Kebutuhan | Solusi Terbaik | Menggunakan Metaclass? |
|---|---|---|
| Hook saat subclass dibuat | __init_subclass__ | ❌ Tidak perlu |
| Validasi saat kelas dibuat | __init_subclass__ | ❌ Tidak perlu |
| Abstract methods | ABC + @abstractmethod | ❌ Tersembunyi |
| Singleton | Metaclass atau module-level | ✅ Bagus |
| ORM field mapping | Metaclass | ✅ Perlu |
| Dynamic class creation | type() | ✅ Perlu |
| Decorating all methods | Metaclass | ✅ Berguna |
Alternatif: Decorators vs Metaclasses
# Dengan metaclass:
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class DB(metaclass=SingletonMeta):
pass
# Alternatif: decorator (lebih sederhana untuk singleton)
def singleton(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Cache:
pass
# Alternatif: __init_subclass__ untuk validasi
class ValidatedBase:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
if not hasattr(cls, 'name'):
raise TypeError(f"{cls.__name__} harus punya atribut 'name'")
class Handler(ValidatedBase):
name = "handler"
# class BadHandler(ValidatedBase):
# pass → TypeError!
Tips Penting Metaclass
- Hindari metaclass yang kompleks — jika Anda butuh lebih dari 50 baris, pertimbangkan ulang
- Dokumentasikan dengan baik — metaclass sulit dipahami oleh developer lain
- Hati-hati dengan MRO — metaclass dari parent harus kompatibel
- Gunakan
__init_subclass__jika memungkinkan - Test dengan subclass bertingkat — pastikan metaclass bekerja di seluruh hierarki
12. Quiz Pemahaman
Uji pemahaman Anda tentang Python Metaclasses dengan quiz berikut:
🧠 Quiz: Python Metaclasses
1. Apa output dari type(type)?
2. Metode apa yang dipanggil pertama kali saat kelas dibuat?
3. Apa metaclass default semua kelas di Python?
4. Apa fungsi utama ABCMeta?
5. Apa alternatif modern untuk metaclass untuk hook subclass?