Menu

Python Kalıtım: Alt Sınıflar, super(), Override ve MRO

Python'da sınıf kalıtımı nasıl çalışır: alt sınıf oluşturmak, super().__init__ çağırmak, metotları override edip genişletmek, isinstance ve issubclass, çoklu kalıtım ve MRO, ve kompozisyonun ne zaman daha iyi bir seçim olduğu.

Bu sayfada çalıştırılabilir editörler var - düzenle, çalıştır ve sonucu anında gör.

Başka Bir Sınıfın Üzerine Kurulan Sınıf

Kalıtım, yeni bir sınıfı var olan bir sınıfın uzantısı olarak tanımlamanı sağlar. Var olan sınıf üst sınıftır (temel sınıf ya da superclass da denir), yeni sınıf ise alt sınıftır (subclass). Alt sınıf, üst sınıfın her metodunu ve sınıf özniteliğini tekrar yazmadan alır; kendi metotlarını ekleyebilir ya da farklı davranması gerekenleri değiştirebilir.

Üst sınıfı, sınıf adından sonra parantez içinde belirtirsin:

Çıktı:

Rex is an animal
Woof

Dog'un kendine ait bir __init__ ya da describe metodu yok. Dog("Rex") çağırdığında Python __init__'i Dog üzerinde arar, bulamaz ve Animal üzerindekini kullanır. Aynı arama rex.describe() için de yapılır. rex.speak() için ise speak'i önce Dog üzerinde bulur ve orada durur.

Mekanizmanın tamamı bu arama sırasıdır: Python önce nesnenin kendi sınıfına, sonra üst sınıfa, sonra onun üst sınıfına bakar ve ilk eşleşmeyi kullanır. Sınıflarda yeniysen önce __init__, self ve metotları anlatan sınıflar sayfasına bak.

Bir Metodu Override Etmek

Alt sınıfta, üst sınıftaki bir metotla aynı adı taşıyan bir metot, alt sınıfın örnekleri için onun yerini alır. Buna override etme (geçersiz kılma) denir. Her alt sınıf aynı çağrıya kendi yöntemiyle cevap verebilir:

Çıktı:

Rectangle with area 12
Square with area 25
Shape with area 0

summary yalnızca bir kez, Shape üzerinde tanımlanmıştır, ama self.area()'yı çağırır. self gerçek nesne olduğu için bir Rectangle Rectangle.area'yı çalıştırır, bir Square ise area'yı üst sınıfı Rectangle'da bulur. Üst sınıfın kodu, alt sınıfın varlığından habersiz olarak alt sınıfın metodunu çağırır. Kalıtımı ortak bir iş akışını paylaşmak için kullanışlı kılan şey budur: ayrıntıları her alt sınıf kendisi doldurur.

super() ile Üst Sınıfı Çağırmak

Bir alt sınıf kendi __init__ metodunu tanımladığında, üst sınıfın __init__'i artık kendiliğinden çalışmaz. Alt sınıfın yine de üst sınıfın kurulumuna ihtiyacı varsa onu super().__init__() ile çağır:

Çıktı:

Ada 1050.0

super(), metotları geçerli sınıfın üst sınıfından başlayarak arayan bir nesne döndürür. Bu yüzden super().__init__(owner, balance) aynı nesne üzerinde Account.__init__'i çalıştırır ve ardından self.owner ile self.balance var olur.

O satırı atlarsan nesne yarım kalır:

Çıktı:

0.05
AttributeError: 'SavingsAccount' object has no attribute 'owner'

Bunun yerine Account.__init__(self, owner) da yazabilirsin; eski kodlarda bu sık görülür. super() tercih edilir, çünkü üst sınıfın adını tekrarlamaz ve aşağıda anlatılan çoklu kalıtımla doğru şekilde birlikte çalışır.

Bir Metodu Değiştirmek Yerine Genişletmek

super() yalnızca __init__'te değil, her metotta çalışır. Override ettiğin bir metodun içinde üst sınıfın sürümünü çağırmak, onu yeniden yazmak yerine etrafına davranış eklemeni sağlar:

