← Kursa Dön
📄 Text · 15 min

Metaclass ve __new__

Giriş: Sınıfların Sınıfı

Python'da her şey bir nesne. 42 bir nesne, "hello" bir nesne, [1, 2, 3] bir nesne. Peki ya sınıflar? Onlar da nesne mi? Evet! Sınıflar da nesnedir. Ve her nesnenin bir sınıfı (tipi) vardır.

Sıradan nesnelerin tipi bir sınıf. Sınıfların tipi ise bir metaclass. Yani metaclass = sınıfların sınıfı.

Bu ders ileri düzey bir konu. Çoğu Python geliştiricisi metaclass yazmaya ihtiyaç duymaz. Ama nasıl çalıştığını anlamak, Python'ın derinliklerini kavramak açısından çok öğretici.


Analoji: Fabrika Kuran Fabrika

Bir fabrika düşün. Bu fabrika araba üretiyor. Arabaların özelliklerini (renk, motor, model) sen belirliyorsun.

Ama bu fabrikayı kim kurdu? Bir fabrika fabrikası (meta-fabrika). Meta-fabrika, fabrikaların nasıl kurulacağını belirler: üretim bandının uzunluğu, kalite kontrol kuralları, çalışan sayısı.

Python'da:

  • Araba = nesne (instance)

  • Fabrika = sınıf (class) — nesneleri üretir

  • Fabrika fabrikası = metaclass — sınıfları üretir


type() — Python'ın Varsayılan Metaclass'ı

Python'da tüm sınıflar type metaclass'ı tarafından oluşturulur.

# Her nesnenin bir tipi var
print(type(42))         # <class 'int'>
print(type("hello"))    # <class 'str'>
print(type([1, 2]))     # <class 'list'>

# Sınıfların tipi de type!
print(type(int))        # <class 'type'>
print(type(str))        # <class 'type'>
print(type(list))       # <class 'type'>

# Kendi sınıfımızın tipi
class Dog:
    pass

print(type(Dog))        # <class 'type'>
print(type(Dog()))      # <class '__main__.Dog'>

Gördün mü? int, str, list gibi sınıfların tipi `type`. Ve type'ın tipi de type:

print(type(type))  # <class 'type'> — Kendi kendinin tipi!

Bu, Python'ın en temel yapısı. type hem bir sınıf hem de bir metaclass.


type() ile Dinamik Sınıf Oluşturma

type() fonksiyonunun iki kullanımı var:

  1. type(obj) — nesnenin tipini döner

  2. type(name, bases, dict) — yeni bir sınıf oluşturur

# Normal yol: class keyword ile
class Dog:
    species = "Canis familiaris"
    
    def bark(self):
        return "Hav hav!"

# Dinamik yol: type() ile (tamamen aynı sonuç)
def bark(self):
    return "Hav hav!"

Dog2 = type(
    "Dog2",                          # Sınıf adı
    (),                              # Base class'lar (tuple)
    {"species": "Canis familiaris",  # Sınıf dict'i
     "bark": bark}
)

# İkisi de aynı çalışır
d1 = Dog()
d2 = Dog2()
print(d1.bark())      # Hav hav!
print(d2.bark())      # Hav hav!
print(d2.species)     # Canis familiaris

Kalıtım ile Dinamik Sınıf

class Animal:
    def breathe(self):
        return "Nefes alıyor..."

# Animal'dan türeyen dinamik sınıf
Cat = type(
    "Cat",
    (Animal,),  # Base class
    {
        "sound": "Miyav",
        "speak": lambda self: f"{self.sound}!"
    }
)

cat = Cat()
print(cat.breathe())  # Nefes alıyor...
print(cat.speak())    # Miyav!
print(isinstance(cat, Animal))  # True

Metaclass Yazma

Kendi metaclass'ını yazarak sınıf oluşturma sürecini kontrol edebilirsin.

Temel Metaclass

