İçeriğe geç
academia.sh

Ders 13 / 16

Modül Sistemi

İçe aktarma bir var etme işidir: aynı modül üç kez istendiğinde iki nesne var edilir, gövde önbellek doluyken bir kez koşar; arama yolunun sırası ancak önbellek boşken karar verir.

İçindekiler

Buraya kadar her şey tek bir dosyanın içindeydi. Kavramalar, üreteçler, sarmalayıcılar ve dekoratörler aynı ad alanında durdu; bir ad tanımlandığı yerden görünüyordu ve görünmediğinde nedeni kapsamdı. Kod birden çok dosyaya yayıldığında bu sorunun biçimi değişir: adlar artık nereye yazılır ve bir dosya öbürünün adını hangi işlemle alır?

Yanıt bu kursun ölçü ekseniyle verilir. İçe aktarma bir ad kopyalama işlemi değil, bir var etme işidir: bir dosya ilk kez istendiğinde ondan bir modül (module) nesnesi var edilir, gövdesi bir kez koşar ve bütün modül düzeyindeki adlar o nesnenin sözlüğüne yazılır. İkinci istek yeni bir nesne var etmez. Bu dersin sorusu şudur: aynı modül birden çok kez istendiğinde kaç nesne var edilir, gövde kaç kez koşar ve hangi kayıt ikinci koşumu engeller?

İçe Aktarma Neyi Var Eder

Bir içe aktarma isteği üç iş yapar. Önce adın modül önbelleğinde (module cache) kayıtlı olup olmadığına bakılır; kayıtlıysa oradaki nesne verilir ve iş biter. Kayıtlı değilse kaynak arama yolunda (search path) aranır, bulunan kaynaktan bir modül nesnesi var edilir, o nesne önce önbelleğe yazılır, sonra gövdesi koşturulur. Son adımda istekte geçen ad, isteği yapan tarafta modül nesnesine bağlanır.

Sıralamanın ayrıntısı önemlidir: nesne önbelleğe gövde koşmadan önce yazılır. Bu yüzden bir modülün gövdesi kendisini dolaylı olarak isteyen başka bir modülü içe aktarsa bile sonsuz döngü doğmaz — ikinci istek, gövdesi henüz bitmemiş ama nesnesi var edilmiş modülü önbellekte bulur.

Bunun doğrudan bir sonucu var: içe aktarma ucuz değildir, bir kez pahalıdır. Gövdede ne varsa — sınıf tanımları, modül düzeyindeki hesaplar, tablolar — hepsi ilk istekte bir kez koşar ve sonuçları modül nesnesinde tutulur. Sonraki istekler yalnızca bir sözlük araması kadar iş yapar.

İki Yazım, Aynı Nesne

İçe aktarmanın iki yazımı vardır ve ikisi de aynı modül nesnesini var eder; ayrıldıkları yer, isteği yapan tarafta hangi adın neye bağlandığıdır. Birinci yazım modül nesnesinin kendisini bir ada bağlar; ikinci yazım modülün sözlüğünden tek bir değeri okur ve o değeri yerel bir ada bağlar. İkincisi yeni nesne var etmez — var edilmiş bir nesneye ikinci bir ad verir.

Ayrımın gözlenebilir sonucu şudur: iki ad nesneyi paylaşır, bağı paylaşmaz. Modülün sözlüğündeki ad sonradan başka bir nesneye bağlanırsa, dışarıda alınmış ad eski nesnede kalır — çünkü o ad modülün sözlüğüne değil, kendi sözlüğüne yazılmıştır. Buna karşılık paylaşılan nesne yerinde değiştirilirse, iki taraf da aynı değişikliği görür. Ölçümün son bölümü bu iki durumu ayrı ayrı sınıyor.

Arama Yolu Bir Listedir

Önbellekte bulunmayan bir ad için sıra arama yoluna gelir. Arama yolu bir dizin adları listesidir ve sıralıdır: ilk eşleşen kaynak kazanır, geri kalanına bakılmaz. İki ayrı dizinde aynı adı taşıyan iki kaynak varsa, hangisinin içe aktarılacağını dosyaların içeriği değil listedeki sıra belirler.

