Ders 10 / 16
Kapsam Kuralları
Ad araması dört düzeyde yürür ve hangi düzeyde durduğunu adın okunduğu yer değil, atandığı yer belirler: gövdeye tek bir atama satırı eklemek aynı okumayı geçerli bir değerden UnboundLocalError'a çevirir.
İçindekiler
Önceki ders argümanların parametrelere nasıl bağlandığını ölçtü. Bağlanan her argüman gövdede bir ad kurar; ama gövde yalnız o adları kullanmaz. Kendi kurduğu adlar, çevreleyen tanımdan gelenler, dosyanın en dışında duranlar ve dilin kendi sağladıkları da oradadır — ve hepsi aynı yazımla, çıplak bir adla okunur.
Programlama Temelleri kursunun kapsam dersi bu arama sırasını dilden bağımsız bir kavram olarak kurdu: yerelden başlanır, çevreleyene ve küresele gidilir, yerleşiklerle biter, ilk bulunan kazanır. O ders kapsamı görünürlükle, yaşam süresini ömürle tanımladı ve gölgelemenin ne olduğunu anlattı. Burada aynı sıra bir arama zinciri olarak ölçülür ve ölçüm bir soruya odaklanır: bir adın hangi düzeyde aranacağını belirleyen şey nedir? Yanıt sezgiye aykırıdır — belirleyen şey adın okunduğu yer değil, gövdede bir yerde atanıp atanmadığıdır.
Dört Düzey
Python’da ad araması dört düzeyde yürür ve sırası sabittir:
- Yerel — içinde bulunulan fonksiyon gövdesi.
- Kapsayan — onu çevreleyen fonksiyon gövdeleri, içten dışa.
- Genel — dosyanın en dış düzeyi.
- Yerleşik — dilin kendi sağladığı adlar.
Arama ilk bulduğu yerde durur ve alttaki düzeylere hiç bakmaz. Dördü de bulamazsa sonuç
bir değer değil, NameError olur.
Dördüncü düzey öbürlerinden farklıdır: içindeki adları program yazmaz, dil koyar. Ama arama sırasındaki yeri özel değildir — en sonda olduğu için, üstteki üç düzeyden herhangi biri aynı adı tanımlarsa yerleşik olan gölgelenir ve o gövdede erişilemez olur.
Adı Yerel Yapan Şey
Asıl kural buradadır ve gövdenin tamamını ilgilendirir. Python bir gövdedeki adın hangi düzeye ait olduğuna çalışma sırasında değil, gövdeyi okurken karar verir. Kural tek cümledir: gövdede bir yerde ada atama yapılıyorsa, o ad o gövdenin tamamı için yereldir.
“Bir yerde” ifadesi harfiyen alınmalıdır. Atama gövdenin en son satırında olsa bile ad
gövdenin en başından itibaren yereldir. Bu yüzden atamadan önce yapılan bir okuma,
dıştaki değeri bulmaz — henüz değer almamış yerel adı bulur ve sonuç bir istisna olur:
UnboundLocalError.
Bu, gölgelemenin ölçülebilir hâlidir. Programlama Temelleri kursu gölgelemeyi “dıştaki ad erişilemez hâle gelir” diye tanımlamıştı; buradaki ölçüm erişilemezliğin nereden başladığını söylüyor — atama satırından değil, gövdenin ilk satırından.
Dışarı Yazmak
Kuralın doğrudan sonucu şudur: dıştaki bir ada atama yapmak çıplak yazımla olanaksızdır. Atama her zaman yerel bir ad kurar; dıştakini değiştirmez, onu gölgeler.
Dil bu yüzden iki bildirim tanır. global, adın genel düzeye ait olduğunu bildirir ve o
gövdedeki atamaları oraya yönlendirir. nonlocal, adın kapsayan bir gövdeye ait
olduğunu bildirir ve atamaları oraya yönlendirir; genel düzeye bakmaz, bulamazsa hata
verir.
Okumak için bildirim gerekmez. Bildirim yalnız yazma yönünü değiştirir; kural yazma kuralıdır, okuma kuralı değil. Bu asimetri kasıtlıdır: dıştaki bir değeri okumak o değeri kimse için değiştirmez, ama ona yazmak gövdenin dışında görülebilir bir sonuç bırakır. Bildirim zorunluluğu, o sonucun kazara doğmasını engeller — dışarı yazan her gövde bunu tek satırla ilan etmek zorundadır.
Ölçümün varsayımları:
- PA24 — Kâhin, her düzeyin o düzeyi adlandıran ayrı bir dizgi bağlamasıdır; dönen değer, aramanın hangi düzeyde durduğunu doğrudan söyler.
- PA25 — Aynı ad
SINIRdört durumda ölçülür ve durumlar yalnız hangi düzeylerin o adı tanımladığı bakımından ayrılır; gövdelerin okuma satırı birebir aynıdır. - PA26 — Yerleşik düzey ayrı bir adla ölçülür, çünkü o düzeydeki adları program
yazamaz. Ölçüm için dilin sağladığı
minadı kullanılır ve üstteki düzeylerde gölgelenir. - PA27 — Atama kuralı üç gövdeyle ölçülür: yalnız okuyan, okuyup sonra atayan, yalnız atayan. Üçünde de okuma satırı aynıdır; değişen tek şey gövdede bir atamanın bulunup bulunmadığı ve yeridir.
- PA28 — Yazma yönü üç durumda ölçülür: bildirimsiz,
nonlocalbildirimiyle veglobalbildirimiyle. Genel düzeydeki ad ölçümün sonunda değiştiği için o satır en sonda çalıştırılır. - PA29 — İstisnalar yalnız sınıf adıyla yazılır, iletileri yazılmaz.
- PA30 — Kapanış ölçümünde üç işlev bir döngüde üretilir ve döngü bittikten sonra çağrılır. Çağrının döngüden sonraya bırakılması kasıtlıdır; ölçülen şey adın ne zaman çözüldüğüdür, hangi değeri taşıdığı değil.
Ölçüm
"""Dort kapsam duzeyi: ad hangi duzeyde bulunur, atama onu nereye tasir.""" SINIR = "genel" def olc(islev): try: return islev() except Exception as e: return f"!{type(e).__name__}" def hicbiri(): return TANIMSIZ_AD def yalniz_genel(): return SINIR def kapsayan_da_var(): SINIR = "kapsayan" def ic(): return SINIR return ic() def yerel_de_var(): SINIR = "kapsayan" def ic(): SINIR = "yerel" return SINIR return ic() ARAMA = ( ("hiçbir düzeyde yok", hicbiri, "-"), ("yalnız genel", yalniz_genel, "genel"), ("genel + kapsayan", kapsayan_da_var, "genel, kapsayan"), ("genel + kapsayan + yerel", yerel_de_var, "genel, kapsayan, yerel"), ) print(f"{'adın tanımlı olduğu düzeyler':<26s} {'bulunan':<22s} sonuç") for ad, f, tanimli in ARAMA: print(f" {ad:<24s} {tanimli:<22s} {olc(f)}") def yerlesigi_kullanir(): return min([3, 1, 2]) def yerlesigi_kapsayan_golgeler(): min = "kapsayan gölgeledi" def ic(): return min return ic() def yerlesigi_yerel_golgeler(): def ic(): min = "yerel gölgeledi" return min return ic() print() print(f"{'yerleşik ad min':<30s} sonuç") for ad, f in (("hiçbir düzey tanımlamaz", yerlesigi_kullanir), ("kapsayan düzey tanımlar", yerlesigi_kapsayan_golgeler), ("yerel düzey tanımlar", yerlesigi_yerel_golgeler)): print(f" {ad:<28s} {olc(f)}") def yalniz_okur(): return SINIR def okur_sonra_atar(): once = SINIR SINIR = "yerel" return once def yalniz_atar(): SINIR = "yerel" return SINIR print() print(f"{'gövdede SINIR ile yapılan':<30s} {'gövdedeki okuma':<18s} sonuç") for ad, f, okuma in (("yalnız okunur", yalniz_okur, "atamadan önce yok"), ("okunur, sonra atanır", okur_sonra_atar, "atamadan önce"), ("yalnız atanır", yalniz_atar, "atamadan sonra")): print(f" {ad:<28s} {okuma:<18s} {olc(f)}") def genel_bildirir(): global SINIR SINIR = "içeriden yazıldı" def kapsayan_bildirir(): SINIR = "kapsayan" def ic(): nonlocal SINIR SINIR = "içeriden yazıldı" ic() return SINIR def bildirimsiz_yazar(): SINIR = "kapsayan" def ic(): SINIR = "içeriden yazıldı" ic() return SINIR print() print(f"{'gövdeden dışarı yazma':<26s} {'hedef düzey':<12s} dıştaki adın sonraki değeri") for ad, f, hedef in (("bildirim yok", bildirimsiz_yazar, "kapsayan"), ("nonlocal bildirimi", kapsayan_bildirir, "kapsayan")): print(f" {ad:<24s} {hedef:<12s} {olc(f)}") once = SINIR genel_bildirir() print(f" {'global bildirimi':<24s} {'genel':<12s} {once} -> {SINIR}") def gec_baglanan(): islevler = [] for i in range(3): def uret(): return i islevler.append(uret) return [f() for f in islevler] def varsayilanla_yakalanan(): islevler = [] for i in range(3): def uret(i=i): return i islevler.append(uret) return [f() for f in islevler] def dongu_sonrasi_ad(): for i in range(3): pass return i print() print(f"{'döngüde üretilen üç işlev':<30s} çağrıldıklarında dönen") print(f" {'gövdede çıplak ad':<28s} {gec_baglanan()}") print(f" {'varsayılanla yakalanmış ad':<28s} {varsayilanla_yakalanan()}") print(f" {'döngü bittikten sonra ad':<28s} {dongu_sonrasi_ad()}")
adın tanımlı olduğu düzeyler bulunan sonuç hiçbir düzeyde yok - !NameError yalnız genel genel genel genel + kapsayan genel, kapsayan kapsayan genel + kapsayan + yerel genel, kapsayan, yerel yerel yerleşik ad min sonuç hiçbir düzey tanımlamaz 1 kapsayan düzey tanımlar kapsayan gölgeledi yerel düzey tanımlar yerel gölgeledi gövdede SINIR ile yapılan gövdedeki okuma sonuç yalnız okunur atamadan önce yok genel okunur, sonra atanır atamadan önce !UnboundLocalError yalnız atanır atamadan sonra yerel gövdeden dışarı yazma hedef düzey dıştaki adın sonraki değeri bildirim yok kapsayan kapsayan nonlocal bildirimi kapsayan içeriden yazıldı global bildirimi genel genel -> içeriden yazıldı döngüde üretilen üç işlev çağrıldıklarında dönen gövdede çıplak ad [2, 2, 2] varsayılanla yakalanmış ad [0, 1, 2] döngü bittikten sonra ad 2
Zincirin Dört Basamağı
Üst tablo arama zincirini basamak basamak kuruyor. Dört satırın okuma satırı birebir aynı; değişen tek şey hangi düzeylerin o adı tanımladığı.
Hiçbir düzeyde tanımlı olmadığında sonuç NameError. İkinci satırda ad yalnız genel
düzeyde var ve bulunan o. Üçüncü satırda kapsayan düzey de aynı adı tanımlıyor ve
kapsayan kazanıyor — genel düzeydeki ad hâlâ orada duruyor ama aramaya hiç
gelmiyor. Dördüncüde yerel düzey de ekleniyor ve bu kez yerel kazanıyor.
Örüntü şudur: her yeni düzey, kendinden dıştaki her şeyi kapatıyor. Arama dıştan içe değil, içten dışa yürüyor ve ilk bulduğunda duruyor. Üç düzey aynı adı tanımladığında üçü yarışmıyor; en içteki öbür ikisini sınamaya bile sokmuyor.
Orta tablo dördüncü düzeyi ekliyor. İlk satırda hiçbir düzey min adını tanımlamıyor ve
arama yerleşik düzeye kadar inip dilin sağladığı adı buluyor; sonuç, üç öğelik listenin
en küçüğü olan 1. Sonraki iki satırda aynı ad önce kapsayan, sonra yerel düzeyde
tanımlanıyor ve ikisinde de yerleşik olan gölgeleniyor. Yerleşik düzeyin arama sırasında
ayrıcalığı yok; en sonda olduğu için her zaman en kolay gölgelenen düzey.
Bunun pratik sonucu, bir yerleşiğin adını yerel bir değişkene vermenin o gövdede o yerleşiği kullanılamaz kılmasıdır. Hata çıkmaz, uyarı çıkmaz; ad artık başka bir şeyi gösterir.
Tek Bir Atama Satırı
Üçüncü tablo dersin ana ölçümüdür ve üç satırın okuma satırı yine aynıdır.
Birinci satır: gövde SINIR adını yalnız okuyor, hiçbir yerde atamıyor. Arama zinciri
yürüyor, genel düzeyde bulunuyor, sonuç genel.
İkinci satır: gövde önce aynı okumayı yapıyor, sonra ada bir atama yapıyor. Okuma
satırı atamadan öncedir ve birinci satırdakiyle karakter karakter aynıdır. Ama sonuç
artık bir değer değil, UnboundLocalError.
Aradaki tek fark, gövdenin ilerideki bir satırında bir atamanın bulunmasıdır. O atama adı gövdenin tamamı için yerel yapıyor; okuma satırına gelindiğinde arama artık genel düzeye hiç çıkmıyor, yerel düzeyde duruyor ve orada henüz değer almamış bir ad buluyor. Zincir kırılmıyor — hiç kurulmuyor.
Buradaki sıra ilişkisi kritiktir: adın yerel olmasına karar veren şey çalışma sırası değil, gövdenin tümüne bakılarak verilen bir karardır. Bu yüzden hatayı üreten satır, hatanın nedenini içermez; neden aşağıdaki atama satırıdır. Üçüncü satır bunu tamamlıyor — gövde yalnız atayıp sonra okuduğunda sorun yok, sonuç yerel.
Yazmanın Üç Yolu
Alt tablo yazma yönünü ölçüyor. Birinci satırda iç gövde bildirimsiz bir atama yapıyor ve
dıştaki ad değişmiyor: sonuç hâlâ kapsayan. Atama dıştakini güncellemedi, iç gövdeye
ait yeni bir ad kurdu ve o ad gövde bitince ortadan kalktı. Yazılan satır bir güncelleme
gibi görünüyor; yaptığı iş bir gölgeleme.
İkinci satırda aynı gövde nonlocal bildirimiyle yazıyor ve dıştaki ad değişiyor.
Üçüncü satırda global bildirimi genel düzeydeki adı değiştiriyor; ölçümden önce genel
olan değer sonrasında içeriden yazıldı oluyor.
Üç satırın söylediği şudur: gövdeden dışarı yazmak bildirimle olur, çıplak atamayla
olmaz. Bildirim yeni bir yetenek eklemiyor; yalnız atamanın hangi düzeyi hedeflediğini
söylüyor. Bildirim olmadığında hedef her zaman yereldir ve bu, önceki tablodaki
UnboundLocalError ile aynı kuralın öbür yüzüdür — biri okumada, öbürü yazmada görünür.
Bu ayrım, dıştaki bir nesneyi değiştirmekle dıştaki adı yeniden bağlamak arasındaki farkı da açıklar. Önceki dersteki paylaşılan kova hiçbir bildirim olmadan büyüyordu, çünkü orada yapılan şey ada atama değil, adın gösterdiği nesneyi değiştirmekti. Kapsam kuralları adların bağlanmasını yönetir; nesnelerin içeriğini değil.
Adın Ne Zaman Çözüldüğü
Son tablo zincirin ne zaman yürüdüğünü ölçüyor ve buraya kadarki her şeyi tek bir sonuçta topluyor. Bir döngüde üç işlev üretiliyor; her biri gövdesinde döngü değişkenini çıplak bir adla okuyor. Döngü bittikten sonra üçü de çağrılıyor.
Beklenen sonuç [0, 1, 2], ölçülen sonuç [2, 2, 2]. Üç işlev de aynı değeri
döndürüyor.
Nedeni kapsam kuralının kendisidir. İşlevlerin gövdesindeki ad, işlev üretildiğinde çözülmüyor; çağrıldığında çözülüyor. Üretim sırasında gövdeye yazılan şey bir değer değil, bir addır — ve o ad kapsayan gövdedeki tek bir değişkeni gösterir. Üç işlev üç ayrı değeri değil, aynı adı taşıyor. Çağrıldıklarında arama zinciri yürüyor, kapsayan düzeydeki adı buluyor ve orada döngünün bıraktığı son değer duruyor.
Üçüncü satır o son değerin ne olduğunu gösteriyor: döngü bittikten sonra da ad yaşıyor ve değeri 2. Python’da döngü kendi kapsamını açmaz; döngü değişkeni onu çevreleyen gövdeye aittir ve döngü bitince ortadan kalkmaz. Programlama Temelleri kursu döngü değişkeninin yerel olduğunu söylemişti ve bu doğrudur — ölçüm onu daraltıyor: yerel olduğu birim döngü değil, fonksiyon gövdesinin tamamıdır. Süslü parantezle ya da anahtar sözcükle kapatılan blokların kendi kapsamını açtığı dillerde bu satır bir hata verirdi; burada bir değer veriyor.
İkinci satır çözümü veriyor ve çözüm önceki dersin kuralıdır. Ad varsayılan değer olarak
yazıldığında sonuç [0, 1, 2] oluyor. Varsayılan ifadesi tanım okunurken bir kez
değerlendirilir; tanım burada döngü gövdesinde olduğu için her turda yeniden çalışıyor ve
o turdaki değeri yakalıyor. Değer, arama zincirine bırakılmak yerine tanım anında
kopyalanmış oluyor.
İki dersin kuralı burada birleşiyor: kapsam adın nerede aranacağını söylüyor, varsayılanın değerlendirilme zamanı ise ne zaman aranacağını değiştiriyor. Aynı üç satırlık döngü, tek bir imza değişikliğiyle iki ayrı sonuç veriyor.
Özet
- Ad araması dört düzeyde ve sabit bir sırada yürür — yerel, kapsayan, genel, yerleşik —
ilk bulduğunda durur ve dördü de bulamazsa
NameErrorverir. - Her düzey kendinden dıştakileri kapatır: aynı ad üç düzeyde tanımlıysa en içteki kazanır ve öbürleri aramaya hiç girmez.
- Yerleşik düzeyin arama sırasında ayrıcalığı yoktur; en sonda olduğu için üstteki herhangi bir düzeyde aynı adın tanımlanması yerleşiği o gövdede erişilemez kılar.
- Bir adın yerel olmasına karar veren şey okumanın yeri değil, gövdede bir yerde ona
atama yapılmasıdır; atama en sonda olsa bile ad gövdenin başından itibaren yereldir ve
atamadan önceki okuma
UnboundLocalErrorüretir. - Gövdeden dışarı yazmak bildirimle olur:
nonlocalatamayı kapsayan düzeye,globalgenel düzeye yönlendirir. Bildirimsiz atama dıştakini güncellemez, onu gölgeleyen yeni bir yerel ad kurar. - Gövdedeki bir ad, işlev üretildiğinde değil çağrıldığında çözülür: döngüde üretilen
üç işlev aynı adı paylaştığı için üçü de
[2, 2, 2]döndürür; adı varsayılan değer olarak yakalamak sonucu[0, 1, 2]yapar.
Sonraki Adım
Arama zincirinin son basamağındaki adlar buraya kadar yalnız gölgelenecek bir şey olarak göründü. Oysa o düzeyde duran adlar dilin en çok kullanılan işlevleridir ve her biri, ilk iki dersin ölçtüğü protokollere doğrudan bağlanır: uzunluk soran, metne çeviren, yineleyici isteyen, doğruluk sınayan, sıralayan. Sonraki ders bu yerleşikleri sözleşmeleriyle ölçer — her birinin hangi özel yöntemi çağırdığını ve o yöntem tanımlı değilse ne olduğunu. Ortaya çıkan şey, yerleşiklerin iş yapan işlevler değil, protokol çağıran ince kabuklar olduğudur.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.