class MyMeta(type):
    """Sınıf oluşturulurken müdahale eden metaclass."""
    
    def __new__(mcs, name, bases, namespace):
        print(f"🏭 Sınıf oluşturuluyor: {name}")
        print(f"   Base class'lar: {bases}")
        print(f"   Attribute'lar: {list(namespace.keys())}")
        
        # Sınıfı oluştur
        cls = super().__new__(mcs, name, bases, namespace)
        return cls

# Metaclass kullanımı
class MyClass(metaclass=MyMeta):
    x = 10
    
    def hello(self):
        return "Merhaba!"

# Çıktı:
# 🏭 Sınıf oluşturuluyor: MyClass
#    Base class'lar: ()
#    Attribute'lar: ['__module__', '__qualname__', 'x', 'hello']

__new__ vs __init__ (Sınıf Düzeyinde)

Metaclass'ta iki önemli metod var:

  • `__new__`: Sınıf oluşturulmadan önce çağrılır. Sınıfı oluşturur ve döndürür.

  • `__init__`: Sınıf oluşturulduktan sonra çağrılır. Sınıfı yapılandırır.

class DebugMeta(type):
    def __new__(mcs, name, bases, namespace):
        print(f"__new__: {name} sınıfı OLUŞTURULUYOR")
        cls = super().__new__(mcs, name, bases, namespace)
        print(f"__new__: {name} sınıfı OLUŞTURULDU")
        return cls
    
    def __init__(cls, name, bases, namespace):
        print(f"__init__: {name} sınıfı YAPILANDIRILIYOR")
        super().__init__(name, bases, namespace)
        print(f"__init__: {name} sınıfı HAZIR")

class Example(metaclass=DebugMeta):
    pass

# Çıktı:
# __new__: Example sınıfı OLUŞTURULUYOR
# __new__: Example sınıfı OLUŞTURULDU
# __init__: Example sınıfı YAPILANDIRILIYOR
# __init__: Example sınıfı HAZIR

Metaclass Pratik Örnekleri

Singleton Pattern

class SingletonMeta(type):
    """Her sınıftan sadece bir instance oluşturulmasını sağlar."""
    
    _instances = {}
    
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

class Database(metaclass=SingletonMeta):
    def __init__(self):
        print("Veritabanı bağlantısı kuruldu")
        self.connected = True

# Her çağrı aynı instance'ı döner
db1 = Database()  # "Veritabanı bağlantısı kuruldu"
db2 = Database()  # Çıktı yok — aynı instance!

print(db1 is db2)  # True — aynı nesne!

Attribute Doğrulama

class ValidatedMeta(type):
    """Sınıftaki tüm public attribute'ların type hint'i olmasını zorunlu kılar."""
    
    def __new__(mcs, name, bases, namespace):
        # Sadece kullanıcı sınıflarını kontrol et
        if bases:  # Base class'ı atla
            annotations = namespace.get("__annotations__", {})
            for key, value in namespace.items():
                if not key.startswith("_") and key not in annotations:
                    if not callable(value):
                        raise TypeError(
                            f"'{name}.{key}' attribute'ı type hint'e sahip olmalı!"
                        )
        
        return super().__new__(mcs, name, bases, namespace)

class Base(metaclass=ValidatedMeta):
    pass

class GoodClass(Base):
    name: str = "Ali"
    age: int = 25

# class BadClass(Base):
#     name = "Ali"  # TypeError: type hint yok!

Registry Pattern

class RegistryMeta(type):
    """Oluşturulan tüm sınıfları kaydeder."""
    
    registry = {}
    
    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)
        if bases:  # Base class'ı kaydetme
            mcs.registry[name] = cls
        return cls
    
    @classmethod
    def get_all(mcs):
        return dict(mcs.registry)

class Plugin(metaclass=RegistryMeta):
    pass

class PDFExporter(Plugin):
    def export(self): return "PDF"

class CSVExporter(Plugin):
    def export(self): return "CSV"

class XMLExporter(Plugin):
    def export(self): return "XML"