Sıranın pratik sonucu, arama yolunun baş tarafındaki bir dizinde standart kitaplıkla aynı adı taşıyan bir dosya bulunduğunda ortaya çıkar: istek o dosyayı bulur ve standart kitaplığın modülü hiç aranmaz. Ortada bir hata iletisi yoktur, çünkü kural doğru işlemiştir — ilk eşleşen kazanmıştır. Ad çakışmasının belirtisi, beklenen adın modülde bulunamamasıdır; nedeni ise adın başka bir kaynaktan gelmiş olmasıdır.

Bu iki düzenek — önbellek ve arama yolu — birlikte çalışırken hangisinin önce söz sahibi olduğu ölçülebilir bir sorudur. Arama yolunun sırasını bir modül içe aktarıldıktan sonra değiştirirsek, sonraki istek yeni sıraya mı uyar yoksa önbellekteki eski nesneyi mi verir? Ölçüm bunu yanıtlıyor.

Modül Düzeyindeki Ad Nereye Yazılır

Kapsam kuralları bu kursun konusu değil; dört düzey — yerel, kapsayan, genel, yerleşik — Python Temelleri kursunun kapsam dersinde kuruldu ve burada tekrarlanmaz. Buraya eklenen tek şey şudur: o dört düzeyden genel olanı soyut bir alan değil, somut bir nesnenin sözlüğüdür. Modül düzeyinde bağlanan her ad, o modül nesnesinin __dict__ sözlüğüne yazılır; vars yerleşiği aynı sözlüğü verir. Bir modüle dışarıdan öznitelik atamak da aynı sözlüğe yazmaktır.

Bu, “genel değişken” deyiminin neden yanıltıcı olduğunu açıklar. Genel olan hiçbir şey yoktur; modüle ait olan vardır. İki modülün aynı adı taşıyan iki değişkeni birbirinden bağımsızdır, çünkü iki ayrı sözlükte dururlar.

Ölçümün varsayımları:

  • MP1 — Ölçüm depoya dosya bırakmaz. Modül kaynakları geçici bir dizinde üretilir, iş bitince dizin silinir; dizinin yolu basılmaz.
  • MP2 — Standart kitaplıktan yapılan içe aktarma gerçek bir içe aktarmadır; kâhin is karşılaştırmasıdır ve kimlik sayısı basılmaz.
  • MP3 — Gövdenin kaç kez koştuğu, gövdenin kendisinin ortak bir listeye eklediği kayıtla sayılır. Sayan taraf ölçülen modülün kendisidir.
  • MP4 — Var edilen modül nesnesi sayısı, görülen nesneler bir listede toplanıp is ile birbirinden ayrılarak bulunur.
  • MP5 — Arama yolu ölçümünde iki ayrı dizin aynı adı taşıyan iki kaynak içerir; ikisi yalnız modül düzeyindeki bir adın değeriyle ayrılır.
  • MP6 — Sıra değiştirildikten sonra önbelleğe iki kez bakılır: bir kez dolu, bir kez boşaltılmış.
  • MP7 — Ölçüm bittiğinde eklenen yol girdileri ve önbellek kayıtları geri alınır; yorumlayıcının başlangıç durumu ölçüme karışmaz.
  • MP8 — Adın nereye yazıldığı vars ile okunur; vars ile __dict__ sonucunun aynı nesne olduğu da is ile sınanır.
  • MP9 — İki yazımın karşılaştırmasında ikinci yazım ayrı bir ad alanı sözlüğünde koşturulur; ölçüm ana koşumun kendi adlarına dokunmaz.
  • MP10 — Paylaşımın sınanması iki adımlıdır: önce nesne yerinde değiştirilir, sonra modüldeki ad başka bir nesneye bağlanır. İki adım ayrı ayrı okunur.

Ölçüm

"""Modul sistemi: ice aktarma kac nesne var eder, govde kac kez calisir."""

import importlib
import sys
import tempfile
from pathlib import Path

KAYIT_KAYNAK = "GOVDE = []\n"
OLCUM_KAYNAK = ("import kayit\n"
                "kayit.GOVDE.append('kosum')\n"
                "ETIKET = 'olcum'\n")
SECIM_KAYNAK = "KAYNAK = '{ad}'\n"
ADLAR = ("kayit", "olcum", "secim")


