İçeriğe geç
academia.sh

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 Kayit sınıfıdır; gövdesine bir sınıf özniteliği (tur) eklenmiş, kaynak ve bicimle yetenekleri 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 is ile 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 is ile 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 Kayit sı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çindeki ad 3 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.
  • self bir 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öntemi AltBicimci üzerinden çağrıldığında cls’e AltBicimci bağ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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat