Kalıtım ve super()
Diyelim ki bir oyun yazıyorsun. Savaşçı, okçu ve büyücü sınıfların var. Her birinin adı, canı, seviyesi var. Her biri hareket edebilir, hasar alabilir. Ama her birinin kendine özel yetenekleri de var. Bu ortak özellikleri her sınıfta tekrar tekrar mı yazacaksın?
Tabii ki hayır. İşte kalıtım (inheritance) tam da bu sorunu çözer.
Kalıtım Nedir?
Kalıtım, bir sınıfın başka bir sınıftan özellik ve davranışları devralması mekanizmasıdır. Miras alan sınıf, miras veren sınıfın tüm attribute ve metodlarını otomatik olarak kullanabilir.
Aile Ağacı Analojisi 🌳
Kalıtımı bir aile ağacı gibi düşün. Sen anne-babandan bazı özellikleri miras alırsın — göz rengi, boy, saç tipi. Ama kendi özelliklerini de geliştirirsin. Anne-babanın özelliklerini "kopyalamıyorsun", onlardan devralıyorsun ve üzerine kendi özelliklerini ekliyorsun.
Programlamada da öyle:
Parent (ebeveyn) sınıf: Temel özellikleri tanımlar. Buna base class veya superclass da denir.
Child (çocuk) sınıf: Parent'tan miras alır, yeni özellikler ekler veya mevcut olanları değiştirir. Buna derived class veya subclass da denir.
# Parent class
class Character:
def __init__(self, name, hp):
self.name = name
self.hp = hp
self.level = 1
def take_damage(self, amount):
self.hp -= amount
if self.hp <= 0:
return f"{self.name} yenildi!"
return f"{self.name} {amount} hasar aldı. HP: {self.hp}"
def level_up(self):
self.level += 1
return f"{self.name} seviye atladı! Seviye: {self.level}"
# Child classes
class Warrior(Character):
def slash(self):
return f"{self.name} kılıçla saldırıyor!"
class Archer(Character):
def shoot(self):
return f"{self.name} ok atıyor!"
class Mage(Character):
def cast_spell(self):
return f"{self.name} büyü yapıyor!"# Kullanım
w = Warrior("Arthas", 150)
a = Archer("Legolas", 100)
m = Mage("Gandalf", 80)
# Parent'ın metodları çalışır
print(w.take_damage(30)) # Arthas 30 hasar aldı. HP: 120
print(a.level_up()) # Legolas seviye atladı! Seviye: 2
# Child'a özel metodlar
print(w.slash()) # Arthas kılıçla saldırıyor!
print(m.cast_spell()) # Gandalf büyü yapıyor!Warrior, Archer ve Mage sınıfları Character'dan miras alıyor. name, hp, take_damage(), level_up() gibi ortak şeyleri tekrar yazmadık — sadece kendilerine özel metodları ekledik.
class Child(Parent): Söz Dizimi
Kalıtım söz dizimi çok basit — sınıf adından sonra parantez içinde parent sınıfı yazarsın:
class Animal:
def __init__(self, name, sound):
self.name = name
self.sound = sound
def speak(self):
return f"{self.name}: {self.sound}!"
class Dog(Animal): # Animal'dan miras alıyor
def fetch(self):
return f"{self.name} topu getirdi!"
class Cat(Animal): # Animal'dan miras alıyor
def purr(self):
return f"{self.name} mırıldanıyor..."
dog = Dog("Karabaş", "Hav hav")
cat = Cat("Boncuk", "Miyav")
print(dog.speak()) # Karabaş: Hav hav!
print(dog.fetch()) # Karabaş topu getirdi!
print(cat.speak()) # Boncuk: Miyav!
print(cat.purr()) # Boncuk mırıldanıyor...Parantez yazmazsan sınıf object'ten miras alır (Python 3'te her sınıf zaten object'ten miras alır):
class Foo: # Aslında class Foo(object) ile aynı
pass
print(Foo.__bases__) # (<class 'object'>,)Kalıtım Terminolojisi
Farklı kaynaklarda farklı isimler görebilirsin, hepsi aynı anlama gelir:
| Miras veren | Miras alan |
|---|---|
| Parent class | Child class |
| Base class | Derived class |
| Superclass | Subclass |
| Üst sınıf | Alt sınıf |
super() Kullanımı
super(), parent sınıfın metodlarına erişmeni sağlar. En yaygın kullanımı __init__ içinde parent'ın constructor'ını çağırmaktır.
class Vehicle:
def __init__(self, brand, year):
self.brand = brand
self.year = year
self.is_running = False
def start(self):
self.is_running = True
return f"{self.brand} çalıştırıldı."
def info(self):
return f"{self.year} {self.brand}"
class ElectricCar(Vehicle):
def __init__(self, brand, year, battery_capacity):
super().__init__(brand, year) # Parent'ın __init__'ini çağır
self.battery_capacity = battery_capacity
self.battery_level = 100
def charge(self):
self.battery_level = 100
return f"{self.brand} şarj edildi."
def info(self): # Override
base = super().info() # Parent'ın info()'sunu çağır
return f"{base} (Elektrikli, {self.battery_capacity}kWh)"
tesla = ElectricCar("Tesla", 2024, 75)
print(tesla.start()) # Tesla çalıştırıldı. — parent metodu
print(tesla.charge()) # Tesla şarj edildi. — child metodu
print(tesla.info()) # 2024 Tesla (Elektrikli, 75kWh) — overridesuper() Olmadan Ne Olur?
class Parent:
def __init__(self, x):
self.x = x
print(f"Parent init: x={x}")
class Child(Parent):
def __init__(self, x, y):
# super().__init__(x) çağırmadık!
self.y = y
print(f"Child init: y={y}")
c = Child(1, 2)
# Child init: y=2
# print(c.x) # AttributeError! Parent'ın __init__'i çağrılmadı!
print(c.y) # 2⚠️ Dikkat: Child sınıfta
__init__tanımlıyorsan,super().__init__()çağırmayı unutma! Yoksa parent'ın attribute'ları oluşturulmaz veAttributeErroralırsın. Bu, yeni başlayanların en sık yaptığı hatalardan biri.
super() Farklı Metodlarda
super() sadece __init__'te değil, herhangi bir override metodda parent'ın versiyonunu çağırmak için kullanılabilir:
class FileHandler:
def save(self, data):
print(f"Dosya kaydediliyor: {len(data)} byte")
return True
class LoggingFileHandler(FileHandler):
def save(self, data):
print(f"[LOG] save() çağrıldı, veri boyutu: {len(data)}")
result = super().save(data) # Parent'ın save'ini çağır
print(f"[LOG] save() tamamlandı, sonuç: {result}")
return result
handler = LoggingFileHandler()
handler.save("merhaba dünya")
# [LOG] save() çağrıldı, veri boyutu: 13
# Dosya kaydediliyor: 13 byte
# [LOG] save() tamamlandı, sonuç: TrueMethod Overriding
Child sınıf, parent'ın bir metodunu aynı isimle yeniden tanımlayarak davranışını değiştirebilir. Buna method overriding denir.
class Shape:
def area(self):
return 0
def describe(self):
return f"Bu bir {self.__class__.__name__}. Alanı: {self.area():.2f}"
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self): # Override!
import math
return math.pi * self.radius ** 2
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self): # Override!
return self.width * self.height
c = Circle(5)
r = Rectangle(4, 6)
print(c.describe()) # Bu bir Circle. Alanı: 78.54
print(r.describe()) # Bu bir Rectangle. Alanı: 24.00
# describe() parent'ta tanımlı ama area() çağırdığında
# child'ın override edilmiş versiyonunu çalıştırıyor!Bu çok güçlü bir mekanizma. describe() parent'ta tanımlı ama area() çağırdığında child'ın override'ını kullanıyor. Bu, polimorfizmin temelidir (bir sonraki derste detaylıca göreceğiz).
Tamamen Yeni vs Genişletme
Override ederken iki yaklaşım var:
Yaklaşım 1: Tamamen Yeni — Parent'ın metodunu kullanmadan sıfırdan yazarsın.
class Logger:
def log(self, message):
print(f"[LOG] {message}")
class TimestampLogger(Logger):
def log(self, message):
# Tamamen yeni — parent'ın log'unu KULLANMIYORUZ
from datetime import datetime
now = datetime.now().strftime("%H:%M:%S")
print(f"[{now}] {message}")Yaklaşım 2: Genişletme — Parent'ın metodunu çağırıp üzerine eklersin.
class VerboseLogger(Logger):
def log(self, message):
# Genişletme — parent'ın log'unu ÇAĞIRIYORUZ + ekstra iş
super().log(message) # Parent'ın log'unu çağır
print(f"[DETAIL] Message length: {len(message)}")
vl = VerboseLogger()
vl.log("Sistem başladı")
# [LOG] Sistem başladı
# [DETAIL] Message length: 14__init__ Override
__init__ override etmek en yaygın senaryodur:
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
class Manager(Employee):
def __init__(self, name, salary, department, team_size):
super().__init__(name, salary) # Parent'ın init'i
self.department = department # Yeni attribute
self.team_size = team_size # Yeni attribute
def __str__(self):
return (f"{self.name} — {self.department} Müdürü "
f"(Takım: {self.team_size} kişi)")
class Developer(Employee):
def __init__(self, name, salary, languages):
super().__init__(name, salary)
self.languages = languages
def __str__(self):
langs = ", ".join(self.languages)
return f"{self.name} — Developer ({langs})"
m = Manager("Ahmet", 50000, "IT", 12)
d = Developer("Zeynep", 40000, ["Python", "JavaScript", "Go"])
print(m) # Ahmet — IT Müdürü (Takım: 12 kişi)
print(d) # Zeynep — Developer (Python, JavaScript, Go)isinstance() ve issubclass()
Bu iki fonksiyon, kalıtım ilişkilerini kontrol etmek için kullanılır:
isinstance(): Nesne-Sınıf İlişkisi
class Animal:
pass
class Dog(Animal):
pass
class Cat(Animal):
pass
class GoldenRetriever(Dog):
pass
rex = GoldenRetriever()
print(isinstance(rex, GoldenRetriever)) # True
print(isinstance(rex, Dog)) # True — Dog'dan miras aldı
print(isinstance(rex, Animal)) # True — Animal'dan da miras aldı
print(isinstance(rex, Cat)) # False
# Tuple ile birden fazla tip kontrolü
print(isinstance(rex, (Dog, Cat))) # True — en az biri eşleşirseissubclass(): Sınıf-Sınıf İlişkisi
print(issubclass(GoldenRetriever, Dog)) # True
print(issubclass(GoldenRetriever, Animal)) # True
print(issubclass(Dog, Cat)) # False
print(issubclass(Animal, Animal)) # True — kendisi de subclass!type() vs isinstance()
rex = GoldenRetriever()
# type() TAM eşleşme arar
print(type(rex) == GoldenRetriever) # True
print(type(rex) == Dog) # False — tam eşleşme yok!
print(type(rex) == Animal) # False
# isinstance() kalıtım zincirini kontrol eder
print(isinstance(rex, GoldenRetriever)) # True
print(isinstance(rex, Dog)) # True
print(isinstance(rex, Animal)) # True💡 İpucu: Tip kontrolü yapacaksan neredeyse her zaman
isinstance()kullan.type()kalıtımı görmezden gelir ve kodunu kırılgan yapar. Tek istisna: tam olarak "bu sınıftan mı, alt sınıf değil mi?" diye kontrol etmek istediğin durum.
Kalıtım vs Composition: "is-a" vs "has-a"
Kalıtım her derde deva değil. Bazen composition (bileşim) daha iyi bir seçim.
"is-a" İlişkisi → Kalıtım
"X bir Y'dir" diyebiliyorsan kalıtım mantıklı:
Köpek bir hayvandır →
class Dog(Animal)Elektrikli araba bir araçtır →
class ElectricCar(Vehicle)Öğrenci bir kişidir →
class Student(Person)
"has-a" İlişkisi → Composition
"X'in bir Y'si vardır" diyorsan composition kullan:
Araba bir motora sahiptir →
self.engine = Engine()Üniversite öğrencilere sahiptir →
self.students = [Student(...)]Bilgisayar bir CPU'ya sahiptir →
self.cpu = CPU()
# ❌ YANLIŞ — Kalıtım ile
class Engine:
def __init__(self, hp):
self.hp = hp
def start(self):
return f"{self.hp}HP motor çalıştı!"
class Car(Engine): # Araba bir motor MU? Hayır!
pass
# ✅ DOĞRU — Composition ile
class Engine:
def __init__(self, hp):
self.hp = hp
def start(self):
return f"{self.hp}HP motor çalıştı!"
class Car:
def __init__(self, brand, engine_hp):
self.brand = brand
self.engine = Engine(engine_hp) # Arabanın bir motoru VAR
def start(self):
engine_msg = self.engine.start()
return f"{self.brand}: {engine_msg}"
car = Car("Toyota", 150)
print(car.start()) # Toyota: 150HP motor çalıştı!Karışık Kullanım — Gerçek Dünya
Gerçek dünyada genellikle ikisi bir arada kullanılır:
class Battery:
def __init__(self, capacity):
self.capacity = capacity
self.level = 100
def charge(self):
self.level = 100
return "Şarj tamamlandı."
def drain(self, amount):
self.level = max(0, self.level - amount)
class GPS:
def __init__(self):
self.location = (41.0082, 28.9784) # İstanbul
def get_location(self):
return f"Konum: {self.location}"
class Vehicle:
def __init__(self, brand, year):
self.brand = brand
self.year = year
def start(self):
return f"{self.brand} çalıştı."
def __str__(self):
return f"{self.year} {self.brand}"
class ElectricCar(Vehicle): # Kalıtım: ElectricCar IS-A Vehicle
def __init__(self, brand, year, battery_cap):
super().__init__(brand, year)
self.battery = Battery(battery_cap) # Composition: HAS-A Battery
self.gps = GPS() # Composition: HAS-A GPS
def drive(self, km):
drain = km * 0.2 # km başına %0.2 şarj
self.battery.drain(drain)
return f"{km}km gidildi. Batarya: %{self.battery.level:.0f}"
def charge(self):
return self.battery.charge()
tesla = ElectricCar("Tesla", 2024, 75)
print(tesla.start()) # Tesla çalıştı.
print(tesla.drive(50)) # 50km gidildi. Batarya: %90
print(tesla.gps.get_location()) # Konum: (41.0082, 28.9784)
print(tesla.charge()) # Şarj tamamlandı.Kalıtım vs Composition Karar Tablosu
| Kriter | Kalıtım | Composition |
|---|---|---|
| İlişki tipi | "is-a" (X bir Y'dir) | "has-a" (X'in Y'si var) |
| Bağımlılık | Sıkı (tight coupling) | Gevşek (loose coupling) |
| Esneklik | Az (parent değişirse child kırılabilir) | Çok (bileşen değiştirilebilir) |
| Kod tekrarı | Az (miras ile paylaşım) | Delegation gerektirir |
| Runtime değişiklik | Zor (sınıf sabit) | Kolay (bileşen değiştirilebilir) |
💡 İpucu: "Kalıtım mı, composition mı?" diye şüpheye düştüğünde composition tercih et. Kalıtım güçlü bir araçtır ama aşırı kullanımı kodunu kırılgan yapar. "Favor composition over inheritance" yazılım dünyasının en bilinen prensiplerinden biridir.
Protected (_) ve Private (__) Kalıtımda Davranışı
Erişim belirleyicileri kalıtımda nasıl davranır?
_protected: Alt Sınıftan Erişilebilir
Tek alt çizgi convention'u "bu iç kullanım için" der ama alt sınıflar erişebilir. Aslında buna "protected" denmesinin nedeni budur — dışarıdan değil ama alt sınıftan erişilebilir:
class Animal:
def __init__(self, name):
self._name = name # "protected" — alt sınıf erişebilir
self._energy = 100
def _rest(self):
"""İç metod — alt sınıflar kullanabilir."""
self._energy = min(100, self._energy + 20)
class Dog(Animal):
def play(self):
self._energy -= 30 # Alt sınıftan erişim OK
if self._energy < 20:
self._rest() # Protected metoda erişim OK
return f"{self._name} oynadı! Enerji: {self._energy}"
def status(self):
return f"{self._name}: %{self._energy} enerji"
d = Dog("Rex")
print(d.play()) # Rex oynadı! Enerji: 70
print(d.play()) # Rex oynadı! Enerji: 40
print(d.play()) # Rex oynadı! Enerji: 30 (10 olacaktı ama _rest çağrıldı)
print(d.status()) # Rex: %30 enerji__private: Name Mangling Etkisi
Çift alt çizgi ile name mangling uygulanır. Alt sınıfta aynı isimle erişmeye çalışırsan farklı bir attribute oluşur:
class Animal:
def __init__(self, name):
self.__id = id(self) # Name mangling → _Animal__id
def get_id(self):
return self.__id # Animal scope'unda → _Animal__id
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name)
self.__id = f"DOG-{id(self)}" # _Dog__id — FARKLI attribute!
def show_ids(self):
# self.__id → _Dog__id
print(f"Dog id: {self.__id}")
# Parent'ın __id'sine erişmek zor
print(f"Animal id: {self._Animal__id}")
d = Dog("Rex", "Golden")
d.show_ids()
# Dog id: DOG-140234567890
# Animal id: 140234567890
print(d.__dict__)
# {'_Animal__id': 140234567890, '_Dog__id': 'DOG-140234567890'}Kalıtımda Erişim Özeti
| Erişim | Parent metod | Child metod | Dışarıdan |
|---|---|---|---|
self.public | ✅ | ✅ | ✅ |
self._protected | ✅ | ✅ | ⚠️ (yapabilirsin ama yapma) |
self.__private | ✅ | ❌ (farklı mangled name) | ❌ |
⚠️ Dikkat: Çift alt çizgiyi (
__) kalıtımda kasıtlı olarak kullan — sadece alt sınıfın bu attribute'u yanlışlıkla ezmesini engellemek istediğin durumlarda. Genel kullanımda tek alt çizgi (_) yeterlidir.
Kalıtım Zinciri
Kalıtım birden fazla seviye derinliğinde olabilir:
class LivingThing:
def breathe(self):
return "Nefes alıyor..."
class Animal(LivingThing):
def move(self):
return "Hareket ediyor..."
class Dog(Animal):
def bark(self):
return "Hav hav!"
class GoldenRetriever(Dog):
def fetch(self):
return "Topu getirdi!"
gr = GoldenRetriever()
# Tüm zincirdeki metodlar erişilebilir
print(gr.fetch()) # GoldenRetriever
print(gr.bark()) # Dog
print(gr.move()) # Animal
print(gr.breathe()) # LivingThing
# MRO (Method Resolution Order)
print(GoldenRetriever.__mro__)
# (GoldenRetriever, Dog, Animal, LivingThing, object)Python, bir metod arandığında MRO sırasını takip eder: önce kendi sınıfında arar, bulamazsa parent'a, sonra grandparent'a… ta ki object'e kadar.
Gerçek Dünya Örneği: Bildirim Sistemi
from datetime import datetime
class Notification:
"""Temel bildirim sınıfı."""
_total_sent = 0
def __init__(self, recipient, message):
self.recipient = recipient
self.message = message
self.created_at = datetime.now()
self.sent = False
self.sent_at = None
def send(self):
"""Alt sınıflar bu metodu override eder."""
raise NotImplementedError("Alt sınıf send() metodunu implement etmeli!")
def _mark_sent(self):
"""Gönderildi olarak işaretle."""
self.sent = True
self.sent_at = datetime.now()
Notification._total_sent += 1
@classmethod
def total_sent(cls):
return cls._total_sent
def __str__(self):
status = "✅" if self.sent else "⏳"
return f"{status} [{self.__class__.__name__}] → {self.recipient}: {self.message}"
def __repr__(self):
return f"{self.__class__.__name__}('{self.recipient}', '{self.message}')"
class EmailNotification(Notification):
def __init__(self, recipient, message, subject):
super().__init__(recipient, message)
self.subject = subject
def send(self):
print(f"📧 Email → {self.recipient} | Konu: {self.subject}")
self._mark_sent()
class SMSNotification(Notification):
MAX_LENGTH = 160
def __init__(self, recipient, message):
if len(message) > self.MAX_LENGTH:
message = message[:self.MAX_LENGTH - 3] + "..."
super().__init__(recipient, message)
def send(self):
print(f"📱 SMS → {self.recipient} | {self.message}")
self._mark_sent()
class PushNotification(Notification):
def __init__(self, recipient, message, title, icon="🔔"):
super().__init__(recipient, message)
self.title = title
self.icon = icon
def send(self):
print(f"{self.icon} Push → {self.recipient} | {self.title}: {self.message}")
self._mark_sent()
class SlackNotification(Notification):
def __init__(self, channel, message, mention=None):
super().__init__(channel, message)
self.mention = mention
def send(self):
mention_str = f"@{self.mention} " if self.mention else ""
print(f"💬 Slack → #{self.recipient} | {mention_str}{self.message}")
self._mark_sent()# Kullanım
notifications = [
EmailNotification("ali@mail.com", "Hoş geldiniz!", "Kayıt"),
SMSNotification("+905551234567", "Doğrulama kodunuz: 4523"),
PushNotification("user_42", "Siparişiniz kargoya verildi", "Kargo"),
SlackNotification("genel", "Deploy tamamlandı ✅", "devops"),
]
for notif in notifications:
notif.send()
print(f" {notif}")
print()
print(f"Toplam gönderilen: {Notification.total_sent()}")Bu örnekte:
Notificationtemel sınıf olarak ortak davranışı tanımlıyor.Her alt sınıf
send()metodunu override ediyor._mark_sent()protected metod olarak alt sınıflardan çağrılabiliyor._total_sentclass attribute ile toplam sayı takip ediliyor.
Yaygın Hatalar
1. super().__init__() Unutmak
class Base:
def __init__(self):
self.base_attr = "important!"
class Child(Base):
def __init__(self):
# super().__init__() YOK!
self.child_attr = "also important"
c = Child()
# print(c.base_attr) # AttributeError!2. Gereksiz Kalıtım (God Class)
# ❌ Her şeyi tek parent'a yığma
class Everything:
def save_to_db(self): ...
def send_email(self): ...
def generate_pdf(self): ...
def parse_json(self): ...
class User(Everything): # "User HER ŞEYDİR" — hayır, değil!
pass3. Çok Derin Kalıtım Zinciri
# ❌ 5+ seviye = bakımı zor, anlaşılması imkansız
class A: pass
class B(A): pass
class C(B): pass
class D(C): pass
class E(D): pass # Burada bir metod nereden geliyor? Bulmaya çalış!Özet
Kalıtım, bir sınıfın başka bir sınıftan özellik ve davranışları devralmasıdır — aile ağacındaki miras gibi.
class Child(Parent):söz dizimi ile kalıtım tanımlanır. Child, Parent'ın tüm attribute ve metodlarına erişir.`super()` ile parent sınıfın metodlarına erişiriz.
__init__'tesuper().__init__()çağırmayı unutma!Method overriding ile child sınıf parent'ın davranışını tamamen değiştirebilir veya
super()ile genişletebilir.`isinstance()` kalıtım zincirini kontrol eder,
type()sadece tam eşleşme arar. Genel olarakisinstance()tercih et.Kalıtım vs Composition: "is-a" ilişkisi varsa kalıtım, "has-a" varsa composition kullan. Şüphede composition tercih et.
_protectedattribute'lar alt sınıftan erişilebilir;__privateattribute'lar name mangling ile karışır.
AI Asistan
Sorularını yanıtlamaya hazır