- Sihirli metot nedir, ne zaman yazılır
- __init__ ve __new__: nesnenin doğuşu
- __repr__ ve __str__: nesne nasıl yazdırılır
- __eq__, __hash__ ve sıralama
- __len__, __getitem__, __iter__ ve __contains__: kapsayıcı davranışı
- __add__, __call__, __enter__ ve __exit__
- Sık yapılan hatalar
- Bu beceriyi Mindset programlarında nerede çalışırsınız
- Sık sorulan sorular
Python Dunder Metotları: Sınıfınızı Python'un Diline Uydurmak
Çift alt çizgili metotlar, sınıfınızın print, len, == ve in gibi Python ifadeleriyle nasıl davranacağını belirler; en sık kullanılanları örneklerle yazarsınız.
Bir bakışta
- Sihirli metotları siz değil, Python çağırır. len(nesne) yazdığınızda Python arka planda nesne.__len__() çağırır.
- __repr__ geliştiriciye, __str__ kullanıcıya yöneliktir. __str__ yoksa Python __repr__ çıktısını kullanır.
- __eq__ yazınca __hash__ kaybolur. Hash tanımlamazsanız nesneyi küme ya da sözlük anahtarı yapamazsınız.
- Desteklenmeyen işlemde NotImplemented döndürün. Hata fırlatmak yerine Python'un diğer tarafı denemesine izin verin.
- Kapsayıcı davranışı birkaç metotla kurulur. __len__, __getitem__, __iter__ ve __contains__ sınıfınızı liste gibi gezilebilir yapar.
Sihirli metot nedir, ne zaman yazılır
Sihirli metotlar, adı iki alt çizgiyle başlayıp iki alt çizgiyle biten metotlardır: __init__, __repr__, __len__ gibi. Adlarına rağmen büyü yoktur; Python'un veri modelinde belirli bir ifadeye karşılık gelen sabit adlardır. len(nesne) yazdığınızda Python nesnenin __len__ metodunu, nesne == baska yazdığınızda __eq__ metodunu, for ile gezdiğinizde __iter__ metodunu çağırır. Bu metotları normalde doğrudan çağırmazsınız; yazarsınız ve Python çağırır.
Ne zaman yazılır? Kendi sınıfınızın Python'un yerleşik davranışlarına uymasını istediğinizde. Bir Kitap nesnesini yazdırınca anlamlı bir metin görmek, iki kitabı == ile karşılaştırmak, bir Sepet nesnesine len uygulamak bunun örnekleridir. Bunları yazmazsanız Python varsayılan davranışı uygular: nesnenin bellekteki adresini gösterir, eşitliği kimlik karşılaştırmasına indirger.
Aşağıdaki bölümler, en sık kullanılan metotları tek bir örnek sınıf üzerinden sırayla ele alır. Genel sınıf, kalıtım ve kapsülleme konuları ayrı bir konudur; burada yalnız bu özel metotlar anlatılır.
__init__ ve __new__: nesnenin doğuşu
En tanıdık sihirli metot __init__'tir. Nesne oluşturulunca Python onu çağırır ve ilk değerleri atamanızı sağlar:
class Kitap: def __init__(self, baslik, yazar): self.baslik = baslik self.yazar = yazar
Belgeler iki ayrıntıyı vurgular. Birincisi, __init__ değer döndürmemelidir; None dışında bir değer döndürürseniz çalışma anında TypeError alırsınız. İkincisi, miras aldığınız sınıfın __init__ metodu varsa, kendi metodunuzdan onu açıkça çağırmanız gerekir.
__new__ ise nesneyi yaratan metottur ve __init__'ten önce çalışır. Belgelere göre __new__ sınıfın bir örneğini döndürürse Python ardından __init__'i çağırır; döndürmezse çağırmaz. Günlük işlerde __new__ yazmanız gerekmez. Değiştirilemez türleri alt sınıflarken ya da tek örnekli (singleton) sınıf kurarken gündeme gelir; öğrenme sırasında __init__ yeterlidir.
__repr__ ve __str__: nesne nasıl yazdırılır
Bu ikisi en çok karıştırılan çifttir. Python belgelerine göre __repr__, mümkünse nesneyi yeniden yaratacak geçerli bir Python ifadesi gibi görünmelidir; hata ayıklama, günlük kayıtları ve etkileşimli kabuk bu çıktıyı kullanır. __str__ için böyle bir beklenti yoktur; kullanıcıya gösterilecek okunaklı bir metin üretir ve print ile str() onu kullanır.
İki kural işinizi kolaylaştırır. Birincisi, __str__ yazılmamışsa Python varsayılan olarak __repr__ çıktısını kullanır. Yani yalnız birini yazacaksanız __repr__'i yazın. İkincisi, __repr__ yazmazsanız nesne <Kitap object at ...> gibi işe yaramaz bir çıktı verir.
def __repr__(self): return f'Kitap({self.baslik!r}, {self.yazar!r})'
def __str__(self): return f'{self.yazar}: {self.baslik}'
f-metindeki !r dönüştürmesi, değeri tırnaklı biçimiyle yazar; bu sayede çıktı, nesneyi yeniden kurabileceğiniz bir ifadeye benzer. Birim testi bir nesneyi karşılaştırıp başarısız olduğunda ekrana düşen metin de __repr__ olduğu için, yazması hata ayıklama süresini doğrudan kısaltır.
__repr__ geliştirici, __str__ kullanıcı içindir
| __repr__ | __str__ | |
|---|---|---|
| Hedef okur | Geliştirici, hata ayıklama | Son kullanıcı, çıktı |
| İdeal içerik | Nesneyi yeniden kuran ifade | Okunaklı kısa metin |
| Yazılmazsa | Bellek adresli işe yaramaz çıktı | Python __repr__ çıktısını kullanır |
| Önce hangisi | Yalnız birini yazacaksanız bunu | Gerekirse sonradan ekleyin |
__eq__, __hash__ ve sıralama
Varsayılan olarak iki nesne yalnız aynı nesneyse eşittir; belgelere göre varsayılan __eq__ kimlik (is) karşılaştırması yapar. İki farklı Kitap nesnesinin aynı başlık ve yazara sahip olması halinde eşit sayılmasını istiyorsanız __eq__ yazarsınız:
def __eq__(self, diger): if not isinstance(diger, Kitap): return NotImplemented return (self.baslik, self.yazar) == (diger.baslik, diger.yazar)
Burada NotImplemented döndürmek önemlidir. Belgelere göre, bir karşılaştırma metodu işlemi o tür için uygulamıyorsa NotImplemented döndürebilir ve Python karşı tarafın metodunu dener. Hata fırlatmak ya da False döndürmek bu esnekliği öldürür.
__eq__ ile gelen tuzak hash'tir. Belgelere göre __eq__ tanımlayıp __hash__ tanımlamayan sınıfın __hash__ değeri örtük olarak None olur; bu nesneler küme elemanı ya da sözlük anahtarı olamaz ve hash istenince TypeError alınır. Nesneniz değişmezse ikisini birlikte yazın:
def __hash__(self): return hash((self.baslik, self.yazar))
Belgeler, değiştirilebilir nesneler için __eq__ yazıp __hash__ yazmamayı önerir; çünkü hash değeri nesnenin ömrü boyunca sabit kalmalıdır. Örnekteki hash yalnız baslik ve yazar hiç değişmeyecekse güvenlidir.
Sıralama için __lt__ (küçüktür) yazmak çoğu zaman yeter; sorted ve min gibi işlevler nesneleri karşılaştırabilir. Diğer karşılaştırma işleçleri de ayrı metotlarla (__le__, __gt__, __ge__) tanımlanır ve __lt__ ile __gt__ birbirinin yansımasıdır.
__len__, __getitem__, __iter__ ve __contains__: kapsayıcı davranışı
Birkaç metotla sınıfınızı liste gibi kullanılabilir yapabilirsiniz:
class Sepet: def __init__(self): self.kitaplar = []
def __len__(self): return len(self.kitaplar)
def __getitem__(self, sira): return self.kitaplar[sira]
def __iter__(self): return iter(self.kitaplar)
def __contains__(self, kitap): return kitap in self.kitaplar
__len__, len() işlevinin sonucunu verir ve sıfırdan küçük olamaz. Önemli bir yan etki: __bool__ yazmadıysanız ve __len__ sıfır döndürüyorsa nesne mantıksal bağlamda yanlış (False) sayılır; boş Sepet if sepet: sınavında yanlış çıkar. __bool__ tanımlarsanız o öncelik kazanır.
__getitem__ nesne[anahtar] ifadesini mümkün kılar. Belgelere göre uygunsuz değerde LookupError, dizilerde IndexError, eşlemelerde KeyError fırlatmalıdır. __iter__ for döngüsü için yeni bir yineleyici döndürür. __contains__ yazmasanız bile in işleci önce __iter__'i, o da yoksa __getitem__'i dener; ancak kendi metodunuz daha verimli bir arama yapmanızı sağlar.
__add__, __call__, __enter__ ve __exit__
Aritmetik işleçler de sihirli metotlarla tanımlanır. a + b için Python a.__add__(b) çağırır. Burada da kural aynıdır: işlem o tür için anlamsızsa NotImplemented döndürün. Örneğin iki Sepet'i birleştirmek için __add__ yazabilirsiniz.
__call__, nesneyi fonksiyon gibi çağrılabilir yapar: nesne(argüman) ifadesi nesne.__call__(argüman) olarak yorumlanır. Ayarlı davranış taşıyan küçük işlevsel nesneler için uygundur.
__enter__ ve __exit__ ise with ifadesinin bağlam yöneticisi protokolüdür. with bloğuna girerken __enter__ çalışır ve dönüş değeri as ile bağlanır; çıkarken __exit__ çalışır. Belgeler, __exit__'in blok normal bittiğinde üç argümanını da None olarak aldığını, bir istisna varsa onu bastırmak için doğru değer döndürmesi gerektiğini ve verilen istisnayı yeniden fırlatmaması gerektiğini söyler. Dosya, bağlantı ya da kilit gibi kapatılması gereken kaynakları sarmak için en temiz yol budur.
Sık yapılan hatalar
Sihirli metotlarda hatalar sessiz olur; program çalışır ama beklenmedik davranır. En sık örnekler:
- __repr__ yazmamak ve hata ayıklarken bellek adresli çıktıyla uğraşmak
- __eq__ yazıp __hash__ yazmayı unutmak; sonra nesneyi kümeye koyunca TypeError almak
- Değiştirilebilir nesne için __hash__ yazmak ve nesne kümedeyken özniteliğini değiştirmek
- __eq__ içinde başka türle karşılaştırmayı hatalı ele almak; NotImplemented döndürmek yerine hata çıkarmak
- __init__ içinde değer döndürmek
- __len__ yazıp boş nesnenin mantıksal olarak yanlış sayıldığını unutmak
Bir kural işe yarar: yalnız ihtiyacınız olan metodu yazın. Her sınıfa tüm sihirli metotları eklemek kodu şişirir. Önce __repr__, ardından sınıfınızın doğal olarak sahip olduğu davranışa uyan metotlar.
Bu beceriyi Mindset programlarında nerede çalışırsınız
Sihirli metotlar sınıf yazmanın doğal devamıdır. Python ile Programlama Eğitimi'nin Sınıflar ve Objeler konusu bu zemini hazırlar. Python ile Nesne Yönelimli Programlama programında Veri Modeli ve Özel Metotlar ayrı bir modül olarak yer alır; konuyu sıfırdan bir tasarım bakışıyla çalışmak isteyenler için doğrudan karşılığıdır. Data Science Bootcamp'ın Sınıflar başlığı ise veri bilimi kodunda karşılaştığınız sınıfları okuyabilmek için gereken temeli verir.
İlgili Mindset programı
Python ile Programlama Eğitimi
Sık sorulan sorular
__str__ ile __repr__ farkı nedir, yalnız birini yazsam olur mu?
__repr__ geliştiriciye yöneliktir ve mümkünse nesneyi yeniden kuracak bir ifade gibi görünür; __str__ kullanıcıya gösterilecek okunaklı metindir. Yalnız birini yazacaksanız __repr__'i yazın, çünkü Python __str__ tanımlı değilse __repr__ çıktısını kullanır. Tersi geçerli değildir.
__eq__ yazdım, neden nesnem artık sözlük anahtarı olamıyor?
__eq__ tanımlayıp __hash__ tanımlamayan sınıfta Python, __hash__ değerini örtük olarak None yapar. Bu yüzden nesne unhashable olur ve anahtar olarak kullanılınca TypeError verir. Nesneniz değişmezse __hash__ yazın; değişebiliyorsa sözlük anahtarı olarak kullanmamak daha güvenlidir.
NotImplemented ile NotImplementedError aynı şey mi?
Hayır. NotImplemented, karşılaştırma ve aritmetik metotlarının döndürdüğü özel bir değerdir ve Python'a karşı tarafın metodunu denemesini söyler. NotImplementedError ise soyut metotlarda fırlatılan bir istisnadır. Karşılaştırma metodunda NotImplementedError fırlatmak, Python'un geri dönüş mekanizmasını devre dışı bırakır.
Sihirli metotları doğrudan çağırabilir miyim?
Çağırabilirsiniz ama gerekmez ve önerilmez. nesne.__len__() yerine len(nesne) yazın. Yerleşik işlevler ek denetimler yapar ve kodunuzu okuyanlar için beklenen biçimdir. Doğrudan çağrı, yalnız üst sınıfın metoduna başvurduğunuz super().__init__ gibi durumlarda anlamlıdır.
Hangi sihirli metotları öğrenmeye önce başlamalıyım?
Önce __init__ ve __repr__, ardından __str__ ve __eq__. Bunlarla çoğu sınıf yazılır. Koleksiyon benzeri sınıflara geçince __len__, __getitem__ ve __iter__'e, dosya ya da bağlantı gibi kaynak yöneten sınıflarda ise __enter__ ve __exit__'e yönelin. Her sınıfa hepsini eklemeyin.