def farkli_nesne(nesneler):
    """Kimlik basmadan, kac ayri nesne goruldugunu sayar."""
    farkli = []
    for n in nesneler:
        if not any(n is f for f in farkli):
            farkli.append(n)
    return len(farkli)


def onbellegi_bosalt():
    for ad in ADLAR:
        sys.modules.pop(ad, None)


def iki_kez_ice_aktar():
    import json as birinci
    import json as ikinci
    return birinci is ikinci, "json" in sys.modules


def govde_kac_kez(kok):
    (kok / "kayit.py").write_text(KAYIT_KAYNAK, encoding="utf-8")
    (kok / "olcum.py").write_text(OLCUM_KAYNAK, encoding="utf-8")
    sys.path.insert(0, str(kok))
    importlib.invalidate_caches()
    gorulen = [importlib.import_module("olcum")]
    gorulen.append(importlib.import_module("olcum"))
    kayit = sys.modules["kayit"]
    onbellekli = len(kayit.GOVDE)
    sys.modules.pop("olcum")
    gorulen.append(importlib.import_module("olcum"))
    sonuc = (len(gorulen), farkli_nesne(gorulen), onbellekli,
             len(kayit.GOVDE), gorulen[-1].kayit is kayit)
    sys.path.remove(str(kok))
    onbellegi_bosalt()
    return sonuc


def arama_yolu(kok):
    for ad in ("bir", "iki"):
        (kok / ad).mkdir()
        (kok / ad / "secim.py").write_text(SECIM_KAYNAK.format(ad=ad),
                                           encoding="utf-8")
    bir, iki = str(kok / "bir"), str(kok / "iki")
    sys.path[:0] = [bir, iki]
    importlib.invalidate_caches()
    once = importlib.import_module("secim").KAYNAK
    sys.path.remove(bir)
    sys.path.insert(1, bir)
    onbellekle = importlib.import_module("secim").KAYNAK
    sys.modules.pop("secim")
    onbelleksiz = importlib.import_module("secim").KAYNAK
    sys.path.remove(bir)
    sys.path.remove(iki)
    onbellegi_bosalt()
    return once, onbellekle, onbelleksiz


def modul_duzeyindeki_ad(kok):
    (kok / "secim.py").write_text(SECIM_KAYNAK.format(ad="bir"),
                                  encoding="utf-8")
    sys.path.insert(0, str(kok))
    importlib.invalidate_caches()
    m = importlib.import_module("secim")
    ayni_sozluk = vars(m) is m.__dict__
    icinde = "KAYNAK" in vars(m)
    m.SONRADAN = "eklendi"
    disaridan = vars(m)["SONRADAN"] == "eklendi"
    sys.path.remove(str(kok))
    onbellegi_bosalt()
    return ayni_sozluk, icinde, disaridan


def iki_yazim(kok):
    (kok / "secim.py").write_text("KAYNAK = ['bir']\n", encoding="utf-8")
    sys.path.insert(0, str(kok))
    importlib.invalidate_caches()
    m = importlib.import_module("secim")
    ad_alani = {}
    exec("from secim import KAYNAK", ad_alani)
    paylasiyor = ad_alani["KAYNAK"] is m.KAYNAK
    m.KAYNAK.append("degisti")
    degisimi_gordu = "degisti" in ad_alani["KAYNAK"]
    m.KAYNAK = ["yeni"]
    yeniden_baglamayi_gordu = ad_alani["KAYNAK"] is m.KAYNAK
    sys.path.remove(str(kok))
    onbellegi_bosalt()
    return paylasiyor, degisimi_gordu, yeniden_baglamayi_gordu


ayni, kayitli = iki_kez_ice_aktar()
print(f"iki kez içe aktarılan modül aynı nesne -> {ayni}, "
      f"önbellekte kayıtlı -> {kayitli}")

with tempfile.TemporaryDirectory() as d:
    istek, nesne, onbellekli, temizden, paylasilan = govde_kac_kez(Path(d))
print()
print(f"içe aktarma isteği {istek} | var edilen modül nesnesi {nesne} | "
      f"gövde koşumu önbellekliyken {onbellekli}, önbellek silinince {temizden}")
print(f"iki modülün gördüğü kayıt modülü aynı nesne -> {paylasilan}")