# Tüm plugin'leri listele
print(RegistryMeta.get_all())
# {'PDFExporter': <class...>, 'CSVExporter': <class...>, 'XMLExporter': <class...>}

# Dinamik olarak plugin oluştur
for name, cls in RegistryMeta.get_all().items():
    plugin = cls()
    print(f"{name}: {plugin.export()}")

Descriptor Protocol

Descriptor, bir sınıfın attribute'larına erişimi kontrol eden nesnedir. __get__, __set__ ve/veya __delete__ metodlarını implemente eder.

Descriptor Nedir?

class Descriptor:
    """Basit bir descriptor."""
    
    def __get__(self, obj, objtype=None):
        print(f"__get__ çağrıldı: obj={obj}, type={objtype}")
        return 42
    
    def __set__(self, obj, value):
        print(f"__set__ çağrıldı: value={value}")
    
    def __delete__(self, obj):
        print(f"__delete__ çağrıldı")

class MyClass:
    attr = Descriptor()

obj = MyClass()
print(obj.attr)     # __get__ çağrıldı → 42
obj.attr = 100      # __set__ çağrıldı: value=100
del obj.attr        # __delete__ çağrıldı

Property = Descriptor'ın Güzel Hali

property aslında bir descriptor:

class Circle:
    def __init__(self, radius):
        self._radius = radius
    
    @property
    def radius(self):
        return self._radius
    
    @radius.setter
    def radius(self, value):
        if value < 0:
            raise ValueError("Yarıçap negatif olamaz!")
        self._radius = value

# property arka planda descriptor protocol kullanır
c = Circle(5)
print(c.radius)    # __get__ → 5
c.radius = 10      # __set__ → 10
# c.radius = -1    # ValueError!

Custom Descriptor Örnekleri

Validated Descriptor: Tip Kontrolü

class Typed:
    """Tip kontrolü yapan descriptor."""
    
    def __init__(self, name, expected_type):
        self.name = name
        self.expected_type = expected_type
    
    def __set_name__(self, owner, name):
        self.name = name
    
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return obj.__dict__.get(self.name)
    
    def __set__(self, obj, value):
        if not isinstance(value, self.expected_type):
            raise TypeError(
                f"'{self.name}' {self.expected_type.__name__} olmalı, "
                f"{type(value).__name__} verildi"
            )
        obj.__dict__[self.name] = value

class Person:
    name = Typed("name", str)
    age = Typed("age", int)
    
    def __init__(self, name, age):
        self.name = name
        self.age = age

p = Person("Ali", 25)
print(p.name)  # Ali
print(p.age)   # 25

# p.name = 42    # TypeError: 'name' str olmalı, int verildi
# p.age = "yirmi"  # TypeError: 'age' int olmalı, str verildi

Validated Descriptor: Aralık Kontrolü

class Bounded:
    """Değer aralığı kontrolü yapan descriptor."""
    
    def __init__(self, min_val=None, max_val=None):
        self.min_val = min_val
        self.max_val = max_val
    
    def __set_name__(self, owner, name):
        self.name = name
    
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return obj.__dict__.get(self.name)
    
    def __set__(self, obj, value):
        if self.min_val is not None and value < self.min_val:
            raise ValueError(f"'{self.name}' en az {self.min_val} olmalı")
        if self.max_val is not None and value > self.max_val:
            raise ValueError(f"'{self.name}' en fazla {self.max_val} olmalı")
        obj.__dict__[self.name] = value

class Student:
    grade = Bounded(min_val=0, max_val=100)
    
    def __init__(self, name, grade):
        self.name = name
        self.grade = grade

s = Student("Ali", 85)
print(s.grade)  # 85

# s.grade = 150  # ValueError: 'grade' en fazla 100 olmalı
# s.grade = -5   # ValueError: 'grade' en az 0 olmalı

Lazy Property Descriptor

