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:
type(obj)— nesnenin tipini dönertype(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 familiarisKalı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)) # TrueMetaclass 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ı HAZIRMetaclass 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 verildiValidated 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 exportDoğ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ğrulama | property veya descriptor |
| Subclass kaydı | __init_subclass__ |
| Metod ekleme/değiştirme | Decorator |
| Singleton | Class variable veya __new__ |
| Interface zorlama | ABC (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 = y2. 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.propertyaslı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."
AI Asistan
Sorularını yanıtlamaya hazır