with tempfile.TemporaryDirectory() as d:
    once, onbellekle, onbelleksiz = arama_yolu(Path(d))
print()
print(f"arama yolu sırası bir-iki -> {once}")
print(f"sıra iki-bir, önbellek dolu -> {onbellekle}")
print(f"sıra iki-bir, önbellek silinmiş -> {onbelleksiz}")

with tempfile.TemporaryDirectory() as d:
    ayni_sozluk, icinde, disaridan = modul_duzeyindeki_ad(Path(d))
print()
print(f"vars(m) is m.__dict__ -> {ayni_sozluk} | modül düzeyindeki ad "
      f"sözlükte -> {icinde} | dışarıdan yazılan ad aynı sözlükte -> {disaridan}")

with tempfile.TemporaryDirectory() as d:
    paylasiyor, degisimi, yeniden = iki_yazim(Path(d))
print()
print(f"içeri alınan ad modüldekiyle aynı nesne -> {paylasiyor} | "
      f"nesnenin değişimini gördü -> {degisimi} | "
      f"yeniden bağlamayı gördü -> {yeniden}")
iki kez içe aktarılan modül aynı nesne -> True, önbellekte kayıtlı -> True

içe aktarma isteği 3 | var edilen modül nesnesi 2 | gövde koşumu önbellekliyken 1, önbellek silinince 2
iki modülün gördüğü kayıt modülü aynı nesne -> True

arama yolu sırası bir-iki -> bir
sıra iki-bir, önbellek dolu -> bir
sıra iki-bir, önbellek silinmiş -> iki

vars(m) is m.__dict__ -> True | modül düzeyindeki ad sözlükte -> True | dışarıdan yazılan ad aynı sözlükte -> True

içeri alınan ad modüldekiyle aynı nesne -> True | nesnenin değişimini gördü -> True | yeniden bağlamayı gördü -> False

Üç İstek, İki Nesne, İki Koşum

İlk satır ortak ölçümün beşinci okumasıdır: aynı modül iki kez istendiğinde iki ad aynı nesneye bağlanır ve o nesne önbellekte kayıtlıdır. Var edilen modül nesnesi 1, tutulan 1, iki adın paylaştığı 1.

İkinci bölüm bunun bedelini gösteriyor. Üç içe aktarma isteği yapıldı; var edilen modül nesnesi 2 oldu. İlk iki istek arasında önbellek dolu olduğu için gövde koşumu 1’de kaldı — ikinci istek yeni nesne var etmedi, gövdeyi yeniden koşturmadı, yalnız önbellekteki nesneyi verdi. Üçüncü istekten önce kayıt önbellekten silindi; o istek kaynağı yeniden buldu, yeni bir modül nesnesi var etti ve gövdeyi ikinci kez koşturdu. Gövde koşumu 2’ye çıktı.

Sayılan üç şey burada birbirinden ayrılıyor. Var edilen modül nesnesi 2, tutulan — yani ölçümün sonunda önbellekte kayıtlı olan — 1, ve paylaşılan olan kayit modülü. Son satır bunu söylüyor: ölçüm modülünün gövdesinde içe aktarılan kayit ile ana koşumun önbellekten aldığı kayit aynı nesnedir. Ölçüm modülü iki ayrı kez var edildi ama ikisinin de gördüğü kayıt tektir, çünkü onun kaydı hiç silinmedi.

Buradan çıkan kural şudur: modül nesnesinin sayısı dosya sayısına değil, önbellek kaydının kaç kez düştüğüne bağlıdır. Aynı dosyadan iki modül nesnesi var edilebilir ve o iki nesnenin sözlükleri birbirinden bağımsızdır — birinde tanımlanan sınıf, öbüründe tanımlanan sınıftan ayrı bir nesnedir. İki nesne aynı kaynaktan üretildiği için aynı adları taşır; taşıdıkları adların bağlandığı nesneler ise ortak değildir. Bir kimlik sınaması bu durumda beklenmedik bir sonuç verir: aynı dosyadan gelmiş iki sınıf, aynı ada sahip olduğu hâlde birbirinin yerine geçmez.

Sıra mı, Önbellek mi

