Python

Python Metaclasses: Panduan Lengkap

Tutorial mendalam tentang Python Metaclasses — type(), __new__, __init_subclass__, ABCMeta, dan metaprogramming tingkat lanjut untuk mengontrol pembuatan kelas secara dinamis

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

Python — Konsep Dasar
# 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)
Diagram: Hierarki Metaclass
┌─────────────────────────────────────────────────────────────┐
│                 HIERARKI METACLASS                          │
│                                                             │
│                    ┌──────────┐                              │
│                    │  type    │  ← metaclass                 │
│                    │ (kelas   │     (kelas dari semua kelas) │
│                    │ dari     │                              │
│                    │ semua)   │                              │
│                    └────┬─────┘                              │
│                         │ isinstance / creates               │
│              ┌──────────┼──────────┐                         │
│              │          │          │                         │
│         ┌────┴───┐ ┌────┴───┐ ┌───┴────┐                   │
│         │  int   │ │  str   │ │  list  │  ← kelas           │
│         └────┬───┘ └────┬───┘ └───┬────┘    (instance type) │
│              │          │          │                         │
│           ┌──┴──┐   ┌───┴──┐  ┌───┴──┐                     │
│           │ 42  │   │ "hi" │  │[1,2] │  ← instance         │
│           └─────┘   └──────┘  └──────┘                      │
└─────────────────────────────────────────────────────────────┘
💡 Tips

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

Python — type() untuk cek tipe
# 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()

Python — Membuat kelas dinamis
# 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: passtype('Foo', (), {})Kelas tanpa parent
class Foo(Bar): passtype('Foo', (Bar,), {})Kelas dengan parent
class Foo: x = 5type('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

Python — Metaclass Dasar
# 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)

🔍 Proses Pembuatan Kelas
  1. Python menemukan definisi class
  2. Python mengidentifikasi metaclass (default: type)
  3. Python memanggil metaclass.__prepare__() untuk mendapatkan namespace
  4. Body kelas dieksekusi dalam namespace tersebut
  5. Python memanggil metaclass.__new__() untuk membuat objek kelas
  6. Python memanggil metaclass.__init__() untuk menginisialisasi kelas
Python — Proses Lengkap
# 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 barucls (kelas)Instance baru
__init__Menginisialisasi instanceself (instance)None
Python — __new__ detail
# __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

Python — __new__ pada instance
# __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

Python — __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

Python — 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()
⚠️ Peringatan

__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

Python — 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)

Python — 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

Python — Abstract Property
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/ORMMapping class ke database tableDjango Models, SQLAlchemy
SingletonMemastikan hanya 1 instanceDatabase connections, Config
ValidasiValidasi atribut saat class dibuatPydantic, Marshmallow
RegistrationAuto-register plugin/handlerpytest, Django admin
Proxy/WrappingModifikasi metode otomatisRPC 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.

Python — Singleton Metaclass
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

Python — 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.

Python — Mini ORM
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.

Python — Mini ORM
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
Diagram: Cara ORM Bekerja
┌───────────────────────────────────────────────────────────────┐
│                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.

Python — Validasi Otomatis
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

⚠️ Peringatan

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 methodsABC + @abstractmethod❌ Tersembunyi
SingletonMetaclass atau module-level✅ Bagus
ORM field mappingMetaclass✅ Perlu
Dynamic class creationtype()✅ Perlu
Decorating all methodsMetaclass✅ Berguna

Alternatif: Decorators vs Metaclasses

Python — Alternatif Decorator
# 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

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?

🔍 Zoom
100%
🎨 Tema