Ders 06 / 11
Veri Sınıfları
Dört kaynak satırlık kısa tanım elle yazılan on satırın yazdığı 4 özel yöntemin aynısını yazar; sıralama seçeneği 8'e, dondurma 6'ya, ikisi birlikte 10'a çıkarır ve dondurulan sınıfta beş işlemin 3'ü düşer.
İçindekiler
Önceki iki ders yazılan gövdeleri saydı: on bir sözdizim için on iki gövde, tek bir alanın denetimi için iki gövde daha. Oysa yazdığımız sınıfların çoğu aynı kalıbı tekrarlar. Birkaç alan taşırlar, kurulumda o alanları doldururlar, alanlarıyla karşılaştırılırlar ve alanlarıyla basılırlar. Bu kalıp her sınıfta elle yazıldığında hem uzar hem de her yazımda bir alanı unutma olasılığı taşır.
Standart kitaplık bu kalıp için kısa bir tanım biçimi sunar. Bu dersin sorusu kolaylık değil, ölçüdür: kısa tanım sınıfın sözlüğüne hangi özel yöntemleri gerçekten yazar, elle yazılmış karşılığıyla yan yana konduğunda hangileri eksik kalır, ve tanıma eklenen her seçenek sözlüğe kaç yeni ad koyar?
Kısa Tanım Ne Yazıyor
Beş sınıf aynı iki alanı taşır: bir dizgi ve bir tam sayı. ElleYazilan kurulum, temsil ve
eşitlik gövdelerini elle yazar. Kalan dördü veri sınıfı (data class) tanımıdır: alanlar
adlarıyla ve açıklamalarıyla bildirilir, gövdeler bildirilmez. Dört tanım birbirinden yalnız
verilen seçeneklerle ayrılır — biri varsayılan, biri sıralama ister, biri dondurulmuştur, biri
ikisini birden.
Ölçüm on özel yöntem adını her sınıfın kendi sözlüğünde arar. Atalardan gelen adlar sayıya girmez; sorulan şey kısa tanımın o sınıfa ne yazdığıdır.
- SN62 — Beş sınıfın alanları aynıdır; adları, sıraları ve açıklamaları da aynıdır.
- SN63 — “Kaynak satırı” satırı her tanımın kaç dolu kaynak satırı tuttuğunu sayar; boş satırlar sayılmaz, süsleyici satırı sayılır.
- SN64 — Aranan on ad yalnız sınıfın kendi sözlüğünde aranır;
saglayankullanılmaz, çünkü ölçülen şey adın yazılıp yazılmadığıdır, nereden geldiği değil. - SN65 — “Karma değeri” satırı adın varlığına değil, değerinin boş olup olmadığına bakar; önceki dersin ölçtüğü boşaltma burada yeniden görünür.
"""Kisa tanim sinifin sozlugune hangi ozel yontemleri yaziyor.""" import inspect from dataclasses import dataclass class ElleYazilan: def __init__(self, ad, sayi): self.ad = ad self.sayi = sayi def __repr__(self): return f"ElleYazilan(ad={self.ad!r}, sayi={self.sayi!r})" def __eq__(self, o): if not isinstance(o, ElleYazilan): return NotImplemented return (self.ad, self.sayi) == (o.ad, o.sayi) @dataclass class Varsayilan: ad: str sayi: int @dataclass(order=True) class Sirali: ad: str sayi: int @dataclass(frozen=True) class Donmus: ad: str sayi: int @dataclass(frozen=True, order=True) class DonmusSirali: ad: str sayi: int SINIFLAR = (ElleYazilan, Varsayilan, Sirali, Donmus, DonmusSirali) ARANAN = ("__init__", "__repr__", "__eq__", "__hash__", "__lt__", "__le__", "__gt__", "__ge__", "__setattr__", "__delattr__") def satir(sinif): """Tanimin kac dolu kaynak satiri tuttugu.""" return sum(1 for s in inspect.getsource(sinif).splitlines() if s.strip()) print(f"{'özel yöntem':<13s}" + "".join(f"{s.__name__:>13s}" for s in SINIFLAR)) for ad in ARANAN: print(f"{ad:<13s}" + "".join(f"{('yazılı' if ad in s.__dict__ else '.'):>13s}" for s in SINIFLAR)) print(f"{'toplam yazılı':<13s}" + "".join(f"{sum(1 for a in ARANAN if a in s.__dict__):>13d}" for s in SINIFLAR)) print(f"{'kaynak satırı':<13s}" + "".join(f"{satir(s):>13d}" for s in SINIFLAR)) print(f"{'karma değeri':<13s}" + "".join(f"{('yok' if s.__hash__ is None else 'var'):>13s}" for s in SINIFLAR))
özel yöntem ElleYazilan Varsayilan Sirali Donmus DonmusSirali __init__ yazılı yazılı yazılı yazılı yazılı __repr__ yazılı yazılı yazılı yazılı yazılı __eq__ yazılı yazılı yazılı yazılı yazılı __hash__ yazılı yazılı yazılı yazılı yazılı __lt__ . . yazılı . yazılı __le__ . . yazılı . yazılı __gt__ . . yazılı . yazılı __ge__ . . yazılı . yazılı __setattr__ . . . yazılı yazılı __delattr__ . . . yazılı yazılı toplam yazılı 4 4 8 6 10 kaynak satırı 10 4 4 4 4 karma değeri yok yok yok var var
İlk iki sütun karşılaştırmanın kendisidir. Elle yazılan tanım 10 kaynak satırında 4 ad yazıyor; kısa tanım 4 satırda aynı 4 adı yazıyor. Yazılan adlar birebir aynıdır — kısa tanım eksik bir şey bırakmıyor, fazladan bir şey de eklemiyor. Ölçünün asıl söylediği şudur: kısa tanımın kazandırdığı yalnız altı satır değildir. Elle yazılan sınıfta her alan adı üç gövdenin üçünde de yazılır — kurulumda, temsilde ve eşitlikte. Yeni bir alan eklendiğinde üç gövdenin üçü de güncellenmek zorundadır; kısa tanımda alan bir kez bildirilir. Unutulan bir güncellemeyle sessizce eksik karşılaştıran bir eşitlik gövdesi, bu kalıbın en pahalı hatasıdır ve hiçbir zaman istisna üretmez.
Dördüncü satır önceki dersin ölçtüğü düzeneği yeniden gösteriyor. Beş sınıfın beşinde de
__hash__ adı sözlükte yazılıdır, ama alt satır ilk üçünde değerinin boş olduğunu
söylüyor. Eşitliği yazan her sınıf karma yeteneğini kaybeder; kısa tanım da bu kuralın dışında
değildir. Elle yazılan sınıf da aynı durumdadır ve aynı nedenle. Sonuç, veri sınıflarının en sık
şaşırtan yanıdır: değerle karşılaştırılabilen bir nesne, dondurulmadıkça bir kümeye ya da
sözlük anahtarına konamaz.
Seçenekler sözlüğe ne kattığını açıkça gösteriyor. Sıralama seçeneği 4 karşılaştırma yöntemi ekler ve toplamı 8’e çıkarır. Dondurma seçeneği yazma ve silme yollarını yakalayan 2 yöntem ekler, toplamı 6 yapar ve karma değerini geri getirir. İkisi birlikte verildiğinde toplam 10 olur — dört satırlık bir tanım, on gövdelik bir sınıf üretir.
Bu tablo kısa tanımın ne olduğunu da tanımlıyor: bir tanım değil, bir yazma işlemidir. Sınıf gövdesindeki alan bildirimleri okunur ve karşılık gelen gövdeler sınıfın sözlüğüne konur. Sözlüğe konan her ad, elle yazılabilecek olanın aynısıdır — bu yüzden kısa tanımla yazılmış bir sınıfta herhangi bir yöntemi elle yazmak da onu geçersiz kılar, çünkü gövdede zaten bulunan bir adın üzerine yazılmaz.
Yazılmayanları da saymak gerekir. Ölçüm on ad aradı ve dördüncü dersteki on bir sözdizimin çoğu bu listede yok: uzunluk, yineleme, üyelik, sıra erişimi ve bağlam sözdizimleri kısa tanımdan gelmez. Kısa tanım bir kayıt yazar, bir kap yazmaz. Bir veri sınıfının kap gibi davranması isteniyorsa gerekli gövdeler yine elle yazılır.
Alanların nasıl bulunduğu da bir noktayı açıkta bırakıyor. Kısa tanım hangi adların alan
olduğunu, sınıf gövdesindeki açıklamalardan okur — ad: str yazımındaki tip adı alanın
varlığını bildirir. Bu tip adının çalışma zamanında ne yaptığı ayrı bir sorudur ve bu kursun
ikinci konusunda ölçülür; burada söylenebilecek olan, kısa tanımın onu yalnız bir işaret
olarak kullandığıdır.
Değiştirilebilir Varsayılan
Kısa tanımın yalnız yazdıkları değil, reddettikleri de ölçülebilir. Python Temelleri kursu varsayılan değerin işlev tanımlanırken bir kez değerlendirildiğini ve değiştirilebilir bir varsayılanın çağrılar arasında paylaşıldığını ölçmüştü; o ölçüm tekrarlanmaz. Burada sorulan şey aynı yazımın iki tanım biçiminde ne ürettiğidir.
- SN66 — Üç tanım aynı alanı aynı varsayılanla kurmayı dener; ilki elle yazılan sınıf, ötekiler kısa tanımdır.
- SN67 — Kurulamayan tanım ünlem önekiyle istisnanın adını kaydeder ve o satırın ölçüm sütunları boş bırakılır.
- SN68 — Kurulan her tanımdan üç örnek var edilir ve her biri kaba kendi sırasını ekler;
paylaşım
isile gösterilir.
from dataclasses import field class ElleKap: """Varsayilan deger bir kez degerlendirilir; uc ornek onu paylasir.""" def __init__(self, ogeler=[]): self.ogeler = ogeler def kisa_tanim(varsayilan): """Ayni varsayilani kisa tanimla kurmayi dener.""" try: @dataclass class Gecici: ogeler: list = varsayilan except ValueError as e: return f"!{type(e).__name__}", None return "kuruldu", Gecici TANIMLAR = ( ("elle yazılan, varsayılan []", "kuruldu", ElleKap), ("kısa tanım, varsayılan []", *kisa_tanim([])), ("kısa tanım, üretici işlev", *kisa_tanim(field(default_factory=list))), ) print(f"{'tanım':<28s} {'tanım anı':>16s} {'üç örneğin gördüğü':>20s} " f"{'kap paylaşılıyor':>17s}") for ad, durum, sinif in TANIMLAR: if sinif is None: print(f"{ad:<28s} {durum:>16s} {'—':>20s} {'—':>17s}") continue ucu = [sinif() for _ in range(3)] for i, n in enumerate(ucu): n.ogeler.append(i) print(f"{ad:<28s} {durum:>16s} {str([len(n.ogeler) for n in ucu]):>20s} " f"{str(ucu[0].ogeler is ucu[2].ogeler):>17s}")
tanım tanım anı üç örneğin gördüğü kap paylaşılıyor elle yazılan, varsayılan [] kuruldu [3, 3, 3] True kısa tanım, varsayılan [] !ValueError — — kısa tanım, üretici işlev kuruldu [1, 1, 1] False
İlk satır bilinen sonucu veriyor: elle yazılan tanım kuruluyor, üç örnek tek kabı paylaşıyor ve her biri 3 öğe görüyor. Hata çalışma zamanında da ortaya çıkmıyor; program sessizce yanlış çalışıyor.
İkinci satır kısa tanımın farkıdır. Aynı yazım tanım anında ValueError ile düşüyor;
sınıf hiç kurulamıyor ve hatanın ortaya çıkacağı yer, hatanın yazıldığı satırın kendisi
oluyor. Üçüncü satır kabul edilen yazımı gösteriyor: varsayılan bir değer değil, her örnek
için çağrılacak bir üretici işlev verildiğinde tanım kuruluyor ve üç örnek [1, 1, 1]
görüyor — kap paylaşılmıyor.
Bu, ilk dersin sınıf gövdesindeki kap ölçümüyle aynı düzenektir ve üçüncü kez karşımıza çıkıyor. Kısa tanımın buradaki katkısı yeni bir kural getirmek değil, var olan kuralı erkene almaktır: bilinen bir yanlış yazımı çalışma zamanına bırakmak yerine tanım anında reddeder.
Reddin ne kadar dar olduğunu da yazmak gerekir. Kısa tanım yalnız bildiği birkaç değiştirilebilir tipi tanır; kendi yazdığımız değiştirilebilir bir sınıf varsayılan olarak verildiğinde tanım kurulur ve paylaşım ilk satırdaki sonucun aynısını verir. Yakalanan şey genel bir kural değil, sık yapılan bir yazımdır. Kuralın kendisi değişmemiştir: varsayılan değer tanım anında bir kez var edilir ve örneğe ait olması gereken her kap bir üretici işlevle bildirilir.
Dondurmanın Ölçüsü
Son seçenek nesnenin yazılabilirliğini kapatır. Değişmezliğin gerekçesi ve değer nesnesi kavramı Yazılım Tasarımı ve Mimari İlkeler müfredatında kuruldu; o tartışma tekrarlanmaz. Burada ölçülen şey tek bir seçeneğin kaç işlemi kapattığı ve karşılığında ne açtığıdır.
- SN69 — İki sınıf aynı iki alanı taşır; tek farkları dondurulmuş olup olmamalarıdır.
- SN70 — Beş işlem denenir ve her biri taze bir örnek üzerinde çalışır; işlemler birbirini etkilemez.
- SN71 — Son satır aynı değerleri taşıyan iki ayrı nesneyi karşılaştırır; kimlik
isile gösterilir ve kimlik sayısı basılmaz.
from dataclasses import replace ISLEMLER = ( ("alana yazma", lambda n: setattr(n, "sayi", 9)), ("alan silme", lambda n: delattr(n, "sayi")), ("yeni alan açma", lambda n: setattr(n, "yeni", 1)), ("yerine yeni nesne", lambda n: replace(n, sayi=9)), ("kümeye koyma", lambda n: len({n})), ) def dene(sinif, islev): try: islev(sinif("k", 1)) except Exception as e: return f"!{type(e).__name__}" return "geçti" print(f"{'işlem':<19s} {'Varsayilan':>21s} {'Donmus':>21s}") for ad, islev in ISLEMLER: print(f"{ad:<19s} {dene(Varsayilan, islev):>21s} {dene(Donmus, islev):>21s}") print() for sinif in (Varsayilan, Donmus): dusen = sum(1 for _, f in ISLEMLER if dene(sinif, f).startswith("!")) print(f"{sinif.__name__}: beş işlemden düşen {dusen}, " f"geçen {len(ISLEMLER) - dusen}") a, b = Donmus("k", 1), Donmus("k", 1) print(f"ayrı iki nesne: a is b -> {a is b}, a == b -> {a == b}, " f"kümede kaç öğe -> {len({a, b})}")
işlem Varsayilan Donmus alana yazma geçti !FrozenInstanceError alan silme geçti !FrozenInstanceError yeni alan açma geçti !FrozenInstanceError yerine yeni nesne geçti geçti kümeye koyma !TypeError geçti Varsayilan: beş işlemden düşen 1, geçen 4 Donmus: beş işlemden düşen 3, geçen 2 ayrı iki nesne: a is b -> False, a == b -> True, kümede kaç öğe -> 1
Dondurma 3 işlemi kapatıyor ve 1 işlemi açıyor. Kapananların üçü de yazmadır: alana yazma, alan silme ve yeni bir alan açma. Üçüncüsü ayrıca dikkat çekicidir — üçüncü dersteki öznitelik kümesi bildirimiyle aynı kapıyı kapatır, ama farklı bir gerekçeyle. Açılan işlem kümeye koymadır: dondurulmuş nesne karma değerine sahip olduğu için bir kapta anahtar olabilir, sıradan veri sınıfı olamaz.
Dördüncü satır iki sınıfta da geçiyor ve dondurmanın nasıl kullanıldığını gösteriyor. Değişmez bir nesne değiştirilmez, yerine yenisi konur; standart kitaplığın verdiği işlev istenen alanları değiştirilmiş yeni bir nesne üretir. Yazma engellenmiştir, yeniden üretme engellenmemiştir.
Son satır dondurulmuş bir veri sınıfının ne olduğunu tanımlıyor: is False, == True,
kümede 1 öğe. İki ayrı nesne, aynı değerleri taşıdıkları için birbirinin yerine geçer.
Kimlikle değil değerle tanımlanmak, bu üç sonucun toplamıdır — ve bu dersteki tanımın ürettiği
şey tam olarak budur.
Dondurmanın sınırını da yazmak gerekir, çünkü tablo onu göstermez. Kapatılan şey alana
yazma yoludur; alanın gösterdiği nesnenin kendisi değil. Dondurulmuş bir nesnenin alanı bir
liste tutuyorsa o listeye öğe eklemek hiçbir engelle karşılaşmaz — üçüncü dersteki ölçümün
son satırı burada aynen geçerlidir. Değişmezlik bir katman derinliğindedir; nesnenin gerçekten
değiştirilemez olması, alanlarının da değiştirilemez nesneler olmasına bağlıdır. Karma değeri
de aynı sınırı taşır: dondurulmuş bir sınıf alanında bir liste taşıyorsa kümeye koyma satırı
TypeError ile düşer, çünkü karma alanlardan üretilir.
Özet
- Kısa tanım 4 kaynak satırında, elle yazılan 10 satırın yazdığı 4 özel yöntemin aynısını sınıfın sözlüğüne yazar; yazılan adlar birebir aynıdır.
- Eşitliği yazan her sınıfta olduğu gibi kısa tanımda da
__hash__adı yazılır ve değeri boş bırakılır; karma yeteneği ancak dondurmayla geri gelir. - Seçenekler sözlüğe ad ekler: sıralama 4 karşılaştırma yöntemi ekleyip toplamı 8’e, dondurma 2 yöntem ekleyip 6’ya çıkarır, ikisi birlikte 10 yapar.
- Değiştirilebilir varsayılan elle yazılan tanımda kurulur ve üç örnek tek kabı paylaşıp
[3, 3, 3] görür; kısa tanımda tanım anında
ValueErrorile düşer ve üretici işlevle yazıldığında [1, 1, 1] görülür. - Dondurma beş işlemin 3’ünü kapatır ve 1’ini açar: üç yazma yolu düşer, kümeye koyma geçer, yerine yeni nesne koyma iki sınıfta da geçer.
- Dondurulmuş iki ayrı nesne için
isFalse,==True ve kümede 1 öğe kalır.
Sonraki Adım
Bu ders bir sınıfın ne yazdığını sözlüğüne bakarak saydı; önceki dersler de yanıtı hep sınıf
ağacında aradı. İki yöntem de aynı varsayıma dayanıyor: bir nesnenin ne yapabildiğini bilmek
için onun hangi sınıftan geldiğini bilmek gerekir. Oysa dördüncü ders bunun aksini
göstermişti — katılım tipin adından değil, yazılan yöntemlerden geliyordu. Sarmal hiçbir
şeyden türemez ama dört yeteneğin dördünü de karşılar. Sonraki ders bu ikiliği sınamaya
çevirir: aynı nesne, soy bağı arayan bir sınamayla yöntem arayan bir sınamadan
hangisini geçer, soyut bir tabanın bildirdiği sözleşme hangi anda uygulanır, ve yöntem arayan
sınama gerçekten neye bakar?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.