Çıktı:

started
[12:00] started
INFO [12:00] started

Her seviye bir parça ekler ve gerisini yukarıya devreder. Üst sınıfın format metodu ileride değişirse iki alt sınıf da bu değişikliği alır.

isinstance ve issubclass

Bir alt sınıf örneği, üzerindeki her sınıfın da örneğidir. isinstance(obj, cls) bu ilişkiyi kontrol eder, issubclass(child, parent) ise aynı ilişkiyi iki sınıf arasında kontrol eder:

type(obj)'yi doğrudan karşılaştırmak yerine isinstance'ı tercih et. type(obj) is Animal gibi bir kontrol her alt sınıfı reddeder ve biri Dog'u eklediği anda bozulur.

Yazdığın her sınıf, sen belirtmesen bile object'ten kalıtım alır. class Animal:, class Animal(object): ile aynıdır. __str__, __repr__ ve __eq__ gibi varsayılan metotlar buradan gelir; __str__'i override etmenin print()'in gösterdiğini değiştirmesinin nedeni de budur:

Çıktı:

(2, 3)
True

Alt Sınıfları Bir Metodu Uygulamaya Zorlamak

Bazen üst sınıf hiçbir zaman doğrudan kullanılmamalı, yalnızca bir şablon olarak kullanılmalıdır. abc modülü böyle bir sınıfı soyut olarak işaretler. @abstractmethod ile işaretlenmiş her metot, sınıftan örnek oluşturulmadan önce override edilmelidir:

Çıktının ilk üç satırı dışa aktarımın kendisidir:

exporting 2 rows
1,2
3,4

Son satır, Exporter'ı ve eksik export metodunu adıyla anan bir TypeError'dır (tam ifade Python sürümleri arasında farklılık gösterir). Hata, export sonunda çağrıldığında değil, nesne oluşturulurken ortaya çıkar.

Çoklu Kalıtım ve MRO

Bir sınıf birden fazla üst sınıf listeleyebilir. Bu durumda hepsinden kalıtım alır:

Çıktı:

paddling flapping
swims
['Duck', 'Swimmer', 'Flyer', 'object']

İki üst sınıf da move tanımlıyor ve Duck, Swimmer'dakini alıyor. Python buna metot çözümleme sırasıyla (MRO, method resolution order) karar verir: Duck.__mro__ içinde saklanan ve her öznitelik aramasının baştan sona gezdiği tek bir sınıf listesi. Liste sınıfın kendisiyle başlar, üst sınıfları senin yazdığın sırada tutar, her sınıfı kendi üst sınıflarından önce koyar ve object ile biter. Sırayı class Duck(Flyer, Swimmer) olarak değiştirirsen move "flies" döndürür.

super() da aynı listeyi izler. Swimmer'ın bir metodunun içinde super(), "nesnenin sınıfının MRO'sunda Swimmer'dan sonraki sınıf" anlamına gelir; bir Duck için bu object değil, Flyer'dır. İş birliği yapan sınıfların her biri super() çağırıp zincirdeki her sınıfın tam olarak bir kez çalışmasını bu sayede sağlar; iki üst sınıfın ortak bir üst sınıfı paylaştığı elmas yapısında bile.

Pratikte çoklu kalıtım çoğunlukla mixin'ler için kullanılır: tek bir yetenek ekleyen küçük sınıflar (örneğin bir to_json metodu ekleyen JsonMixin), tek bir ana üst sınıfla birleştirilir. Birkaç tam üst sınıfa sahip derin hiyerarşileri takip etmek zordur ve nadiren gerekir.

Kalıtım mı Kompozisyon mu?

Kalıtım bir "bir türüdür" (is a) ilişkisini modeller. Bir SavingsAccount bir Account'tur: bir Account ile çalışan her kod, onunla da değişiklik yapmadan çalışmalıdır. Bu cümle kulağa yanlış geliyorsa muhtemelen kompozisyon istiyorsundur. Kompozisyon "sahiptir" (has a) ilişkisini modeller: nesne başka bir nesneyi öznitelik olarak tutar ve onu çağırır.

Çıktı:

Roadster: 300 hp engine running

class Car(Engine) de çalışırdı, ama bir arabanın bir tür motor olduğunu iddia eder, Car'a her motor metodunu verir ve farklı bir sınıf yazmadan başka bir motor takmayı imkânsız hale getirirdi. Kompozisyonla parçalar ayrı kalır ve motoru değiştirmek başka bir nesne vermekten ibarettir.

Kalıtımın yanlış araç olduğunu gösteren birkaç işaret:

  • Alt sınıf, üst sınıfın metotlarının çoğunu override ediyor ya da bazılarını devre dışı bırakıyor.
  • Yalnızca bir iki yardımcı metodu yeniden kullanmak için kalıtım alıyorsun.
  • Hiyerarşi iki üç seviyeyi aşıyor ve bir metodun ne yaptığını görmek için birkaç dosya okuman gerekiyor.

Sık Yapılan Hatalar

  • super().__init__()'i unutmak. Alt sınıfın __init__'i üst sınıfınkinin yerini alır, bu yüzden üst sınıfın öznitelikleri hiç ayarlanmaz ve sonradan bir AttributeError alırsın.
  • super() çağrılarına self vermek. super().__init__(self, name) değil, super().__init__(name) yaz. super() metodu zaten geçerli nesneye bağlar.
  • Override ederken uyumsuz imzalar. Üst sınıfın speak(self) metodu ortak kod tarafından çağrılıyorsa, alt sınıfta zorunlu bir ek argüman alan speak(self, volume) o kodu bozar. Override'ları uyumlu tut ya da yeni parametrelere varsayılan değer ver.
  • type(obj) == SomeClass ile kontrol etmek. Alt sınıfları yok sayar. isinstance kullan.
  • İlgisiz sınıflar arasında kod paylaşmak için kalıtım kullanmak. Bunun yerine ortak kodu bir fonksiyona ya da yardımcı bir nesneye taşı.

Sıkça Sorulan Sorular

Python'da bir sınıftan nasıl kalıtım alınır?

Üst sınıfı, yeni sınıfın adından sonra parantez içinde yaz: class Dog(Animal):. Dog, Animal'ın her metodunu ve sınıf özniteliğini alır; yenilerini ekleyebilir ya da var olanları override edebilir.

super().__init__() ne işe yarar?

Üst sınıfın __init__ metodunu çağırır, böylece üst sınıf kendi özniteliklerini kurabilir. Bir alt sınıf kendi __init__ metodunu tanımladığında Python üst sınıfınkini otomatik olarak çağırmaz; bu yüzden üst sınıfın ihtiyaç duyduğu argümanlarla super().__init__(...) çağırır, ardından alt sınıfın ek özniteliklerini ayarlarsın.

Python'da isinstance ile type arasındaki fark nedir?

isinstance(obj, Animal) bir Animal için de, Animal'ın herhangi bir alt sınıfının örnekleri için de True döner. type(obj) is Animal ise yalnızca nesne doğrudan Animal'dan oluşturulduysa True döner. isinstance'ı tercih et, çünkü biri bir alt sınıf eklediğinde çalışmaya devam eder.

Python çoklu kalıtımı destekler mi?

Evet: class C(A, B): ikisinden de kalıtım alır. İki üst sınıf da aynı metodu tanımladığında Python, metot çözümleme sırasını (MRO) izleyerek birini seçer; bunu C.__mro__ ile inceleyebilirsin. Liste önce C'yi, sonra üst sınıfları soldan sağa, en sonda da object'i içerir.

Kalıtım yerine ne zaman kompozisyon kullanmalıyım?

Alt sınıfın, üst sınıfın beklendiği her yerde kullanılabildiği bir "bir türüdür" (is a) ilişkisi için kalıtım kullan (bir SavingsAccount bir Account'tur). Bir "sahiptir" (has a) ilişkisi için kompozisyon kullan, yani nesneyi bir öznitelik olarak sakla (bir Car'ın bir Engine'i vardır). Emin değilsen kompozisyonu sonradan değiştirmek daha kolaydır.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA