Ders 07 / 16
Koşullar
Koşul bir mantıksal değer beklemez, nesneyi doğruluk kuralına sokar: on iki sınamada sekiz protokol çağrısı ölçülür, dördü hiçbir yöntem çağırmadan doğru döner ve karşılaştırma zinciri orta terimi bir kez üretir.
İçindekiler
Önceki ders tip dönüşümünü ölçtü ve dönüşümün başarısız olduğu yerde bir istisna doğduğunu gösterdi: nesne kendisinden istenen tipe karşılık veremediğinde soru yanıtsız kalmaz, akış kesilir. Dönüşüm nesneye “sen bir tam sayı olarak neye karşılık geliyorsun?” diye sorar ve bu sorunun yanıtsız kalma hakkı vardır.
Koşul ifadesi de nesneye bir soru sorar, ama başka bir soru: “sen doğru mu
sayılıyorsun?” Bu sorunun yanıtı, dönüşümdekinin tersine, neredeyse hiçbir zaman
istisna olmaz — nesne yanıtı kendisi vermezse dil onun yerine bir yanıt üretir. Peki o
yanıtı kim veriyor? if deyimi nesnenin içine bakıp öğe mi sayıyor, tipine mi bakıyor,
yoksa nesneye mi soruyor? Bu dersin ölçtüğü şey budur.
Koşul Bir Mantıksal Değer Beklemez
Programlama Temelleri kursunun koşullu dallanma dersi koşulu mantıksal değer üreten bir
ifade olarak tanımlamıştı; o tanım dilden bağımsızdı ve dallanmayı bir denetim yapısı
olarak kurdu. Python’da tanım daha geniştir: if anahtar sözcüğünün arkasına herhangi
bir nesne yazılabilir ve dil onu bir doğruluk değerine kendisi indirger. Burada ölçülen
şey dallanmanın kendisi değil, o indirgemenin hangi özel yöntemi çağırdığıdır.
İndirgeme kuralına doğruluk kuralı (truthiness) denir ve üç basamaklıdır:
- Nesnede
__bool__tanımlıysa o çağrılır; döndürdüğü değer sonucun kendisidir. - Tanımlı değilse
__len__çağrılır; sonuç sıfırdan büyükse nesne doğru sayılır. - İkisi de tanımlı değilse hiçbir yöntem çağrılmaz ve nesne her zaman doğrudur.
Üçüncü basamak kuralın en çok gözden kaçan yeridir. Boş bir nesnenin yanlış sayılması bir dil kuralı değil, nesnenin kendi bildirimidir: bunu bildiren bir yöntem yoksa boşluk diye bir kavram da yoktur. Sıradan bir nesne, içi ne olursa olsun, doğrudur.
Zincir Bir Kısaltmadır
Karşılaştırma işleçleri Python’da zincirlenebilir: a < b < c geçerli bir ifadedir. Bu
yazımın anlamı soldan sağa ikili karşılaştırma değildir — öyle olsaydı a < b bir
doğruluk değeri üretir, o değer c ile karşılaştırılırdı ve sonuç anlamsız çıkardı.
Zincirin anlamı a < b and b < c ifadesidir; iki farkla. Birincisi, orta terim b
bir kez değerlendirilir. Açık yazımda b iki kez yazılır ve bir işlev çağrısıysa iki
kez çalışır. İkincisi, and gibi zincir de kısa devre yapar: ilk karşılaştırma yanlış
çıkarsa ikincisi hiç kurulmaz, sağdaki terim üretilmez bile.
Zincirleme kuralı yalnız < ve > için değil, bütün karşılaştırma işleçleri için
geçerlidir; üyelik ve kimlik işleçleri de aynı sınıftadır. Bu yüzden a < b <= c kadar
a is b is c de geçerli bir zincirdir ve aynı iki güvenceyi taşır. Zincirin uzunluğunda
bir sınır yoktur: her ek terim, bir öncekiyle kendi karşılaştırmasını kurar ve
soldakilerin hepsi doğru çıkmadıkça sıraya hiç gelmez.
İkisi de gözle görülmez. Terimlerin kaç kez üretildiğini anlamanın tek yolu, üretimi sayan bir kurgu kurmaktır.
Kısa Devrenin Döndürdüğü
and ve or işleçleri bir mantıksal değer döndürmez; işlenenlerden birini döndürür.
x or y ifadesi x doğruysa x’i, değilse y’yi verir. Döndürülen şey nesnenin
kendisidir, onun doğruluk değeri değil.
Bu ikisinin çağırdığı protokol de ayrıdır: and ve or, işlenenler üzerinde bir aritmetik
işleç gibi kendi özel yöntemini çağırmaz. Yaptıkları tek şey sol işleneni doğruluk
kuralına sokmak ve sonuca göre hangi işleneni döndüreceğine karar vermektir. Sağ işlenen,
gerekiyorsa döndürülür — sınanmaz.
Ölçümün varsayımları:
- PA1 — Kâhin kurgunun kendisidir: hangi özel yöntemin çağrıldığını nesne kendi kaydettiği için bilinir; ölçüm dışarıdan bir gözlem değil, nesnenin kendi bildirimidir.
- PA2 — Üç sınıf kullanılır.
Izciyalnız__len__tanımlar,Bosluksuzona__bool__ekler,Sessizikisini de tanımlamaz. Üç sınıf doğruluk kuralının üç basamağına birebir karşılık gelir. - PA3 — Her sınıf boş ve üç öğeli iki durumda sınanır; öğe sayısı sonucu değil, sonucu kimin belirlediğini ayırmak için değişir.
- PA4 — Zincirin orta terimi bir işlev çağrısıyla üretilir ve her üretim kaydedilir; “kaç kez değerlendirildi” sorusunun ölçüsü bu kayıttır.
- PA5 —
__lt__uzunluğa göre karşılaştırır. Karşılaştırmanın neye göre yapıldığı ölçüme girmez; ölçülen şey kaç kez çağrıldığıdır. - PA6 —
Izciyalnız__lt__tanımlar, karşıtı__gt__tanımsız bırakılır; yansımanın ölçülebilmesi için tanımsızlık kasıtlıdır. - PA7 — Sayılan çağrılar yalnız bu dersin kurduğu biçimlerden gelir; ortak tanımın diğer biçimleri bu ölçüme dahil değildir.
Ölçüm
"""Dogruluk kurali: if n hangi ozel yontemi cagirir, hangi sirayla.""" KAYIT = [] def kaydet(ad): KAYIT.append(ad) class Izci: """__len__ tanimli, __bool__ tanimsiz.""" def __init__(self, ogeler=(1, 2, 3)): self.ogeler = list(ogeler) def __len__(self): kaydet("__len__") return len(self.ogeler) def __lt__(self, o): kaydet("__lt__") return len(self.ogeler) < len(o.ogeler) class Bosluksuz(Izci): """__bool__ tanimli: dogruluk sinamasi __len__'e dusmez.""" def __bool__(self): kaydet("__bool__") return len(self.ogeler) > 0 class Sessiz: """Ne __bool__ ne __len__ tanimli.""" def __init__(self, ogeler=()): self.ogeler = list(ogeler) def olc(islev): KAYIT.clear() try: deger = islev() except Exception as e: KAYIT.append(f"!{type(e).__name__}") deger = None return list(KAYIT), deger NESNELER = ( ("Izci(3 oge)", lambda: Izci((1, 2, 3))), ("Izci(0 oge)", lambda: Izci(())), ("Bosluksuz(3 oge)", lambda: Bosluksuz((1, 2, 3))), ("Bosluksuz(0 oge)", lambda: Bosluksuz(())), ("Sessiz(3 oge)", lambda: Sessiz((1, 2, 3))), ("Sessiz(0 oge)", lambda: Sessiz(())), ) print(f"{'nesne':<18s} {'if n':>6s} {'not n':>6s} çağrılan protokol") toplam = sinama = sessiz = 0 for ad, kur in NESNELER: n = kur() c1, d1 = olc(lambda: bool(n)) c2, d2 = olc(lambda: not n) toplam += len(c1) + len(c2) sinama += 2 sessiz += (len(c1) == 0) + (len(c2) == 0) print(f" {ad:<16s} {str(d1):>6s} {str(d2):>6s} {' '.join(c1) or '(yöntem yok)'}") print(f"\nsınama {sinama}, çağrı {toplam}, hiçbir yöntemi çağırmayan {sessiz}") URETIM = [] def uret(ad, n): URETIM.append(ad) return Izci(range(n)) def zincir(): return uret("a", 1) < uret("b", 2) < uret("c", 3) def acilim(): return (uret("a", 1) < uret("b", 2)) and (uret("b", 2) < uret("c", 3)) def kisa_devre(): return uret("a", 3) < uret("b", 2) < uret("c", 9) print() print(f"{'biçim':<16s} {'sonuç':>6s} {'__lt__':>7s} {'üretim':>7s} sıra") for ad, f in (("a<b<c", zincir), ("a<b and b<c", acilim), ("a<b<c (yanlış)", kisa_devre)): URETIM.clear() c, d = olc(f) print(f" {ad:<14s} {str(d):>6s} {c.count('__lt__'):7d}" f" {len(URETIM):7d} {' '.join(URETIM)}") print() print(f"{'karşılaştırma':<24s} {'sonuç':>6s} çağrılan protokol") for ad, f in (("Izci(1) < Izci(2)", lambda: Izci((1,)) < Izci((1, 2))), ("Izci(2) > Izci(1)", lambda: Izci((1, 2)) > Izci((1,))), ("Sessiz(1) < Sessiz(2)", lambda: Sessiz((1,)) < Sessiz((1, 2)))): c, d = olc(f) print(f" {ad:<22s} {str(d):>6s} {' '.join(c)}") print() print(f"{'ifade':<26s} {'çağrı':>6s} {'döndürülen tip':<16s} protokol") BOS, DOLU = Izci(()), Bosluksuz((1, 2)) for ad, f in (("Izci(0) or Bosluksuz(2)", lambda: BOS or DOLU), ("Bosluksuz(2) or Izci(0)", lambda: DOLU or BOS), ("Izci(0) and Bosluksuz(2)", lambda: BOS and DOLU), ("bool(Izci(0) or Izci(0))", lambda: bool(Izci(()) or Izci(())))): c, d = olc(f) print(f" {ad:<24s} {len(c):6d} {type(d).__name__:<16s} {' '.join(c)}")
nesne if n not n çağrılan protokol Izci(3 oge) True False __len__ Izci(0 oge) False True __len__ Bosluksuz(3 oge) True False __bool__ Bosluksuz(0 oge) False True __bool__ Sessiz(3 oge) True False (yöntem yok) Sessiz(0 oge) True False (yöntem yok) sınama 12, çağrı 8, hiçbir yöntemi çağırmayan 4 biçim sonuç __lt__ üretim sıra a<b<c True 2 3 a b c a<b and b<c True 2 4 a b b c a<b<c (yanlış) False 1 2 a b karşılaştırma sonuç çağrılan protokol Izci(1) < Izci(2) True __lt__ Izci(2) > Izci(1) True __lt__ Sessiz(1) < Sessiz(2) None !TypeError ifade çağrı döndürülen tip protokol Izci(0) or Bosluksuz(2) 1 Bosluksuz __len__ Bosluksuz(2) or Izci(0) 1 Bosluksuz __bool__ Izci(0) and Bosluksuz(2) 1 Izci __len__ bool(Izci(0) or Izci(0)) 2 bool __len__ __len__
Üç Basamağın Bedeli
Üst tablo doğruluk kuralının üç basamağını yan yana koyuyor. On iki sınamada sekiz çağrı var; aradaki dört sınama hiçbir yöntemi çağırmadan sonuç üretiyor.
İlk iki satır ikinci basamak: Izci __bool__ tanımlamadığı için sınama __len__’e
düşüyor ve sonucu öğe sayısı belirliyor. Üçüncü ve dördüncü satır birinci basamak: aynı
sınıfa __bool__ eklendiği anda __len__ hiç çağrılmıyor. İkisi de tanımlıyken hangi
yöntemin çağrılacağı sorusu yok — __bool__ varsa __len__ sınamaya katılmaz.
Son iki satır üçüncü basamak ve tablonun asıl sonucu burada. Sessiz(0 oge) boş, ama
sınama doğru veriyor. Üstelik bunu bir yöntem çağırarak değil, hiç çağırmayarak
yapıyor. Nesnenin üç öğesi mi var, sıfır mı — sonucu değiştirmiyor, çünkü sonucu üretecek
bir yöntem yok. Bu satırın pratik karşılığı şudur: kendi yazdığı bir sınıfın boş örneğini
if ile sınayan bir program, __len__ ya da __bool__ yazmayı unutmuşsa her zaman
doğru dalını alır ve bunu bildiren hiçbir belirti çıkmaz.
not n sütunu aynı zincirin bir kez daha yürüdüğünü gösteriyor: olumsuzlama ayrı bir
protokol değil, aynı sınamanın sonucunun tersidir. Sütunlardaki tek fark Sessiz
satırlarında görülüyor — not bir nesne değil, gerçek bir mantıksal değer döndürüyor.
Zincir Kaç Terim Üretiyor
Orta tablo zincirin kısaltma olduğunu sayıyla gösteriyor. a<b<c ile açık yazımın
__lt__ sayısı aynı: ikisi de 2. Karşılaştırma sayısında bir kazanç yok.
Fark üretim sütununda. Zincir üç terim üretiyor, açık yazım dört: b iki kez
üretiliyor. Bu, okunabilirlik değil anlam farkıdır. Orta terim bir işlev çağrısıysa
o işlev iki kez çalışır; yan etkisi varsa yan etki iki kez oluşur; her çağrıda başka bir
değer döndürüyorsa iki karşılaştırma birbirinden habersiz iki ayrı değeri sınar. Açık
yazım, zincirin verdiği güvenceyi vermez.
Üçüncü satır kısa devreyi ölçüyor. İlk karşılaştırma yanlış çıktığı için __lt__
1 kez çağrılıyor ve üretim 2’de duruyor: c hiç kurulmuyor. Zincirin sağ ucundaki
terim, sola bakılmadan üretilmeyecek bir şeydir.
Ters Yönde Bulunan Protokol
Üçüncü tablo karşılaştırmanın kendi düşme kuralını gösteriyor ve ortadaki satır dersin en
sert sonucudur. Izci sınıfı __lt__ tanımlar, __gt__ tanımlamaz. Buna rağmen
Izci(2) > Izci(1) çalışıyor — ve çağrılan yöntem __lt__.
Kural şudur: bir karşılaştırmada sol işlenenin yöntemi bulunamazsa dil sağ işlenenin
karşıt yöntemini dener. a > b için önce a üzerinde __gt__ aranır; yoksa b
üzerinde __lt__ çağrılır ve işlenenler yer değiştirir. Sonuç doğru çıkar, çünkü
“b, a’dan küçüktür” ile “a, b’den büyüktür” aynı iddiadır.
Bunun sonucu doğrudan ikinci iddiadır: yazılan işleç, çağrılan yöntemin adını
söylemez. İki satır sonucu True veriyor, ikisi de aynı yöntemi çağırıyor, ama biri
< biri > yazıyor. Sözdizime bakarak hangi yöntemin çalıştığını kestirmek olanaksızdır;
bunu yalnız ölçüm söyler.
Üçüncü satır kuralın sonunu gösteriyor. Sessiz ne __lt__ ne __gt__ tanımlar; ne sol
işlenende ne sağ işlenende bir yöntem bulunuyor ve karşılaştırma bir sonuç değil,
TypeError üretiyor. Doğruluk sınamasının üçüncü basamağı sessizce doğru dönerken,
karşılaştırmanın son basamağı istisnayla biter: bir sözdizim için varsayılan davranış
var, öbürü için yok. Protokolü olmayan nesne, o sözdizimi hiç taşımaz.
Koşul Deyimi ile Koşul İfadesi
Doğruluk kuralı yalnız if deyiminde çalışmaz. Aynı indirgeme while koşulunda, and ve
or işleçlerinde, not işlecinde ve koşul ifadesinde de yürür.
Koşul ifadesi, önceki konunun deyim ile ifade ayrımını kullanır: if bir deyimdir,
değer üretmez ve bir atamanın sağına yazılamaz. Bunun ifade karşılığı a if k else b
yazımıdır ve bir değer üretir — dolayısıyla bir atamanın sağına, bir argümanın yerine
ya da bir listenin içine konabilir.
İkisi arasındaki fark yalnız yazımdadır; doğruluk kuralı ikisinde de aynı üç basamağı
yürütür ve k yerine yazılan nesne aynı ölçümden geçer. Kısa devre de korunur: koşul
ifadesinde a ile b dallarından yalnız biri değerlendirilir. Bu, koşul ifadesini
and ve or zincirlerinden ayıran şeydir — orada döndürülen değer işlenenlerden biriydi,
burada seçilen dalın kendisidir ve seçilmeyen dal hiç kurulmaz.
Döndürülen Nesnenin Tipi
Alt tablo and ile or işleçlerinin ne döndürdüğünü gösteriyor ve üç satırının üçünde de
dönen şey bir mantıksal değer değil.
İlk satır: sol işlenen Izci(0) boş, sınama __len__ ile yanlış çıkıyor, or sağ
işleneni döndürüyor — dönen tip Bosluksuz. İkinci satır: sol işlenen bu kez doğru,
__bool__ ile sınanıyor ve or sol işleneni döndürüyor. Üçüncü satırda and tersini
yapıyor: sol işlenen yanlış olduğu için sonuç doğrudan o oluyor, tip Izci.
Üç satırın da çağrı sayısı 1. Sağ işlenen hiçbir zaman sınanmıyor; yalnız döndürülüyor.
Bunun sonucu, or ile yazılan varsayılan değer kalıbının Python’da tam olarak ne yaptığıdır:
sol işlenen doğruluk kuralından yanlış çıkarsa sağdaki verilir — boş olduğu için değil,
yanlış sayıldığı için.
Son satır ikisini ayırıyor. bool() yerleşiği araya girdiğinde dönen tip gerçekten bool
oluyor ve çağrı sayısı 2’ye çıkıyor: bir kez or sol işleneni sınıyor, bir kez de
bool() sonucu sınıyor. Aynı ifadeyi mantıksal değere çevirmenin bedeli fazladan bir
protokol çağrısıdır.
Özet
- Python’da koşul mantıksal değer beklemez;
ifarkasındaki her nesne doğruluk kuralından geçirilir ve ölçülen şey bu indirgemenin çağırdığı özel yöntemdir. - Kural üç basamaklıdır:
__bool__varsa o çağrılır, yoksa__len__’e düşülür, o da yoksa hiçbir yöntem çağrılmadan nesne doğru sayılır — on iki sınamanın dördü bu basamakta ve boş nesne bile doğru döner. __bool__tanımlıyken__len__sınamaya hiç katılmaz; iki yöntem yarışmaz, ilki ikincisini devre dışı bırakır.- Karşılaştırma zinciri
a<b<cile açık yazım aynı sayıda__lt__çağırır (2), ama zincir orta terimi bir kez üretir, açık yazım iki kez; zincir yanlış çıktığında sağ terim hiç üretilmez. - Karşılaştırmanın kendi düşme kuralı vardır: sol işlenende yöntem yoksa sağ işlenenin
karşıtı çağrılır —
>yazılan bir ifade__lt__çalıştırır; ikisi de yoksa sonuç bir değer değil,TypeErrorolur. andileorbir mantıksal değer değil, işlenenlerden birini döndürür; yalnız sol işleneni doğruluk kuralına sokarlar, sağ işleneni sınamazlar.
Sonraki Adım
Doğruluk kuralı tek bir soruyu bir kez sorar ve yanıtı bir dalla biter. Döngü ise aynı
soruyu değil, başka bir soruyu tekrar tekrar sorar: sırada bir öğe var mı? Programlama
Temelleri kursunda döngü bir denetim yapısıydı; kaç tur döneceği koşula ya da koleksiyonun
uzunluğuna bağlıydı. Python’da döngü bir protokoldür ve turların bitmesine karar veren
şey bir koşul değildir. Sonraki ders üç öğelik bir nesnede döngünün kaç kez __next__
çağırdığını sayar — ve o sayının neden öğe sayısından bir fazla olduğunu.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.