Ders 14 / 16
Paket Yapısı
Aynı kısa ad göreli yazımda komşuya, mutlak yazımda köke çözülür ve iki ayrı nesne verir; tek bir alt modül isteği beş gövde koşturur ve beş modül nesnesi önbelleğe yazar.
İçindekiler
Önceki ders modülleri düz bir listede aradı: her kaynak, arama yolundaki bir dizinin doğrudan içindeydi ve adları tek parçaydı. O düzende iki dosyanın aynı adı taşıması bir çakışmaydı ve kazananı yalnızca sıra belirliyordu. Kod büyüdüğünde bu düzen yetmez; aynı işi yapan dosyalar birbirine yakın durmak, aynı kısa adı farklı bağlamlarda taşımak ister.
Paket (package), dizinlerin ad alanına katıldığı düzendir: bir dizin ve içindeki
__init__.py dosyası birlikte bir modül nesnesi verir, ve o dizinin içindekiler noktayla
ona bağlanır. Bu, adı iki parçalı yapar — bir yolu ve bir kısa adı olur. Dersin sorusu şudur:
aynı kısa ad iki ayrı düzeyde varken, göreli ve mutlak yazım hangi nesneye çözülür,
ve tek bir istek kaç gövde koşturur?
Dizin Nasıl Modül Olur
Bir paket, içinde __init__.py bulunan dizindir. O dosya paketin gövdesidir ve paket ilk
kez istendiğinde bir kez koşar; boş olması da bir seçenektir, o zaman gövde hiçbir ad
bağlamaz ama modül nesnesi yine var edilir. Paketin içindeki dosyalar ve alt dizinler onun
alt modülleri ve alt paketleridir.
Noktalı bir ad soldan sağa çözülür ve her düzey ayrı bir modül nesnesidir. paket.alt.arac
isteği tek bir nesne değil, üç nesne gerektirir: paket, paket.alt ve paket.alt.arac.
Üçü de ayrı ayrı önbelleğe yazılır, üçünün gövdesi de sırayla koşar. Bu, önceki dersin
kuralının doğrudan sonucudur — her ad kendi kaydını alır.
Çözülen her düzey, bir üstünde öznitelik olarak da bağlanır. paket.alt içe
aktarıldıktan sonra paket modül nesnesinin sözlüğünde alt adı vardır ve o ad,
önbellekteki paket.alt nesnesinin aynısına bağlıdır. Noktalı erişimin çalışma biçimi
budur: nokta bir arama yolu araması değil, bir öznitelik okumasıdır.
İki Yazım, İki Başlangıç Noktası
Aynı alt modüle iki yoldan ulaşılır. Mutlak yazım adı kökten başlatır: adın ilk parçası arama yolunda aranır ve zincir oradan çözülür. Göreli içe aktarım (relative import) ise adı isteği yapan modülün paketinden başlatır; baştaki nokta “bulunduğum paket”, iki nokta “bir üstteki paket” demektir.
İkisi aynı harfleri taşıyan iki ayrı adres üretir. Bir paketin kökünde arac adlı bir modül,
alt paketinde de arac adlı başka bir modül varsa, alt paketin içinden yazılan göreli istek
komşudakini, mutlak istek kökteki modülü verir. Adlar aynıdır; çözüldükleri nesneler
ayrıdır.
Göreli içe aktarımın bir koşulu vardır: isteği yapan modülün bir pakete ait olması. Bir paketin içinde olmayan bir modül için “bulunduğum paket” diye bir şey yoktur ve istek çözülemez. Ölçüm bu durumu da sınıyor.
Paketin Gövdesi Kimin Faturası
__init__.py bir paketin kapısıdır ve pakete ait herhangi bir modül istendiğinde koşar.
Bu, oraya yazılanın bedelini paketin bütün kullanıcılarına ödettiği anlamına gelir. İki
yaygın yazım vardır ve aralarındaki fark ölçülebilir.
Sade yazımda __init__.py yalnız paketin kendi adlarını bağlar, alt modülleri içe
aktarmaz; kullanıcı hangi alt modülü istiyorsa onu ayrıca yazar. Peşin yazımda
__init__.py alt modülleri kendisi içe aktarır ve onların adlarını paket düzeyine taşır;
kullanıcı tek bir addan hepsine ulaşır. İkincisi kısa yazım kazandırır ama tek bir yaprağı
isteyen kullanıcıya bütün kardeşlerin gövdesini koşturur. Ölçümün son bölümü aynı tek
yaprak isteğini iki yazımda karşılaştırıyor.
Bir İstek Kaç Gövde Koşturur
İçe aktarmanın bir var etme işi olduğu önceki derste ölçüldü. Paketlerde bu sayı zincirin uzunluğuyla büyür ve zincirin bir tarafı da içe aktarılan modülün kendi isteklerinden gelir. Bir alt modül, gövdesinde başka modüller içe aktarıyorsa onlar da aynı istekte koşar. Ölçüm, tek bir isteğin koşturduğu gövdeleri koşum sırasıyla yazıyor.
Ölçümün varsayımları:
- MP11 — Ölçüm depoya dosya bırakmaz; paket ağacı geçici bir dizinde üretilir, iş bitince silinir ve dizinin yolu basılmaz.
- MP12 — Koşan gövdeler, her gövdenin ortak bir listeye kendi adını eklemesiyle sayılır; sayan taraf koşan modülün kendisidir ve liste koşum sırasını korur.
- MP13 — Kök ve alt pakette aynı kısa adı taşıyan iki modül vardır; ikisi yalnız modül düzeyindeki bir adın değeriyle ayrılır.
- MP14 — Göreli ve mutlak istek, ölçülen üçüncü bir modülün gövdesinden yapılır; iki sonuç o modülün sözlüğünden okunur.
- MP15 — Hangi adın bağlandığı ayrı bir ad alanı sözlüğünde ölçülür; iki tanımlı ad dışındaki adlar ayıklanır.
- MP16 — Pakete ait olmayan bir modülde göreli istek denenir; kâhin istisnanın sınıf adıdır, ileti metni değil.
- MP17 — Ölçüm bittiğinde eklenen yol girdisi ve bütün önbellek kayıtları geri alınır.
- MP18 — Sade ve peşin yazım aynı ağaç biçimini taşır: bir paket ve üç yaprak. Tek
fark, peşin yazımın
__init__.pydosyasındaki tek satırdır. - MP19 — İki yazımın karşılaştırmasında istenen tek bir yapraktır ve sayım her ölçümden önce sıfırlanır; iki ölçüm birbirinin sayısını taşımaz.
Ölçüm
"""Paket yapisi: ayni kisa ad iki yazimda hangi nesneye cozulur.""" import importlib import sys import tempfile from pathlib import Path def govde(ad, ek=""): return f"import kayit\nkayit.GOVDE.append('{ad}')\n{ek}" KAYNAKLAR = { "kayit.py": "GOVDE = []\n", "paket/__init__.py": govde("paket"), "paket/arac.py": govde("paket.arac", "AD = 'paket.arac'\n"), "paket/alt/__init__.py": govde("paket.alt"), "paket/alt/arac.py": govde("paket.alt.arac", "AD = 'paket.alt.arac'\n"), "paket/alt/kullanan.py": govde("paket.alt.kullanan", "from . import arac as goreli\n" "from paket import arac as mutlak\n"), "yalniz.py": "from . import arac\n", "sade/__init__.py": govde("sade"), "pesin/__init__.py": govde("pesin", "from . import x, y, z\n"), } for kok_ad in ("sade", "pesin"): for yaprak in ("x", "y", "z"): KAYNAKLAR[f"{kok_ad}/{yaprak}.py"] = govde(f"{kok_ad}.{yaprak}") TEPELER = ("kayit", "yalniz", "paket", "sade", "pesin") def kur(kok): for ad, metin in KAYNAKLAR.items(): yol = kok / ad yol.parent.mkdir(parents=True, exist_ok=True) yol.write_text(metin, encoding="utf-8") sys.path.insert(0, str(kok)) importlib.invalidate_caches() def sok(kok): sys.path.remove(str(kok)) for ad in list(sys.modules): if ad.split(".")[0] in TEPELER: sys.modules.pop(ad) def olc(kok): kur(kok) kullanan = importlib.import_module("paket.alt.kullanan") kayit = sys.modules["kayit"] kosan = list(kayit.GOVDE) kayitli = sorted(a for a in sys.modules if a.split(".")[0] == "paket") goreli, mutlak = kullanan.goreli, kullanan.mutlak oznitelik = (sys.modules["paket"].alt is sys.modules["paket.alt"] and sys.modules["paket.alt"].arac is sys.modules["paket.alt.arac"]) importlib.import_module("paket.alt.arac") ikinci_istek = len(kayit.GOVDE) - len(kosan) ns_nokta, ns_from = {}, {} exec("import paket.arac", ns_nokta) exec("from paket import arac", ns_from) bagli_nokta = sorted(a for a in ns_nokta if not a.startswith("__")) bagli_from = sorted(a for a in ns_from if not a.startswith("__")) ayni_nesne = ns_nokta["paket"].arac is ns_from["arac"] try: importlib.import_module("yalniz") hata = "yok" except Exception as e: hata = type(e).__name__ kayit.GOVDE.clear() importlib.import_module("sade.x") sade = list(kayit.GOVDE) kayit.GOVDE.clear() importlib.import_module("pesin.x") pesin = list(kayit.GOVDE) sok(kok) return {"kosan": kosan, "kayitli": kayitli, "ikinci": ikinci_istek, "ayni": goreli is mutlak, "goreli_ad": goreli.AD, "mutlak_ad": mutlak.AD, "oznitelik": oznitelik, "bagli_nokta": bagli_nokta, "bagli_from": bagli_from, "ayni_nesne": ayni_nesne, "hata": hata, "sade": sade, "pesin": pesin} with tempfile.TemporaryDirectory() as d: s = olc(Path(d)) print(f"tek istek {len(s['kosan'])} gövde koşturdu: {' -> '.join(s['kosan'])}") print(f"önbelleğe yazılan paket modülü {len(s['kayitli'])}: {s['kayitli']}") print(f"aynı alt modül ikinci kez istendiğinde koşan gövde {s['ikinci']}") print() print(f"göreli yazım -> {s['goreli_ad']}") print(f"mutlak yazım -> {s['mutlak_ad']}") print(f"aynı nesne mi -> {s['ayni']}") print() print(f"noktalı yazımın bağladığı ad {s['bagli_nokta']}, " f"from yazımının bağladığı ad {s['bagli_from']}") print(f"ikisinin ulaştığı modül aynı nesne -> {s['ayni_nesne']}") print(f"alt paket üst pakette öznitelik olarak bağlı -> {s['oznitelik']}") print(f"paketsiz modülde göreli içe aktarım -> {s['hata']}") print() print(f"{'tek yaprak isteği':<22s} {'koşan gövde':>12s} koşum sırası") print(f"{'sade paket':<22s} {len(s['sade']):12d} {' -> '.join(s['sade'])}") print(f"{'peşin içe aktaran':<22s} {len(s['pesin']):12d} " f"{' -> '.join(s['pesin'])}")
tek istek 5 gövde koşturdu: paket -> paket.alt -> paket.alt.kullanan -> paket.alt.arac -> paket.arac önbelleğe yazılan paket modülü 5: ['paket', 'paket.alt', 'paket.alt.arac', 'paket.alt.kullanan', 'paket.arac'] aynı alt modül ikinci kez istendiğinde koşan gövde 0 göreli yazım -> paket.alt.arac mutlak yazım -> paket.arac aynı nesne mi -> False noktalı yazımın bağladığı ad ['paket'], from yazımının bağladığı ad ['arac'] ikisinin ulaştığı modül aynı nesne -> True alt paket üst pakette öznitelik olarak bağlı -> True paketsiz modülde göreli içe aktarım -> ImportError tek yaprak isteği koşan gövde koşum sırası sade paket 2 sade -> sade.x peşin içe aktaran 4 pesin -> pesin.x -> pesin.y -> pesin.z
Beş Gövde, Beş Nesne
İlk satır tek bir isteğin bedelini sırasıyla veriyor. paket.alt.kullanan istendi; koşan
gövde 5 oldu. Sıra kendini açıklıyor: önce paket, sonra paket.alt — noktalı ad soldan
sağa çözülüyor ve her düzeyin gövdesi kendi sırasında koşuyor. Üçüncü sırada istenen modülün
kendi gövdesi başlıyor, ve o gövde iki istek daha yapıyor: göreli istek paket.alt.arac’ı,
mutlak istek paket.arac’ı koşturuyor.
Var edilen modül nesnesi 5, tutulan 5 — hepsi önbellekte kayıtlı kalıyor. Üçüncü satır bunun sonucunu ödüyor: aynı alt modül ikinci kez istendiğinde koşan gövde 0. İkinci istek yeni nesne var etmiyor, hiçbir gövdeyi koşturmuyor, yalnız önbellekteki kaydı veriyor.
Buradan çıkan ölçü şudur: bir paket isteğinin maliyeti istenen modülde bitmez. Zincirdeki
her düzey ve o düzeylerin gövdelerinde yapılan her istek aynı faturaya girer. Bir paketin
__init__.py dosyasının içinde ne varsa, o pakete ait herhangi bir modülü isteyen herkes
onu ödemiş olur.
Aynı Ad, İki Nesne
Orta bölüm dersin ana ölçüsüdür. paket/alt/kullanan.py içinde aynı kısa ad iki kez
yazılıyor; göreli yazım paket.alt.arac, mutlak yazım paket.arac veriyor ve
is karşılaştırması False. İki ad, iki ayrı nesne. Var edilen 2, paylaşılan 0.
Ayrım tamamen başlangıç noktasındadır. Göreli yazım adı isteği yapan modülün paketinden
başlatır; kullanan modülü paket.alt içinde durduğu için komşusunu bulur. Mutlak yazım
adı arama yolundan başlatır ve kökteki paketi bulur. Kaynak dosyalarda yazan kısa ad
ikisinde de aynıdır; ayrımı yapan yalnız yazımın kendisidir.
Bunun bir de tersi vardır ve önceki dersin ölçtüğü kuraldan çıkar: önbellek kaydı ada göre tutulur, dosyaya göre değil. Aynı kaynak iki ayrı adla istenirse iki ayrı kayıt açılır ve iki ayrı modül nesnesi var edilir — önceki derste önbellek kaydı silindiğinde ölçülen durumun aynısı. O iki nesnenin sözlükleri bağımsız olduğu için içlerinde tanımlanan sınıflar da ayrı nesnelerdir; birinden üretilmiş bir örnek, öbürünün sınıfına göre sınandığında beklenen yanıtı vermez. Aynı ağaca hem paket adıyla hem de doğrudan erişilebilmesi bu yüzden bir kolaylık değil, ikiye bölünmüş bir ad alanıdır.
Bunun okuma tarafında bir sonucu var: bir dosyada yalnız kısa adı görmek hangi modülün kastedildiğini söylemez. Aynı satırın anlamı, dosyanın hangi pakette durduğuna bağlıdır. Dosya bir başka pakete taşındığında göreli istek sessizce başka bir nesneye çözülür ya da hiç çözülmez; mutlak istek taşınmadan etkilenmez. İki yazım arasındaki seçim bir üslup tercihi değil, adın hangi bağlama bağlandığı kararıdır.
Hangi Ad Bağlanır
Son bölüm iki yazımın ad bağlama farkını veriyor. Noktalı yazım, isteği yapan tarafta
yalnız paket adını bağlıyor — alt modülün adını değil. Alt modüle o addan noktayla
ulaşılır, çünkü zincirin her düzeyi bir üstüne öznitelik olarak bağlanmıştır. from yazımı
ise yalnız arac adını bağlıyor; paketin adı isteği yapan tarafta hiç görünmüyor.
İki yazım farklı adlar bağlasa da aynı nesneye ulaşıyor: is karşılaştırması True. Var
edilen yeni nesne 0; ikisi de önbellekteki tek kaydı paylaşıyor. Bir sonraki satır bunun
paketler tarafını gösteriyor — üst paketin sözlüğündeki alt adı ile önbellekteki
paket.alt kaydı aynı nesnedir, ve aynısı bir düzey aşağıda da geçerlidir.
Sonraki satır göreli yazımın sınırıdır. Bir pakete ait olmayan modülde göreli istek
ImportError ile sonuçlanıyor. Neden açıktır: göreli ad, isteği yapan modülün paketinden
başlar; paketi olmayan bir modül için başlanacak yer yoktur. Bu, bir dosyanın nasıl
koşturulduğuna bağlı olarak ortaya çıkabilen bir durumdur ve bu konunun son dersinde
ölçülür.
Peşin İçe Aktarmanın Bedeli
Son tablo aynı isteği iki paket yazımında karşılaştırıyor ve fark tek bir satırdan doğuyor.
Sade pakette tek bir yaprak istendiğinde koşan gövde 2: paketin kendisi ve istenen yaprak.
Peşin yazımda aynı istek 4 gövde koşturuyor — paketin gövdesi, ve onun kendi içinden
istediği üç yaprağın hepsi. İstenen yaprak zaten o üçün içindedir; sıraya bakıldığında
pesin.x paketin gövdesi sırasında koşmuş, dış istek ona sıra geldiğinde önbellekte hazır
bulmuştur.
Sayılar oran olarak okunur: kullanıcı bir yaprak istedi, üç yaprağın bedelini ödedi. Yaprak sayısı arttıkça bu oran büyür; on yapraklı bir pakette tek bir yaprak isteği on birin tamamını koşturur. Var edilen modül nesnesi sade yazımda 2, peşin yazımda 4; tutulan da aynı, çünkü hepsi önbellekte kalır.
Bu bir kusur değil, bir takastır ve iki tarafı da ölçülebilir. Peşin yazım kullanıcıya kısa bir ad verir ve paketi tek bir yüzey gibi gösterir; bedeli, paketin herhangi bir parçasını isteyen herkese bütün parçaların gövdesini ödetmektir. Sade yazım bedeli isteyene bırakır; bedeli, kullanıcının hangi alt modülde ne olduğunu bilmesini gerektirmesidir. Seçimi belirleyen şey, paketin parçalarının birlikte mi ayrı mı kullanıldığıdır.
Özet
- Paket,
__init__.pydosyasıyla birlikte bir modül nesnesi veren dizindir; noktalı ad soldan sağa çözülür, her düzey ayrı bir nesnedir ve her düzey bir üstüne öznitelik olarak bağlanır. - Tek bir alt modül isteği 5 gövde koşturdu ve 5 modül nesnesi önbelleğe yazdı; aynı modülün ikinci isteği 0 gövde koşturdu.
- Aynı kısa ad göreli yazımda
paket.alt.arac, mutlak yazımdapaket.aracverdi veisFalse çıktı; ayrımı yapan tek şey adın nereden başlatıldığıdır. - Noktalı yazım yalnız kök paketin adını,
fromyazımı yalnız alınan adı bağlar; ikisi de 0 yeni nesne var eder ve aynı modül nesnesini paylaşır. __init__.pyalt modülleri peşin içe aktardığında tek bir yaprak isteği 2 yerine 4 gövde koşturur; kullanıcı bir yaprak ister, üçünün bedelini öder.- Pakete ait olmayan bir modülde göreli içe aktarım
ImportErrorverir, çünkü adın başlayacağı bir paket yoktur.
Sonraki Adım
Buraya kadar ölçülen paketlerin hepsi ölçüm için üretildi: gövdeleri bir satırlık, işleri kendilerini saydırmaktı. Oysa her koşumda hazır bulunan, hiçbir kurulum gerektirmeyen büyük bir paket kümesi vardır ve dilin kendisiyle birlikte gelir. Sonraki ders o kümenin kapsamına bakıyor ve tek bir soruyla ölçüyor: aynı işi standart kitaplıktan bir modülle yazmakla elle yazmak arasında kaç nesne ve kaç satır fark var, ve elle yazım hangi durumlarda sessizce ayrışıyor?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.