class LazyProperty:
    """İlk erişimde hesaplar, sonra cache'ler."""
    
    def __init__(self, func):
        self.func = func
    
    def __set_name__(self, owner, name):
        self.name = name
    
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        # İlk kez hesapla ve cache'le
        value = self.func(obj)
        setattr(obj, self.name, value)  # Instance dict'e yaz
        return value

class DataAnalyzer:
    def __init__(self, data):
        self.data = data
    
    @LazyProperty
    def mean(self):
        print("Ortalama hesaplanıyor...")
        return sum(self.data) / len(self.data)
    
    @LazyProperty
    def std_dev(self):
        print("Standart sapma hesaplanıyor...")
        mean = self.mean
        variance = sum((x - mean) ** 2 for x in self.data) / len(self.data)
        return variance ** 0.5

analyzer = DataAnalyzer([10, 20, 30, 40, 50])
print(analyzer.mean)     # "Hesaplanıyor..." → 30.0
print(analyzer.mean)     # 30.0 (cache'ten, hesaplamak yok)

__init_subclass__: Metaclass'a Basit Alternatif

Python 3.6 ile gelen __init_subclass__, metaclass kullanmadan subclass oluşturma sürecine müdahale etmeni sağlar. Çoğu zaman metaclass yerine bunu kullan.

class Plugin:
    """Plugin base class — alt sınıfları otomatik kaydeder."""
    
    _plugins = {}
    
    def __init_subclass__(cls, plugin_name=None, **kwargs):
        super().__init_subclass__(**kwargs)
        name = plugin_name or cls.__name__
        Plugin._plugins[name] = cls
        print(f"📝 Plugin kaydedildi: {name}")
    
    @classmethod
    def get_plugin(cls, name):
        return cls._plugins.get(name)
    
    @classmethod
    def list_plugins(cls):
        return list(cls._plugins.keys())

class JSONExporter(Plugin, plugin_name="json"):
    def export(self): return "JSON export"

class XMLExporter(Plugin, plugin_name="xml"):
    def export(self): return "XML export"

class CSVExporter(Plugin):  # plugin_name verilmezse sınıf adı kullanılır
    def export(self): return "CSV export"

# Çıktı:
# 📝 Plugin kaydedildi: json
# 📝 Plugin kaydedildi: xml
# 📝 Plugin kaydedildi: CSVExporter

print(Plugin.list_plugins())  # ['json', 'xml', 'CSVExporter']

# Dinamik plugin yükleme
exporter_cls = Plugin.get_plugin("json")
exporter = exporter_cls()
print(exporter.export())  # JSON export

Doğrulama ile __init_subclass__

class Serializable:
    """Alt sınıfların serialize() metodu olmasını zorunlu kılar."""
    
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        if not hasattr(cls, "serialize"):
            raise TypeError(
                f"'{cls.__name__}' sınıfı 'serialize()' metodunu "
                f"implemente etmelidir!"
            )

class JSONData(Serializable):
    def serialize(self):
        return "{}"

# class BrokenData(Serializable):  # TypeError!
#     pass  # serialize() yok!

💡 İpucu: __init_subclass__ metaclass'ın yaptığı işlerin %80'ini daha basit şekilde yapabilir. Metaclass yazmadan önce __init_subclass__ yeterli mi diye düşün.


Ne Zaman Metaclass Kullanmalı?

Nadiren!

Metaclass'lar güçlü ama karmaşık. Çoğu durumda daha basit alternatifler var:

İhtiyaçÇözüm
Attribute doğrulamaproperty veya descriptor
Subclass kaydı__init_subclass__
Metod ekleme/değiştirmeDecorator
SingletonClass variable veya __new__
Interface zorlamaABC (Abstract Base Class)

Metaclass Gerçekten Gereken Durumlar

  • ORM framework'leri (Django Model, SQLAlchemy)

  • Serializasyon kütüphaneleri

  • API framework'leri

  • Plugin sistemleri (karmaşık olanlar)

