← Kursa Dön
📄 Text · 15 min

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):
    pass
class 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:

  1. Önce D'de arar — yok.

  2. 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:

  1. Çocuk her zaman ebeveynden önce gelir.

  2. Parent'ların sırası korunur (soldan sağa).

  3. 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
     \ /
      D
class 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. **kwargs pattern'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:

  1. Mixin tek başına instantiate edilmez.

  2. Mixin küçük ve tek bir işe odaklıdır.

  3. İsmi genellikle ...Mixin ile biter.

  4. 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()) == 0

Birden 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 > other
class 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ı?

  1. 3'ten fazla parent → Muhtemelen tasarımda sorun var.

  2. Mixin olmayan çoklu kalıtım → Genellikle composition daha iyi.

  3. Derin ve geniş hiyerarşi → Anlaşılmaz hale gelir.

  4. `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)

KuralAçıklama
Tek sorumlulukHer mixin tek bir yetenek
__init__ yok (ideal)Veya **kwargs + super()
İsim conventionSomethingMixin
BağımsızlıkMixin'ler birbirine bağımlı olmasın
Sade tutmakMax 2-3 mixin bir sınıfta

💡 İpucu: Mixin pattern'i Python ekosisteminde çok yaygındır. Django'nun LoginRequiredMixin, ListView, CreateView sınıfları; DRF'nin ListModelMixin, CreateModelMixin sı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())  # C

MRO'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.