Ders 03 / 16
Değişkenler ve Veri Tipleri
Ad tip taşımaz, nesne taşır: tek bir ad yedi ayrı tipe bildirimsiz bağlanır, `+=` yazımı dört tipin üçünde adı yeniden bağlayıp birinde nesneyi değiştirir ve eşit iki değerin aynı nesne çıkması gerçekleştirime bağlıdır.
İçindekiler
Önceki ders girintinin blokları, blokların da deyimleri taşıdığını gösterdi ve en sık yazılan deyim olan atama tabloda yalnızca “ifade değil” olduğu için göründü. Bu ders o deyimin ne yaptığına bakar.
Programlama Temelleri kursu atamanın bir bağlama olduğunu, değişkenin ya adlandırılmış
bir bellek gözü ya da nesneye takılmış bir etiket olarak modellenebileceğini, is ile ==
arasındaki ayrımı ve takma adı zaten kurmuştu. Bunlar tekrarlanmaz. Orada kurulan şey
bir kavramdı: bir dil iki modelden birini seçer. Burada ölçülen şey Python’un seçtiği
modelin gözlenebilir sonuçlarıdır — adın hiç tip taşımaması, += yazımının tipe göre
bağ mı kurduğu yoksa nesneyi mi değiştirdiği, ve eşit iki değerin aynı nesne çıkıp
çıkmamasının neye dayandığı.
Ad Tip Taşımaz
Python’da her değer bir nesnedir ve tip nesnenin özelliğidir. Ad bir bildirim değil,
yalnızca bir bağdır; ne bir tip ilan eder ne de bir tipe kilitlenir. type yerleşiği bu
yüzden nesneye sorulur, ada değil.
- DT15 — Yedi ayrı tipten birer değer tek bir ada sırayla bağlanır; kaynakta hiçbir tip bildirimi yoktur. Ölçülen şey her bağlamadan sonra nesnenin bildirdiği tip adıdır.
- DT16 — Son bağlama bir işlevedir; işlevin de bağlanabilir bir nesne olduğu böylece aynı tabloda görünür.
"""Ad bir etikettir: tipi nesne tasir, ad tasimaz.""" DEGERLER = (12, 3.5, "olcum", (1, 2), [1, 2], {"kuzey": 1}, None) def uzunluk(metin): return len(metin) olcum = None tipler = [] for deger in DEGERLER: olcum = deger # tek ad, sirayla yedi nesne tipler.append(type(olcum).__name__) print("tek ada sırayla bağlanan tipler:", " ".join(tipler)) print("bağlama sayısı", len(DEGERLER), "— hiçbirinde tip bildirimi yok") print("son bağlamadan sonra adın tipi:", type(olcum).__name__) olcum = uzunluk # ayni ad, simdi bir islev print("aynı ad bir işleve de bağlanabilir:", type(olcum).__name__, "->", olcum("olcum"))
tek ada sırayla bağlanan tipler: int float str tuple list dict NoneType bağlama sayısı 7 — hiçbirinde tip bildirimi yok son bağlamadan sonra adın tipi: NoneType aynı ad bir işleve de bağlanabilir: function -> 5
Tek bir ad 7 ayrı tipe bağlandı ve sekizincide bir işleve. Hiçbir bağlamada bir bildirim
yazılmadı, çünkü yazılacak bir yer yok: olcum adı bir kap değil, üzerine takıldığı nesneye
işaret eden bir addır. Adın tipini soran bir işlem de yoktur; type çağrısı adın bağlı
olduğu nesneye gider ve yanıtı o nesne verir.
Son satır bir sınıfı daha kapatıyor. İşlev de bir nesnedir; tanımlanması, bir ada bir nesne bağlamaktan başka bir şey değildir. Bu yüzden bir işlev bir ada, bir listeye ya da bir sözlüğe konabilir ve oradan çağrılabilir.
Python’da bir bildirime benzeyen bir yazım vardır ve karıştırılmaya elverişlidir: bir parametrenin ya da dönüş değerinin yanına bir tip adı yazılabilir. Bu yazım bir bildirim değil, bir nottur; yorumlayıcı onu saklar ama zorlamaz.
"""Not bildirim degildir: yorumlayici yazilani zorlamaz.""" def iki_kati(n: int) -> int: return n * 2 print("tam sayı:", iki_kati(12)) print("dizgi: ", repr(iki_kati("ol"))) print("liste: ", iki_kati([1, 2])) print("notların saklandığı yer:", list(iki_kati.__annotations__))
tam sayı: 24 dizgi: 'olol' liste: [1, 2, 1, 2] notların saklandığı yer: ['n', 'return']
İşlev bir tam sayı beklediğini yazdığı hâlde dizgiyle de listeyle de çalıştı ve hiçbir uyarı üretmedi. Yazılan notlar kaybolmuş da değil; işlevin üzerinde iki ad altında duruyorlar. Yani not, yürütmeye değil okuyucuya ve dışarıdan çalışan denetleyicilere yazılır. Nota bakıp “burada tip denetleniyor” sanmak, dilin en pahalı yanlış okumalarından biridir.
Buna dinamik tipleme denir ve doğru okunması gerekir: tipin olmadığı anlamına gelmez,
tip denetiminin yürütme aşamasına ertelendiği anlamına gelir. Bu, ilk dersteki sınırın
aynısıdır. Bir adın var olup olmadığı derlenirken bilinemiyordu; bir nesnenin bir işlemi
karşılayıp karşılamadığı da bilinemez, çünkü nesne ancak çalışırken ortaya çıkar. Kusur
bulunduğunda ortaya çıkan istisna da ilk derste ölçülenle aynıdır — aranan özel yöntem yoksa
TypeError.
Yerleşik Tipler ve Taşıdıkları Protokol
Tip nesnenindir; öyleyse tipleri birbirinden ayıran şey nedir? Programlama Temelleri kursu temel veri tiplerini bir sınıflandırma olarak kurmuştu — sayı, metin, mantıksal değer, koleksiyon. O sınıflandırma tekrarlanmaz. Burada sorulan şey başkadır ve ilk dersin ölçüsünden doğar: bir yerleşik tip, hangi sözdizim biçimlerini karşılayabildiğini tanımladığı özel yöntemlerle ilan eder. Tipin adı hiçbir şey söylemez; yöntem listesi her şeyi söyler.
- DT17 — Her tip tek bir örnekle temsil edilir ve yoklama örneğin kendisine değil, tipine yapılır; sorulan şey tipin o yöntemi taşıyıp taşımadığıdır.
- DT18 — “Değişebilir” sütunu tipin bilinen bir niteliğidir, ölçülmez; tabloda yalnız yöntem sütunları ölçümdür.
"""Yerlesik tipler ne yapabiliyor: tasidiklari ozel yontemlere bakilir.""" ORNEKLER = (("int", 12, False), ("float", 3.5, False), ("bool", True, False), ("str", "olcum", False), ("tuple", (1, 2), False), ("list", [1, 2], True), ("dict", {"kuzey": 1}, True), ("set", {1, 2}, True), ("NoneType", None, False)) PROTOKOL = ("__len__", "__iter__", "__contains__", "__getitem__", "__add__") print(f"{'tip':<9s} {'değişebilir':>11s} " + " ".join(f"{p.strip('_'):>10s}" for p in PROTOKOL)) for ad, ornek, degisebilir in ORNEKLER: tasidigi = " ".join(f"{'var' if hasattr(type(ornek), p) else '—':>10s}" for p in PROTOKOL) print(f"{ad:<9s} {str(degisebilir):>11s} {tasidigi}") print() print("bool bir int mi:", isinstance(True, int), "| True + True =", True + True) buyuk = 2 ** 100 print("tam sayının genişliği sabit değil:", buyuk.bit_length(), "bit;", "karesi", (buyuk * buyuk).bit_length(), "bit")
tip değişebilir len iter contains getitem add int False — — — — var float False — — — — var bool False — — — — var str False var var var var var tuple False var var var var var list True var var var var var dict True var var var var — set True var var var — — NoneType False — — — — — bool bir int mi: True | True + True = 2 tam sayının genişliği sabit değil: 101 bit; karesi 201 bit
Tablo tipleri adlarına göre değil, yeteneklerine göre öbekliyor. Üç sayısal tip yalnız
__add__ taşıyor: toplanabiliyorlar ama üzerlerinde döngü kurulamıyor, uzunlukları
sorulamıyor. str, tuple ve list beş sütunun beşini de dolduruyor; aralarındaki tek
fark ölçülen sütunlarda değil, değişebilirlik sütunundadır. dict ve set üstteki üç
protokolü taşıyıp __add__ taşımıyor — iki sözlüğün + ile birleştirilememesinin nedeni
bir yasak değil, o yöntemin tanımlı olmamasıdır. NoneType satırı ise boş: None
üzerinde bu biçimlerin hiçbiri çalışmaz ve ilk dersteki çıplak nesneyle aynı sonucu verir.
set satırındaki tek boşluk da öğreticidir. Küme __contains__ taşıyor ama __getitem__
taşımıyor: içinde bir öğenin bulunup bulunmadığı sorulabilir, sırayla kaçıncı olduğu
sorulamaz. Bu, kümenin “sırasız” olmasının kod düzeyindeki karşılığıdır.
Alt iki satır iki Python’a özgü noktayı kapatıyor. bool, int‘in bir alt tipidir — bu
yüzden True + True bir TypeError değil, 2 verir. Ve tam sayının sabit bir genişliği
yoktur: yüz bitlik bir değer 101 bitte durur, karesi 201 bite büyür. Bilgisayarlar
Nasıl Çalışır kursunda sabit genişlikli tipin taşma davranışı kurulmuştu; burada ölçülen
fark, Python’un tam sayısında böyle bir sınırın bulunmamasıdır. Sığmama diye bir durum
yoktur, yalnızca büyüyen bir gösterim vardır.
Bağın Kimliği
Aynı nesneye birden çok ad bağlanabilir ve is işleci bunu sorar. Bu ders kimlik
sayısını basmaz: sayı ortama bağlıdır, koşumdan koşuma değişir ve hiçbir programın
doğruluğu ona dayanmamalıdır. Sorulacak soru “hangi nesne” değil, “aynı nesne mi”dir ve
buna yalnız is yanıt verir.
Sorunun Python’a özgü karşılığı bileşik atamada görünür. x += ek tek bir yazımdır, ama iki
ayrı sonucu vardır: ya adı yeni bir nesneye bağlar, ya da bağlı olduğu nesneyi yerinde
değiştirir. Hangisinin olduğu nesneye ikinci bir ad takılarak ölçülebilir.
- DT19 — Her ölçümde değer bir ada bağlanır, aynı nesneye ikinci bir ad takılır ve
+=yalnız birinci ad üzerinde çalıştırılır.isile birinci adın hâlâ aynı nesnede olup olmadığına bakılır. - DT20 — Dört tip, üçü değiştirilemez biri değiştirilebilir olacak biçimde seçilmiştir; sonuç sütunu tipin bu niteliğini yansıtır, seçilen değerleri değil.
"""Ayni yazim, iki ayri sonuc: bag mi yeniden kuruldu, nesne mi degisti?""" def yerinde_mi(deger, ek): x = deger izci = x # ayni nesneye takilan ikinci ad x += ek return x is izci, izci print(f"{'tip':<9s} {'+= sonrası':<22s} ikinci adın gördüğü") for ad, deger, ek in (("tam sayı", 10, 1), ("dizgi", "ol", "cum"), ("demet", (1, 2), (3,)), ("liste", [1, 2], [3])): ayni, izci = yerinde_mi(deger, ek) print(f"{ad:<9s} {'nesne değişti' if ayni else 'ad yeniden bağlandı':<22s} {izci!r}")
tip += sonrası ikinci adın gördüğü tam sayı ad yeniden bağlandı 10 dizgi ad yeniden bağlandı 'ol' demet ad yeniden bağlandı (1, 2) liste nesne değişti [1, 2, 3]
Dört tipin üçünde ad yeniden bağlandı, birinde nesne değişti. Ayrımı yaratan şey
tipin değiştirilebilir olup olmamasıdır. Tam sayı, dizgi ve demet değiştirilemez
(immutable) tiplerdir: bir kez kurulan nesnenin içeriği hiçbir yolla değiştirilemez.
Böyle bir nesnede += yerinde bir işlem yapamaz; yeni bir nesne üretip adı ona bağlar
ve ikinci ad eski nesnede kalır. Sağdaki sütun bunu doğrudan gösteriyor — üç satırda ikinci
ad hâlâ başlangıç değerini görüyor.
Listede sonuç tersine döner. Nesnenin kendisi büyür; ada dokunulmaz, çünkü dokunmaya gerek yoktur. İkinci ad da aynı nesneye bağlı olduğundan büyümüş hâli görür. Aynı satırın iki tipte iki ayrı anlama gelmesi bir tutarsızlık değil, tek bir kuralın iki sonucudur: değiştirilemez bir nesne üzerinde yerinde işlem tanımlanamaz.
Bu, kursun ikinci iddiasının ilk belirtisidir. x = x + ek ile x += ek yazımları burada
aynı sonucu vermez ve fark tesadüfi değildir; iki yazım iki ayrı özel yöntemi çağırır.
Adları sonraki derste konacak.
Önbelleklenmiş Nesne
Geriye tek bir soru kalıyor: eşit iki değer aynı nesne midir? Yanıt, sorunun sanıldığından çok daha az güvence taşıdığıdır.
- DT21 — Karşılaştırılan değerler bir işlev çağrısı içinde, çalışma sırasında üretilir; böylece kaynakta yan yana duran iki sabitin derleme aşamasında birleştirilmesi ölçüme karışmaz.
- DT22 — “Güvence” sütunu, sonucun neye dayandığını söyler:
dil tanımısatırı her yorumlayıcıda aynıdır,gerçekleştirimsatırı değişebilir. - DT23 — Kimlik yalnız
isile sorulur; hiçbir kimlik sayısı basılmaz.
"""Onbelleklenmis nesne: sonucu GERCEKLESTIRIME BAGLIDIR.""" def uret(a, b): return a + b # deger calisma sirasinda uretilir def bosluk(): return None def dogru(): return 1 == 1 CIFTLER = ( ("küçük tam sayı", uret(2, 3), uret(1, 4)), ("büyük tam sayı", uret(500, 1), uret(499, 2)), ("kısa dizgi", uret("ol", "cum"), uret("olc", "um")), ("liste", uret([1], [2]), uret([1], [2])), ) print(f"{'çift':<16s} {'==':>6s} {'is':>6s} güvence") for ad, x, y in CIFTLER: guvence = "dil tanımı" if isinstance(x, list) else "gerçekleştirim" print(f"{ad:<16s} {str(x == y):>6s} {str(x is y):>6s} {guvence}") print("\ndil tanımının güvence verdiği tekil nesneler:") print(" başka yerden dönen None aynı nesne mi:", bosluk() is None) print(" başka yerden dönen doğruluk değeri aynı nesne mi:", dogru() is True)
çift == is güvence küçük tam sayı True True gerçekleştirim büyük tam sayı True False gerçekleştirim kısa dizgi True False gerçekleştirim liste True False dil tanımı dil tanımının güvence verdiği tekil nesneler: başka yerden dönen None aynı nesne mi: True başka yerden dönen doğruluk değeri aynı nesne mi: True
Dört çiftin dördünde de == True veriyor: değerler eşit. is sütunu ise dağılıyor.
Küçük tam sayıda iki ayrı hesap aynı nesneyi verdi; büyük tam sayıda ve kısa dizgide
vermedi.
Bu üç satır gerçekleştirime bağlıdır ve böyle okunmalıdır. Bir yorumlayıcı, sık
kullanılan bazı değiştirilemez nesneleri önceden kurup her istendiğinde aynısını verebilir;
buna önbelleklenmiş nesne (interning) denir. Hangi değerlerin bu kapsama girdiği dilin
tanımında yazmaz. Başka bir yorumlayıcıda — hatta aynı yorumlayıcının başka bir koşumunda —
bu üç satırın üçünün de True ya da üçünün de False çıkması dilin tanımını bozmaz. Tablo
bir davranışı açıklar, bir kural kurmaz.
Dördüncü satır ayrı bir sınıftandır ve güvencesi vardır: bir liste yazımı her değerlendirildiğinde yeni bir nesne kurar. Önbellekleme burada söz konusu bile değildir, çünkü nesne değiştirilebilirdir — paylaşılsaydı birinde yapılan değişiklik ötekinde görünürdü.
Buradan tek bir pratik kural çıkar ve istisnası yoktur: eşitlik == ile sorulur, is
yalnız kimlik sorusu içindir. İki değerin aynı olup olmadığını is ile sınayan bir kod,
küçük değerlerde çalışıp büyüklerde sessizce yanlış yanıt verir — hata iletisi vermeyen, en
zor bulunan kusur sınıfıdır.
Son iki satır kuralın meşru kullanımını gösteriyor. None dil tanımı gereği tek bir
nesnedir; nereden gelirse gelsin aynı nesnedir. Doğruluk değerleri için de aynısı geçerlidir.
Bu yüzden bir değerin boş olup olmadığı == None ile değil, is None ile sorulur:
burada gerçekten kimlik sorulmaktadır ve yanıtı dil güvence eder.
Özet
- Tip nesnenin özelliğidir, adın değil; tek bir ad 7 ayrı tipe ve bir işleve hiçbir bildirim olmadan bağlanabilir.
- Dinamik tipleme tipin yokluğu değil, tip denetiminin yürütme aşamasına ertelenmesidir;
karşılıksız bir işlem
TypeErrorile düşer. +=yazımı dört tipin 3’ünde adı yeni bir nesneye bağlar, 1’inde nesneyi yerinde değiştirir; ayrımı yapan şey tipin değiştirilebilir olup olmamasıdır.- Değiştirilemez bir nesnede yerinde işlem tanımlanamaz; bu yüzden ikinci bir ad eski değeri görmeye devam eder.
- Eşit iki değerin aynı nesne çıkması gerçekleştirime bağlıdır ve programın doğruluğu buna dayandırılamaz; liste yazımının her seferinde yeni nesne kurması ise dil tanımının güvencesidir.
- Eşitlik
==ile sorulur;isyalnız kimlik içindir —Nonesınaması bu yüzdenis Noneile yazılır.
Sonraki Adım
Bu derste x += ek yazımının tipe göre iki ayrı iş yaptığı ölçüldü ama yaptığı işin adı
konmadı. Sonraki ders o adı koyar ve kursun ikinci iddiasını öder: n + m ile n += m aynı
işlecin iki yazımı değil, iki ayrı özel yöntemin çağrısıdır. Aritmetik, karşılaştırma,
mantıksal ve üyelik işleçlerinin her biri hangi yöntemi çağırıyor, üyelik sınaması aradığı
yöntemi bulamadığında nereye düşüyor ve bir işleç hiçbir karşılık bulamadığında ne oluyor?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.