# Django bunu yapar (basitleştirilmiş):
# class User(Model):
#     name = CharField(max_length=100)
#     age = IntegerField()
# 
# Metaclass arka planda:
# - name ve age'i descriptor olarak yapılandırır
# - Veritabanı tablosunu oluşturur
# - CRUD metodlarını ekler

⚠️ Dikkat: Tim Peters (Zen of Python'ın yazarı) demiş ki: "Metaclass'lar derin büyüdür (deep magic) ve kullanıcıların %99'unun endişelenmesine gerek yok. Eğer ihtiyacınız olup olmadığını merak ediyorsanız, ihtiyacınız yok. Gerçekten ihtiyacı olan insanlar bunu kesinlikle bilir."


Metaclass vs Alternatifler: Karar Şeması

Sınıf oluşturma sürecine müdahale etmek istiyorum
│
├── Subclass'ları kaydetmek/doğrulamak istiyorum
│   └── __init_subclass__ kullan ✅
│
├── Attribute erişimini kontrol etmek istiyorum
│   └── Descriptor veya property kullan ✅
│
├── Metod eklemek/değiştirmek istiyorum
│   └── Class decorator kullan ✅
│
├── Soyut metod zorunlu kılmak istiyorum
│   └── ABC (abc.ABCMeta) kullan ✅
│
└── Yukarıdakilerin hiçbiri yetmiyor
    └── Metaclass düşün (ama iki kez düşün!) ⚠️

Yaygın Hatalar

1. Gereksiz Metaclass Kullanımı

# ❌ Basit bir iş için metaclass: overkill
class AutoReprMeta(type):
    def __new__(mcs, name, bases, namespace):
        def __repr__(self):
            attrs = ", ".join(f"{k}={v!r}" for k, v in self.__dict__.items())
            return f"{name}({attrs})"
        namespace["__repr__"] = __repr__
        return super().__new__(mcs, name, bases, namespace)

# ✅ Aynı işi decorator ile yap
def auto_repr(cls):
    def __repr__(self):
        attrs = ", ".join(f"{k}={v!r}" for k, v in self.__dict__.items())
        return f"{cls.__name__}({attrs})"
    cls.__repr__ = __repr__
    return cls

@auto_repr
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

2. Metaclass Çakışması

# Farklı metaclass'ları olan sınıflardan türetme
class Meta1(type): pass
class Meta2(type): pass

class A(metaclass=Meta1): pass
class B(metaclass=Meta2): pass

# class C(A, B): pass  # TypeError: metaclass conflict!

3. __set_name__ Kullanmamak (Descriptor'larda)

# ❌ İsmi manuel geçmek
class OldDescriptor:
    def __init__(self, name):
        self.name = name  # İsmi elle ver

class MyClass:
    x = OldDescriptor("x")  # "x"'i iki kez yazdık!

# ✅ __set_name__ ile otomatik
class NewDescriptor:
    def __set_name__(self, owner, name):
        self.name = name  # Otomatik!

class MyClass:
    x = NewDescriptor()  # İsim otomatik alınır

Özet

  • Metaclass, sınıfların nasıl oluşturulacağını kontrol eden "sınıfların sınıfı"dır. Python'da varsayılan metaclass type'tır.

  • `type(name, bases, dict)` ile sınıflar dinamik olarak oluşturulabilir. Metaclass'ta __new__ sınıf oluşturmadan önce, __init__ oluşturulduktan sonra çağrılır.

  • Descriptor protocol (__get__, __set__, __delete__) attribute erişimini kontrol eder. property aslında bir descriptor'dır.

  • `__init_subclass__`, metaclass gerektirmeden subclass oluşturma sürecine müdahale etmenin basit yoludur. Plugin kaydı, doğrulama gibi işler için idealdir.

  • Metaclass nadiren gerekir. Çoğu durumda __init_subclass__, descriptor, decorator veya ABC daha uygun ve basit çözümlerdir.

  • Tim Peters'ın tavsiyesi: "Metaclass'a ihtiyacınız olup olmadığını soruyorsanız, ihtiyacınız yoktur."