Multiple Inheritance ve MRO
Bir önceki derslerde tek parent'tan kalıtımı gördük. Ama Python, bir sınıfın birden fazla parent'tan miras almasına izin verir. Bu güçlü bir özellik ama dikkatli kullanılmazsa ciddi karmaşıklığa yol açabilir.
Bu derste çoklu kalıtımı, MRO'yu, diamond problem'i ve en önemlisi mixin pattern'ini öğreneceğiz.
Çoklu Kalıtım Nedir?
Tek kalıtımda bir sınıf tek bir parent'tan miras alır. Çoklu kalıtımda ise birden fazla parent'tan miras alabilir:
class Flyable:
def fly(self):
return "Uçuyor! 🦅"
class Swimmable:
def swim(self):
return "Yüzüyor! 🐟"
class Walkable:
def walk(self):
return "Yürüyor! 🚶"
# Çoklu kalıtım — birden fazla parent
class Duck(Flyable, Swimmable, Walkable):
def __init__(self, name):
self.name = name
def __str__(self):
return f"🦆 {self.name}"
donald = Duck("Donald")
print(donald.fly()) # Uçuyor! 🦅
print(donald.swim()) # Yüzüyor! 🐟
print(donald.walk()) # Yürüyor! 🚶Analoji: Çoklu kalıtımı İsviçre Çakısı gibi düşün. Bıçak, tornavida, makas, tirbuşon — farklı araçlardan gelen yetenekler tek bir objede birleşiyor. Çok kullanışlı ama kalabalık olursa elinde tutması zorlaşır!
Duck sınıfı üç farklı parent'tan miras alarak uçma, yüzme ve yürüme yeteneklerini ediniyor. Her parent küçük ve odaklı — sadece bir yetenek sunuyor.
Söz Dizimi
Çoklu kalıtım söz dizimi basit — parent sınıfları virgülle ayır:
class Child(Parent1, Parent2, Parent3):
passclass A:
def method_a(self):
return "A'dan"
class B:
def method_b(self):
return "B'den"
class C:
def method_c(self):
return "C'den"
class D(A, B, C):
def method_d(self):
return "D'den"
d = D()
print(d.method_a()) # A'dan
print(d.method_b()) # B'den
print(d.method_c()) # C'den
print(d.method_d()) # D'den
# Hangi parent'lardan miras alıyor?
print(D.__bases__) # (<class 'A'>, <class 'B'>, <class 'C'>)MRO (Method Resolution Order)
Peki birden fazla parent'ta aynı isimde metod varsa ne olur? Python hangi parent'ın metodunu çağırır?
İşte burada MRO (Method Resolution Order — Metod Çözümleme Sırası) devreye girer. Python, C3 Linearization algoritmasını kullanarak bir sıra belirler.
class A:
def who(self):
return "A"
class B(A):
def who(self):
return "B"
class C(A):
def who(self):
return "C"
class D(B, C):
pass
d = D()
print(d.who()) # B — neden?Cevap MRO'da:
print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)
# Veya daha okunabilir:
print(D.mro())
# [<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>]MRO sırası: D → B → C → A → object. Python who() metodunu ararken:
Önce
D'de arar — yok.Sonra
B'de arar — var!B.who()çağrılır.
Eğer B'de de olmasaydı, C'ye, sonra A'ya, en son object'e bakacaktı.
C3 Linearization Kuralları
MRO şu kurallara uyar:
Çocuk her zaman ebeveynden önce gelir.
Parent'ların sırası korunur (soldan sağa).
Tutarsız bir hiyerarşi varsa Python hata verir.
class X:
pass
class Y:
pass
class A(X, Y):
pass
class B(Y, X):
pass
# class C(A, B): # TypeError! Tutarsız MRO
# pass # A: X→Y, B: Y→X — çelişki!Diamond Problem ve Python'ın Çözümü
Diamond problem, çoklu kalıtımın en bilinen sorunu. Adını, kalıtım diyagramının elmas şekline benzemesinden alır:
A
/ \
B C
\ /
Dclass A:
def __init__(self):
print("A.__init__")
self.value = "A"
class B(A):
def __init__(self):
print("B.__init__")
super().__init__()
self.value = "B"
class C(A):
def __init__(self):
print("C.__init__")
super().__init__()
self.value = "C"
class D(B, C):
def __init__(self):
print("D.__init__")
super().__init__()
d = D()
# D.__init__
# B.__init__
# C.__init__
# A.__init__Dikkat: A.__init__ sadece bir kez çağrıldı! Bu, Python'un diamond problem'i çözmesi. super() MRO'yu takip eder ve her sınıfın __init__'i sadece bir kez çalışır.
super() MRO'ya Göre Çalışır
Bu çok önemli: super() her zaman "parent class" anlamına gelmez — MRO'daki bir sonraki sınıf anlamına gelir.
print(D.__mro__)
# D → B → C → A → object
# D'deki super().__init__() → B.__init__()
# B'deki super().__init__() → C.__init__() (parent'ı A değil, MRO'daki sonraki!)
# C'deki super().__init__() → A.__init__()B'nin parent'ı A'dır ama super() MRO'daki sonraki sınıf olan C'yi çağırır. Bu, diamond problem'de A'nın iki kez çağrılmasını önler.
super() İle __init__ ve Argümanlar
Diamond problem'de __init__ argümanları zorlaşabilir. En temiz yol **kwargs kullanmak:
class A:
def __init__(self, **kwargs):
print(f"A.__init__ kwargs={kwargs}")
class B(A):
def __init__(self, b_param=None, **kwargs):
print(f"B.__init__ b_param={b_param}")
super().__init__(**kwargs)
class C(A):
def __init__(self, c_param=None, **kwargs):
print(f"C.__init__ c_param={c_param}")
super().__init__(**kwargs)
class D(B, C):
def __init__(self, **kwargs):
print("D.__init__")
super().__init__(**kwargs)
d = D(b_param="hello", c_param="world")
# D.__init__
# B.__init__ b_param=hello
# C.__init__ c_param=world
# A.__init__ kwargs={}⚠️ Dikkat: Çoklu kalıtımda
super().__init__()kullanırken tüm__init__metodlarının uyumlu argümanlar alması gerekir.**kwargspattern'i bunu kolaylaştırır ama karmaşık hiyerarşilerde hâlâ zorlayıcı olabilir.
Mixin Pattern
Mixin, çoklu kalıtımın en temiz ve en önerilen kullanımıdır. Bir mixin, tek başına kullanılmak için değil, başka sınıflara belirli bir yetenek eklemek için tasarlanmış küçük, odaklı bir sınıftır.
Kurallar:
Mixin tek başına instantiate edilmez.
Mixin küçük ve tek bir işe odaklıdır.
İsmi genellikle
...Mixinile biter.Genellikle
__init__tanımlamaz.
JsonMixin: JSON Dönüşüm Yeteneği
import json
class JsonMixin:
"""JSON serileştirme yeteneği ekler."""
def to_json(self, indent=2):
"""Nesneyi JSON string'e çevirir."""
data = {}
for key, value in vars(self).items():
if not key.startswith('_'):
data[key] = value
return json.dumps(data, indent=indent, ensure_ascii=False)
@classmethod
def from_json(cls, json_str):
"""JSON string'den nesne oluşturur."""
data = json.loads(json_str)
return cls(**data)
def to_dict(self):
"""Nesneyi sözlüğe çevirir."""
return {k: v for k, v in vars(self).items() if not k.startswith('_')}
class User(JsonMixin):
def __init__(self, username, email, age=None):
self.username = username
self.email = email
self.age = age
class Product(JsonMixin):
def __init__(self, name, price, stock=0):
self.name = name
self.price = price
self.stock = stock
# User ile kullanım
user = User("ali", "ali@mail.com", 25)
print(user.to_json())
# {
# "username": "ali",
# "email": "ali@mail.com",
# "age": 25
# }
# JSON'dan geri oluştur
user2 = User.from_json('{"username": "veli", "email": "veli@mail.com", "age": 30}')
print(user2.username) # veli
# Product ile de aynı mixin çalışır!
product = Product("Laptop", 15000, 10)
print(product.to_json())LogMixin: Loglama Yeteneği
from datetime import datetime
class LogMixin:
"""Loglama yeteneği ekler."""
_log_history = [] # Dikkat: class-level (tüm instance'lar paylaşır)
def log(self, message, level="INFO"):
timestamp = datetime.now().strftime("%H:%M:%S")
entry = f"[{timestamp}] [{level}] {self.__class__.__name__}: {message}"
self._log_history.append(entry)
print(entry)
def log_warning(self, message):
self.log(message, level="WARNING")
def log_error(self, message):
self.log(message, level="ERROR")
@classmethod
def get_logs(cls):
return cls._log_history.copy()
class Database(LogMixin):
def __init__(self, host):
self.host = host
self.log(f"Database bağlantısı oluşturuldu: {host}")
def query(self, sql):
self.log(f"Query: {sql}")
return f"Result for: {sql}"
def close(self):
self.log("Bağlantı kapatıldı")
db = Database("localhost:5432")
# [14:30:25] [INFO] Database: Database bağlantısı oluşturuldu: localhost:5432
db.query("SELECT * FROM users")
# [14:30:25] [INFO] Database: Query: SELECT * FROM users
db.close()
# [14:30:25] [INFO] Database: Bağlantı kapatıldıValidatorMixin: Doğrulama Yeteneği
class ValidatorMixin:
"""Doğrulama yeteneği ekler."""
_validation_rules = {}
def validate(self):
"""Tüm validation rule'ları çalıştırır."""
errors = []
for field, rules in self._validation_rules.items():
value = getattr(self, field, None)
for rule_name, check in rules.items():
if not check(value):
errors.append(f"{field}: {rule_name} hatası")
return errors
def is_valid(self):
return len(self.validate()) == 0Birden Fazla Mixin Kullanma
class User(JsonMixin, LogMixin):
def __init__(self, username, email):
self.username = username
self.email = email
self.log(f"Yeni kullanıcı: {username}")
def update_email(self, new_email):
old = self.email
self.email = new_email
self.log(f"Email güncellendi: {old} → {new_email}")
user = User("ali", "ali@mail.com")
# [14:30:25] [INFO] User: Yeni kullanıcı: ali
user.update_email("ali@newmail.com")
# [14:30:25] [INFO] User: Email güncellendi: ali@mail.com → ali@newmail.com
print(user.to_json())
# {"username": "ali", "email": "ali@newmail.com"}User sınıfı hem JSON serileştirme hem de loglama yeteneğine sahip — iki mixin sayesinde.
Pratik: Timestamp ve Serialization Mixin'leri
Gerçek bir projede kullanabileceğin mixin'ler:
from datetime import datetime
import json
class TimestampMixin:
"""Oluşturulma ve güncellenme zamanı ekler."""
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
original_init = cls.__init__
def new_init(self, *args, **kwargs):
original_init(self, *args, **kwargs)
if not hasattr(self, 'created_at'):
self.created_at = datetime.now()
self.updated_at = datetime.now()
cls.__init__ = new_init
def touch(self):
"""Güncellenme zamanını şimdiye ayarlar."""
self.updated_at = datetime.now()
class ReprMixin:
"""Otomatik __repr__ oluşturur."""
def __repr__(self):
attrs = ", ".join(
f"{k}={v!r}"
for k, v in vars(self).items()
if not k.startswith('_')
)
return f"{self.__class__.__name__}({attrs})"
class EqualityMixin:
"""Attribute tabanlı eşitlik karşılaştırması."""
def __eq__(self, other):
if type(self) != type(other):
return NotImplemented
return vars(self) == vars(other)
def __hash__(self):
return hash(tuple(sorted(vars(self).items())))
class ComparableMixin:
"""Sıralama yeteneği ekler (key metodu gerektirir)."""
def _compare_key(self):
raise NotImplementedError("_compare_key() implement edin")
def __lt__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return self._compare_key() < other._compare_key()
def __le__(self, other):
return self == other or self < other
def __gt__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return self._compare_key() > other._compare_key()
def __ge__(self, other):
return self == other or self > otherclass Product(JsonMixin, ReprMixin, EqualityMixin, ComparableMixin):
def __init__(self, name, price, stock=0):
self.name = name
self.price = price
self.stock = stock
def _compare_key(self):
return self.price # Fiyata göre sırala
p1 = Product("Laptop", 15000, 5)
p2 = Product("Mouse", 500, 50)
p3 = Product("Laptop", 15000, 5)
# ReprMixin
print(repr(p1)) # Product(name='Laptop', price=15000, stock=5)
# EqualityMixin
print(p1 == p3) # True
# ComparableMixin
print(p1 > p2) # True (15000 > 500)
print(sorted([p1, p2])) # [Product(Mouse, 500), Product(Laptop, 15000)]
# JsonMixin
print(p1.to_json())⚠️ Karmaşık Kalıtım Hiyerarşisi: Anti-Pattern
Çoklu kalıtım güçlü ama aşırı kullanıldığında karmaşıklık patlar:
# ❌ ANTİ-PATTERN: Spaghetti Inheritance
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
class E(B): pass
class F(D, E): pass # MRO'yu kafadan söyleyebilir misin? Hayır!Ne Zaman Kaçınmalı?
3'ten fazla parent → Muhtemelen tasarımda sorun var.
Mixin olmayan çoklu kalıtım → Genellikle composition daha iyi.
Derin ve geniş hiyerarşi → Anlaşılmaz hale gelir.
`super()` zincirinde sürpriz → MRO beklemediğin sınıfı çağırabilir.
Alternatifler
# ❌ Karmaşık çoklu kalıtım
class MyClass(DatabaseMixin, CacheMixin, LogMixin, ValidatorMixin,
SerializerMixin, NotificationMixin):
pass
# ✅ Composition ile
class MyClass:
def __init__(self):
self.db = Database()
self.cache = Cache()
self.logger = Logger()
self.validator = Validator()
self.serializer = Serializer()
self.notifier = Notifier()Mixin Kuralları (Best Practice)
| Kural | Açıklama |
|---|---|
| Tek sorumluluk | Her mixin tek bir yetenek |
__init__ yok (ideal) | Veya **kwargs + super() |
| İsim convention | SomethingMixin |
| Bağımsızlık | Mixin'ler birbirine bağımlı olmasın |
| Sade tutmak | Max 2-3 mixin bir sınıfta |
💡 İpucu: Mixin pattern'i Python ekosisteminde çok yaygındır. Django'nun
LoginRequiredMixin,ListView,CreateViewsınıfları; DRF'ninListModelMixin,CreateModelMixinsınıfları hep bu pattern'i kullanır. Framework öğrendiğinde bu bilgi çok işine yarayacak.
__mro__ ve mro() ile Debug
MRO'yu anlamak debug için kritik. İki yoldan görebilirsin:
class A:
def greet(self):
return "A"
class B(A):
pass
class C(A):
def greet(self):
return "C"
class D(B, C):
pass
# MRO görüntüleme
print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)
# Daha okunabilir
for i, cls in enumerate(D.mro()):
print(f" {i}. {cls.__name__}")
# 0. D
# 1. B
# 2. C
# 3. A
# 4. object
# D().greet() → B'de yok → C'de var → "C"
print(D().greet()) # CMRO'yu Tahmin Etme
Basit kural: soldan sağa, derinlik öncelikli ama ortak ataları sona bırak.
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
# D → B → C → A → object
# B ve C'nin ortak atası A, en sona
class X: pass
class Y: pass
class M(X, Y): pass
class N(Y, X): pass
# class O(M, N): pass # TypeError — çelişkili sıra!Gerçek Dünya Örneği: Web Framework Tarzı Mixin'ler
class View:
"""Temel view sınıfı."""
def render(self):
return f"<html>{self.get_content()}</html>"
def get_content(self):
return ""
class AuthMixin:
"""Kimlik doğrulama gerektirir."""
_authenticated = False
def check_auth(self):
if not self._authenticated:
return "<h1>401 Unauthorized</h1>"
return None
def login(self):
self._authenticated = True
class CacheMixin:
"""Response caching yeteneği."""
_cache = {}
def get_cached(self, key):
return self._cache.get(key)
def set_cache(self, key, value, ttl=300):
self._cache[key] = value
class PaginationMixin:
"""Sayfalama yeteneği."""
_per_page = 10
def paginate(self, items, page=1):
start = (page - 1) * self._per_page
end = start + self._per_page
return {
"items": items[start:end],
"page": page,
"total_pages": (len(items) + self._per_page - 1) // self._per_page,
"total_items": len(items),
}
class UserListView(AuthMixin, PaginationMixin, CacheMixin, View):
"""Kullanıcı listesi — korumalı, sayfalı, cacheli."""
_per_page = 5
def get_content(self):
# Auth kontrolü
auth_error = self.check_auth()
if auth_error:
return auth_error
# Cache kontrolü
cached = self.get_cached("user_list")
if cached:
return cached
# Veri
users = ["Ali", "Veli", "Ayşe", "Fatma", "Mehmet",
"Zeynep", "Can", "Ece", "Burak", "Selin",
"Deniz", "Kerem"]
result = self.paginate(users, page=2)
content = f"<h1>Kullanıcılar (Sayfa {result['page']}/{result['total_pages']})</h1>"
content += "<ul>"
for user in result["items"]:
content += f"<li>{user}</li>"
content += "</ul>"
self.set_cache("user_list", content)
return content
# Kullanım
view = UserListView()
# Kimlik doğrulama yapılmadan
print(view.render()) # <html><h1>401 Unauthorized</h1></html>
# Giriş yap
view.login()
print(view.render())
# <html><h1>Kullanıcılar (Sayfa 2/3)</h1><ul><li>Zeynep</li>...</ul></html>Özet
Çoklu kalıtım bir sınıfın birden fazla parent'tan miras almasıdır:
class D(B, C).MRO (Method Resolution Order) C3 linearization ile hesaplanır.
ClassName.mro()veya__mro__ile görüntülenir.Diamond problem: Ortak atanın birden fazla yoldan miras alınması. Python MRO ile çözer — her sınıf sadece bir kez çağrılır.
`super()` MRO'ya göre çalışır — her zaman "parent" demek değildir, "MRO'daki sonraki sınıf" demektir.
Mixin pattern: Küçük, odaklı, tek yetenekli sınıflar. En temiz çoklu kalıtım kullanımı.
Karmaşık kalıtım hiyerarşileri anti-pattern'dir. 3+ parent veya derin zincirlerden kaçın, alternatif olarak composition düşün.
AI Asistan
Sorularını yanıtlamaya hazır