Ders 01 / 11
Sınıf ve Örnek
Sınıf gövdesinde yazılan öznitelik üç örnekte 1 nesne olarak paylaşılır, örnek gövdesinde yazılan 3 ayrı nesne üretir; `self` bir anahtar sözcük değil, erişim anında kurulan bağlı yöntemin taşıdığı örnektir.
İçindekiler
Veri Yapıları ve Fonksiyonel Araçlar kursu kabın bedelini saydı ve kapanışta bir sınır çizdi: kabı seçmekle kabı yazmak ayrı şeylerdir. Buraya kadar ölçülen bütün protokoller dilin verdiği nesnelerde işledi. Liste bir uzunluğa sahipti, demet bir kapta anahtar olabiliyordu, sözlük karşılaştırılabiliyordu — hiçbiri bizim yazdığımız bir sözleşme değildi, hepsi seçilmiş bir kabın hazır gelen özellikleriydi.
Bu kurs sınırın öbür tarafındadır: protokole katılan tarafı biz yazarız. Ve bir şeyi
yazmak, onun çağrıyı gerçekten yanıtladığı anlamına gelmez. Kursun tek sorusu budur:
çağrıyı kim yanıtladı? İlk ders sorunun en küçük hâlini kurar. Bir sınıf tanımlandığında
iki ayrı ad alanı ortaya çıkar — sınıfın kendisi ve ondan var edilen her örnek. Bir öznitelik
okunduğunda yanıt bu ikisinden hangisinden gelir, ve self denen ad nereden doğar?
İki Ayrı Ad Alanı
Bir class deyimi çalıştığında yorumlayıcı gövdeyi bir kez koşturur ve gövdede oluşan
adları sınıf nesnesinin sözlüğüne yazar. Sınıf, çalışma zamanında var olan bir nesnedir;
yöntemler de o sözlüğün içindeki sıradan işlevlerdir.
Her örnek ise kendi sözlüğüyle gelir. __init__ içinde self.ad = ... yazıldığında ad
sınıfa değil, o örneğin sözlüğüne yazılır. Bir öznitelik okunduğunda arama önce örneğin
sözlüğüne bakar, orada yoksa sınıfın sözlüğüne, sonra sınıfın atalarına. Yanıtı ilk bulan
verir. Ortak kurgunun saglayan işlevi bu aramanın sınıf tarafını yazıya döker: bir adı
çözümleme sırasında ilk hangi sınıfın taşıdığını söyler.
__init__’in adı da bu düzenden çıkar. Örnek çağrı yapıldığında önce boş bir nesne var
edilir, sonra __init__ o nesne üzerinde koşar; yani __init__ bir nesne üretmez, var
edilmiş bir nesnenin sözlüğünü doldurur. Bir şey döndürmemesinin nedeni budur — döndürecek bir
nesnesi yoktur, kendisine verilmiş bir nesnesi vardır.
Ölçümün varsayımları:
- SN1 — Ölçülen sınıf ortak kurgunun
Kayitsınıfıdır; gövdesine bir sınıf özniteliği (tur) eklenmiş,kaynakvebicimleyetenekleri değiştirilmemiştir. - SN2 — Üç örnek aynı sınıftan ve tek bir kavrama ile var edilir; aralarındaki tek fark
__init__’e verilen addır. - SN3 — “Ayrı nesne” sütunu ilk iki örneğin gördüğü değeri
isile karşılaştırır; kimlik sayısı basılmaz. - SN4 — “Örnek sözlüğünde” sütunu adın kaç örneğin kendi sözlüğünde bulunduğunu sayar; sınıftan gelen adlar bu sayıya girmez.
"""Oznitelik nerede duruyor: sinif govdesi ile ornek govdesi.""" class Kayit: """Ortak kurgunun temel sinifi; burada iki yetenegi kullanilir.""" tur = "kayit" # sinif govdesinde yazili def __init__(self, ad): self.ad = ad # ornek govdesinde yazili def kaynak(self): return "Kayit" def bicimle(self): return f"<{self.kaynak()}>" def saglayan(sinif, ad): """Bir ozniteligi hangi sinif sagliyor: cozumleme sirasinda ilk bulan.""" for s in sinif.__mro__: if ad in s.__dict__: return s.__name__ return None ucu = [Kayit(f"k{i}") for i in range(3)] print(f"{'öznitelik':<10s} {'yanıtlayan':>11s} {'ayrı nesne':>11s} " f"{'örnek sözlüğünde':>17s}") for ad in ("tur", "ad"): ayri = getattr(ucu[0], ad) is not getattr(ucu[1], ad) yerel = sum(1 for u in ucu if ad in u.__dict__) print(f"{ad:<10s} {saglayan(Kayit, ad) or 'örnek':>11s} {str(ayri):>11s} " f"{yerel:>17d}") a, b = Kayit("a"), Kayit("b") print(f"\nbaşta a.tur={a.tur!r} b.tur={b.tur!r} Kayit.tur={Kayit.tur!r} " f"a'nın sözlüğünde tur -> {'tur' in a.__dict__}") a.tur = "özel" print(f"a.tur atandı a.tur={a.tur!r} b.tur={b.tur!r} Kayit.tur={Kayit.tur!r} " f"a'nın sözlüğünde tur -> {'tur' in a.__dict__}") del a.tur print(f"ad silindi a.tur={a.tur!r} b.tur={b.tur!r} Kayit.tur={Kayit.tur!r} " f"a'nın sözlüğünde tur -> {'tur' in a.__dict__}")
öznitelik yanıtlayan ayrı nesne örnek sözlüğünde tur Kayit False 0 ad örnek True 3 başta a.tur='kayit' b.tur='kayit' Kayit.tur='kayit' a'nın sözlüğünde tur -> False a.tur atandı a.tur='özel' b.tur='kayit' Kayit.tur='kayit' a'nın sözlüğünde tur -> True ad silindi a.tur='kayit' b.tur='kayit' Kayit.tur='kayit' a'nın sözlüğünde tur -> False
İlk tablo iki adın iki ayrı yerde durduğunu gösteriyor. tur okunduğunda yanıtı veren
Kayit’tir; üç örneğin üçü de aynı nesneyi görür ve hiçbirinin kendi sözlüğünde bu ad
yoktur — sayı 0. ad okunduğunda yanıtı veren sınıf yoktur; üç örneğin üçü de kendi
sözlüğünde taşır, sayı 3, ve gördükleri nesneler ayrıdır.
Alt bölüm arama sırasının doğrudan sonucudur. a.tur = "özel" satırı sınıftaki adı
değiştirmez; a’nın sözlüğüne yeni bir giriş açar ve o giriş aramada önce bulunduğu için
sınıftakini gölgeler. b ile sınıfın kendisi olduğu gibi kalır. Örnekteki ad silindiğinde
gölge kalkar ve a.tur yine sınıfın verdiği değeri okumaya döner. Bir örneğe atama yapmak
sınıfa hiçbir zaman dokunmaz — sınıfı değiştirmenin tek yolu sınıfa yazmaktır.
Sınıf Gövdesi Bir Kez Koşar
Yukarıdaki ayrımın maliyet tarafı vardır ve dikkatsiz yazımda gözden kaçar: sınıf gövdesi
tanım anında bir kez koşar, örnek gövdesi (__init__) her örnekte koşar. Sınıf
gövdesinde = [] yazmak tek bir liste var eder ve o listeyi bütün örnekler paylaşır.
- SN5 — Ölçülen sınıf aynı türden iki adı bilerek iki ayrı gövdede tanımlar; adların dışında hiçbir farkları yoktur.
- SN6 — Üç örnek var edilir ve her biri kendi sırasını iki kaba da ekler; ölçülen şey her örneğin sonunda kaç öğe gördüğüdür.
- SN7 — Paylaşım
isile gösterilir.
class Ortak: """Ayni turden iki adi ayri govdelerde tanimlar: biri sinifta, oteki ornekte.""" ortak_kutu = [] # sinif govdesi: bir kez calisir def __init__(self): self.kendi_kutusu = [] # ornek govdesi: her ornekte calisir ucu = [Ortak() for _ in range(3)] for i, n in enumerate(ucu): n.ortak_kutu.append(i) n.kendi_kutusu.append(i) print(f"{'öznitelik':<14s} {'var edilen kap':>15s} {'her örneğin gördüğü':>20s} " f"{'paylaşılıyor':>13s}") print(f"{'ortak_kutu':<14s} {1:15d} {str([len(n.ortak_kutu) for n in ucu]):>20s} " f"{str(ucu[0].ortak_kutu is ucu[2].ortak_kutu):>13s}") print(f"{'kendi_kutusu':<14s} {3:15d} {str([len(n.kendi_kutusu) for n in ucu]):>20s} " f"{str(ucu[0].kendi_kutusu is ucu[2].kendi_kutusu):>13s}")
öznitelik var edilen kap her örneğin gördüğü paylaşılıyor ortak_kutu 1 [3, 3, 3] True kendi_kutusu 3 [1, 1, 1] False
Üç örnek, kap başına birer ekleme yaptı. Sınıf gövdesindeki kap 1 kez var edildiği için üç eklemenin üçü de aynı listeye gitti ve her örnek 3 öğe görüyor. Örnek gövdesindeki kap 3 kez var edildi ve her örnek yalnız kendi eklediğini, 1 öğe görüyor. Sayılar aynı satırlardan çıkmıştır; ayrım tek başına gövdenin yerindedir.
Buradan bir yazım kuralı çıkar: değiştirilebilir bir değer sınıf gövdesine yazılmaz. Sınıf
gövdesi sabitler, sayılar ve değiştirilemez değerler içindir — onlarda paylaşımın gözlenebilir
bir sonucu yoktur, çünkü kimse onları yerinde değiştiremez. Bir örneğe ait olması gereken her
durum __init__ içinde kurulur. Sınıf özniteliğinin doğru kullanımı da bu tabloda görünür:
ilk ölçümdeki tur bir varsayılandır ve örnek onu yalnız gerektiğinde gölgeler; üç örnek
için tek nesne yeter, çünkü kimse onu yerinde değiştirmez.
self Bir Anahtar Sözcük Değildir
Sınıf sözlüğünde duran yöntem sıradan bir işlevdir ve ilk parametresi başka bir addan farklı değildir. Örnek üzerinden erişildiğinde araya bir adım girer: erişim, işlevi ve örneği bir arada tutan bağlı bir yöntem nesnesi üretir. Çağrıda ilk argümanı yazmayışımızın nedeni budur — o argüman erişim anında zaten bağlanmıştır.
- SN8 — Ölçüm ilk bloğun
Kayitsınıfını sürdürür; yeni bir tanım yapılmaz. - SN9 — Aynı çağrı iki yazımla yapılır: örnek üzerinden ve sınıf üzerinden, örnek elle geçirilerek. Ölçülen şey iki yazımın aynı sonucu verip vermediğidir.
- SN10 — Nesnelerin tipi ad olarak basılır; kimlik sayısı ve bellek verisi basılmaz.
- SN11 — Son satırdaki çağrı bilerek örneksiz yapılır; istisnanın adı kaydedilir.
k = Kayit("k") print(f"k.bicimle() -> {k.bicimle()} | Kayit.bicimle(k) -> {Kayit.bicimle(k)} | " f"eşit -> {k.bicimle() == Kayit.bicimle(k)}") print(f"sınıf sözlüğündeki nesnenin tipi -> " f"{type(Kayit.__dict__['bicimle']).__name__}") print(f"örnek üzerinden erişilenin tipi -> {type(k.bicimle).__name__}") print(f"bağlı yöntemin tuttuğu nesne k mi -> {k.bicimle.__self__ is k}") print(f"k.bicimle is k.bicimle -> {k.bicimle is k.bicimle} | " f"Kayit.bicimle is Kayit.bicimle -> {Kayit.bicimle is Kayit.bicimle}") print(f"bağlı yöntemin sardığı işlev -> {k.bicimle.__func__ is Kayit.bicimle}") try: Kayit.bicimle() except TypeError as e: print(f"örneksiz çağrı -> {type(e).__name__}") print() print(f"{'yetenek':<10s} {'yanıtlayan sınıf':>17s}") for ad in ("kaynak", "bicimle", "__init__"): print(f"{ad:<10s} {saglayan(Kayit, ad):>17s}") print(f"{'ad':<10s} {saglayan(Kayit, 'ad') or '—':>17s}")
k.bicimle() -> <Kayit> | Kayit.bicimle(k) -> <Kayit> | eşit -> True sınıf sözlüğündeki nesnenin tipi -> function örnek üzerinden erişilenin tipi -> method bağlı yöntemin tuttuğu nesne k mi -> True k.bicimle is k.bicimle -> False | Kayit.bicimle is Kayit.bicimle -> True bağlı yöntemin sardığı işlev -> True örneksiz çağrı -> TypeError yetenek yanıtlayan sınıf kaynak Kayit bicimle Kayit __init__ Kayit ad —
İlk satır iki yazımın aynı işi yaptığını gösteriyor: k.bicimle() ile Kayit.bicimle(k)
aynı sonucu veriyor. İkincisi ilkinin açılmış hâlidir. Sonraki iki satır aradaki adımı
adlandırıyor — sınıfın sözlüğünde duran nesne bir işlev, örnek üzerinden erişilen nesne bir
yöntem. Dördüncü satır yöntemin ne taşıdığını söylüyor: erişimin yapıldığı örneğin
kendisini.
Beşinci satır bağlamanın erişim anında olduğunu kanıtlıyor. Aynı adı iki kez okumak iki
ayrı bağlı yöntem nesnesi üretiyor; is karşılaştırması False veriyor. Sınıf üzerinden
erişimde ise bağlama yoktur — her seferinde sözlükteki aynı işlev döner ve karşılaştırma True
çıkar. Altıncı satır ikisini birleştiriyor: bağlı yöntemin sardığı işlev, sınıftan okunan
işlevin ta kendisidir. Örneksiz çağrının TypeError vermesinin nedeni de buradadır — işlev
bir parametre bekliyordu ve onu bağlayacak bir örnek yoktu.
Bunun günlük yazımda iki sonucu vardır. Birincisi, bağlı yöntem çağrılmadan da bir değer
olarak taşınabilir: k.bicimle bir yere geçirildiğinde yanına k de gitmiş olur, çünkü nesne
örneği içinde taşır. Bir geri çağrı listesine yöntem koymak, o listeye örneği de koymaktır.
İkincisi, bir yöntemi sınıftan okuyup elle çağırmak her zaman mümkündür; örnek gizli bir
argüman değil, açıkça yazılabilen ilk argümandır.
Nesneye dayalı programlamanın kavramları Programlama Temelleri kursunda kurulmuştu; bunlar
tekrarlanmaz. Orada sınıf bir kalıp, örnek onun bir üretimiydi. Burada ölçülen şey kalıbın
kendisinin de bir nesne olduğu ve yanıtın hangi sözlükten geldiğidir. Son tablo bunu
kapatıyor: dört adın dördünü de Kayit yanıtlıyor, ad özniteliğini yanıtlayan bir sınıf
yok — o ad hiçbir sınıfta yazılı değil, her örnekte ayrı ayrı doğuyor.
Bağlamanın Üç Biçimi
Bağlama bir kural olduğuna göre değiştirilebilir. Aynı sınıf gövdesinde üç ayrı bağlama biçimi tanımlanabilir: ilk argümanı örneğe bağlayan yöntem, ilk argümanı sınıfa bağlayan yöntem, ve hiçbir şeye bağlanmayan yöntem. Üçü de aynı gövdede durur ve aynı biçimde çağrılır; ayrıldıkları tek yer, çağrı anında ilk argümanın ne olduğudur.
- SN12 — Üç yöntem tek bir sınıf gövdesinde tanımlanır ve üçü de aynı işi yapar: kendisine bağlanan şeyin adını döndürür. Bağlanan bir şey yoksa yerine bir tire döndürülür.
- SN13 — Ölçüm bir alt sınıf üzerinden yapılır; gövdeleri yazan sınıf ile çağrının yapıldığı sınıf bilerek ayrıştırılmıştır.
- SN14 — “Çağrı” sütunları yöntemi argümansız çağırır; karşılanamayan çağrı bir istisnayla düşer ve ünlem önekiyle istisnanın adı kaydedilir.
- SN15 — Erişim sütunları dönen nesnenin tipini ad olarak basar; kimlik sayısı basılmaz.
class Bicimci: """Ayni govdede uc baglama bicimi: ornege, sinifa ve hicbir seye baglanan.""" def ornek_yontemi(self): return type(self).__name__ @classmethod def sinif_yontemi(cls): return cls.__name__ @staticmethod def duragan_yontem(): return "—" class AltBicimci(Bicimci): pass def dene(cagri): try: return cagri() except TypeError as e: return f"!{type(e).__name__}" n = AltBicimci() print(f"{'yöntem':<15s} {'sınıftan erişim':>15s} {'örnekten erişim':>15s} " f"{'sınıftan çağrı':>15s} {'örnekten çağrı':>15s}") for ad in ("ornek_yontemi", "sinif_yontemi", "duragan_yontem"): print(f"{ad:<15s} {type(getattr(AltBicimci, ad)).__name__:>15s} " f"{type(getattr(n, ad)).__name__:>15s} " f"{dene(getattr(AltBicimci, ad)):>15s} " f"{dene(getattr(n, ad)):>15s}") print(f"\ngövdeyi yazan sınıf {saglayan(AltBicimci, 'sinif_yontemi')}, " f"cls'e bağlanan sınıf {AltBicimci.sinif_yontemi()}")
yöntem sınıftan erişim örnekten erişim sınıftan çağrı örnekten çağrı ornek_yontemi function method !TypeError AltBicimci sinif_yontemi method method AltBicimci AltBicimci duragan_yontem function function — — gövdeyi yazan sınıf Bicimci, cls'e bağlanan sınıf AltBicimci
Tablo bağlamanın nerede olduğunu satır satır gösteriyor. Örnek yöntemi yalnız örnekten
erişildiğinde bağlanır; sınıftan erişilen nesne çıplak bir işlevdir ve argümansız çağrı
TypeError ile düşer. Sınıf yöntemi her iki erişimde de bağlıdır — sınıftan okunduğunda
bile bir yöntem döner, çünkü bağlanacak şey zaten sınıfın kendisidir; argümansız çağrı iki
yoldan da çalışır. Durağan yöntem hiçbir erişimde bağlanmaz; iki yoldan da çıplak işlev döner
ve iki çağrı da bağlanan bir şey olmadığını bildirir.
Altı çağrının beşi karşılık buluyor, biri düşüyor. Düşen çağrı bir eksiklik değil, kuralın kendisidir: örnek yöntemi bir örnek olmadan çağrılamaz, çünkü ilk parametresini dolduracak bir şey yoktur.
Son satır kursun sorusunun ilk keskin hâlidir. sinif_yontemi gövdesi Bicimci’de
yazılıdır; ama çağrı AltBicimci üzerinden yapıldığı için cls’e bağlanan sınıf
AltBicimci’dir. Gövdeyi yazan sınıf ile çağrıyı yanıtlayan bağlam aynı şey değildir —
gövde bir yerde durur, bağlama çağrı anında olur. Sonraki dersin ölçtüğü ayrım bunun
büyütülmüş hâlidir.
Özet
- Sınıf ve örnek iki ayrı ad alanıdır; öznitelik araması önce örneğin sözlüğüne, sonra sınıfın sözlüğüne bakar ve yanıtı ilk bulan verir.
- Sınıf gövdesindeki
turüç örnekte 1 nesne olarak paylaşılır ve hiçbirinin sözlüğünde bulunmaz;__init__içindekiad3 ayrı nesne üretir ve üç sözlüğün üçünde de durur. - Örneğe atama sınıfı değiştirmez, gölgeler; örnekteki ad silindiğinde sınıftaki değer yine görünür hâle gelir.
- Sınıf gövdesi bir kez, örnek gövdesi her örnekte koşar: sınıfta tanımlanan bir kap 1 kez var edilip 3 eklemeyi birlikte tutar, örnekte tanımlanan 3 kez var edilir.
selfbir anahtar sözcük değildir: örnek üzerinden erişim her seferinde yeni bir bağlı yöntem üretir, o yöntem erişilen örneği taşır ve çağrıda ilk argüman olarak geçirir.- Bağlamanın üç biçimi vardır; altı çağrının beşi karşılık bulur ve düşen tek çağrı örnek
yönteminin örneksiz çağrılmasıdır. Gövdesi
Bicimci’de yazılı bir sınıf yöntemiAltBicimciüzerinden çağrıldığındacls’eAltBicimcibağlanır.
Sonraki Adım
Bu derste yanıtı veren her zaman Kayit’ti, çünkü ortada tek bir sınıf vardı ve arama ilk
adımda bitiyordu. Sınıflar birbirinden türediğinde arama uzar: bir ad birden çok sınıfta yazılı
olabilir ve hangisinin bulunacağını çözümleme sırası belirler. Sonraki ders bu sırayı
ölçer ve kursun ikinci iddiasını öder — dört yeteneğin gövdesi bir kalıtım ağacında hangi
sınıflara dağılır, aynı dört yeteneği bileşimle veren bir sınıf kaçını kendi gövdesinde yazar,
ve gövdesi bir sınıfta yazılı olan bir yöntem neden başka bir sınıfın adını basar?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.