Üçüncü bölüm iki düzeneği karşı karşıya getiriyor. Arama yolunun başında bir dizini varken içe aktarma bir veriyor, beklendiği gibi. Sonra sıra değiştirildi: iki başa alındı. Önbellek dolu olduğu için sonuç değişmedi — hâlâ bir. Önbellek kaydı silindikten sonra aynı istek iki verdi.

Bu, önbelleğin arama yolundan önce geldiğinin doğrudan kanıtıdır. Arama yolu yalnız önbellekte kayıt yokken karar verir; kayıt varsa yol hiç okunmaz. Yolu çalışma sırasında değiştirmenin, o ana kadar içe aktarılmış hiçbir modüle etkisi olmaz.

Dördüncü bölüm adın nereye yazıldığını kapatıyor. vars ile __dict__ aynı sözlüğü veriyor, modül düzeyinde bağlanan ad orada duruyor, ve modüle dışarıdan atanan öznitelik de aynı sözlüğe yazılıyor. Modülün ad alanı ile modülün özniteliklerinin ayrı iki şey olmadığı buradan görünür: dosyanın içinde KAYNAK = "bir" yazmakla, dışarıdan m.KAYNAK = "bir" yazmak aynı sözlükte aynı anahtarı bağlar.

Nesne Paylaşılır, Bağ Paylaşılmaz

Son bölüm iki yazımın farkını üç değerle veriyor. İçeri alınan ad ile modüldeki ad aynı nesneye bağlı: paylaşılan nesne 1, var edilen yeni nesne 0. İkinci yazım hiçbir şey var etmedi, var edilmiş bir nesneye ikinci bir ad verdi.

İkinci değer paylaşımın canlı olduğunu gösteriyor. Modüldeki nesne yerinde değiştirilince içeri alınan ad değişikliği gördü — aynı nesneye baktıkları için başka türlüsü olamazdı. Üçüncü değer sınırı çiziyor: modüldeki ad başka bir nesneye yeniden bağlanınca içeri alınan ad bunu görmedi. O ad artık eski nesnede duruyor ve modülün sözlüğüyle bağı yok.

Ayrımı bir cümleye indirgemek gerekirse: içe aktarma nesneyi paylaşır, adı kopyalar. Bir modülün sonradan değişecek bir adını izlemek gerekiyorsa modülün kendisi içeri alınır ve erişim her seferinde modül üzerinden yapılır; değişmeyecek bir nesne için ikinci yazım fazladan bir arama kadar iş kazandırır. İki durum arasındaki fark bir üslup tercihi değil, ölçülen bir davranış farkıdır.

Özet

  • İçe aktarma bir ad kopyalama değil var etme işidir: kaynak bir kez okunur, ondan bir modül nesnesi var edilir ve gövde bir kez koşar.
  • Sıra önbellek, arama yolu, gövde biçimindedir; nesne önbelleğe gövde koşmadan önce yazılır ve iki kez içe aktarılan modül aynı nesnedir.
  • Üç istek, önbellek bir kez silindiğinde 2 modül nesnesi var eder ve gövdeyi 2 kez koşturur; önbellek bozulmasaydı ikisi de 1 kalırdı.
  • Arama yolunun sırası ancak önbellekte kayıt yokken karar verir: sıra değiştirilip önbellek dolu bırakıldığında sonuç değişmez, kayıt silinince değişir.
  • Modül düzeyindeki her ad, modül nesnesinin __dict__ sözlüğüne yazılır; vars aynı sözlüğü verir ve dışarıdan atanan öznitelik de oraya gider.
  • Modülden tek bir ad almak 0 yeni nesne var eder ve nesneyi paylaşır; nesnenin yerinde değişimi görünür, modüldeki adın yeniden bağlanması görünmez.

Sonraki Adım

Ölçümde modüller düz durdu: hepsi arama yolundaki bir dizinin doğrudan içindeydi ve adları tek parçaydı. Kod büyüdüğünde adlar noktayla bölünür ve dizinler iç içe girer. O zaman aynı kısa ad birden çok yerde bulunabilir — bir üst dizinde bir tane, bir alt dizinde bir tane daha. Sonraki ders bu durumu ölçüyor: aynı kısa ad, biri komşuluğa biri kökten yazılmış iki istekle çağrıldığında hangi nesneye çözülür ve iç içe dizinlerin kaç gövdesi koşar?

İ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