Modül/paket düzeni ve temel tip ipuçları
Öğrenirken birkaç sınıfı tek dosyaya yazmak iş görür. Kod büyüdükçe her işi ayrı bir dosyaya taşımak gerekir. 4. haftada bir fonksiyonu başka bir dosyadan import ile almıştınız. Bu hafta bir mağaza programının sınıflarını modüllere (her biri bir .py dosyası) ve bu modülleri bir arada tutan bir pakete (klasöre) böleceğiz. Tip ipucu (type hint) ile de bir fonksiyonun hangi türde değer aldığını ve döndürdüğünü imzasına yazacağız. Fonksiyonun imzası def satırıdır: fonksiyonun adını, parametrelerini ve tip ipucu yazıldıysa dönüş türünü gösterir.
Bu sayfadaki kod hücreleri kendiliğinden çalışmaz. Her hücrede önce çıktıyı tahmin edin, sonra Run Code düğmesine basın. Kodu değiştirip yeniden çalıştırabilir, Start Over ile ilk hâline döndürebilirsiniz. Paket örnekleri birden çok dosya istediği için hücrede çalışmaz. Onları kendi bilgisayarınızda VS Code ve terminalle deneyeceksiniz.
Bu bölümün kapsamı
- Basit paket düzeni ve
__init__.pydosyasının rolü ModuleNotFoundErrorkarşısında çalışma dizini denetimipython app.pyilepython -m paket.modulayrımı- Kodu modüllere sorumluluğa göre ayırma
- Tip ipucunun çalışma zamanında türü denetlememesi
T | Noneyazımı ve çağıranınNonekontrolü- Sınıf ve metot imzalarındaki tip ipuçlarından nesnenin nasıl kullanılacağını okuma
- İleri referans ve
from __future__ import annotations dataclassveEnum- Paket yayımlama,
pyproject.toml, sanal ortam yönetimi mypygibi statik tür denetleyicilerinin ayarları
Bu başlıklar konunun devamıdır. İleride karşınıza çıkar, ama bu derste ezberlemeniz beklenmiyor.
Büyüyen mağaza programını C’de nasıl bölerdiniz?
Önceki haftalarda yazdığınız mağaza programı tek bir dosyada büyüdü: Product, Customer ve Order sınıfları da, 12. haftadaki OutOfStockError da aynı dosyada. Programı birkaç dosyaya bölüp şu beş işi yapacaksınız:
- Ürün, müşteri ve sipariş kodunu ayrı dosyalara koymak
- Bir dosyada yazılan sınıfı başka bir dosyada kullanmak
- Programı terminalden başlatmak, “bulunamadı” hatası gelince nedenini bulmak
- Bir fonksiyonun hangi türde değer alıp döndürdüğünü gövdesini okumadan öğrenmek
- Aranan ürün bulunamadığında bunu çağıran koda bildirmek
Programlama Temelleri’nde C ile birden çok dosyalı bir program yazdığınızı düşünün: .c ve .h dosyaları, #include satırları, derleyiciye verilen dosya adları. Bu işlerin her birini C’de nasıl yapardınız? 4. haftada Python’da gördüğünüz modülleri de kullanabilirsiniz. Okumaya devam etmeden önce her iş için bir satır yazın. “product.c ve product.h diye iki dosya açarım” da bir cevap.
Bölümde bu beş işin Python’da nasıl yapıldığını göreceğiz. Bölümün sonunda listenize döneceğiz.
Basit paket düzeni
C’de her .c dosyasının yanında, başka dosyaların kullanacağı fonksiyonların bildirimlerini tutan bir .h başlık dosyası vardı. Python’da başlık dosyası yoktur. Her .py dosyası bir modüldür ve başka bir dosya içindeki adları doğrudan import ile alabilir. Birbiriyle ilgili modülleri bir klasörde toplarsanız o klasör bir paket olur:
project/
├── app.py
└── shop/
├── __init__.py
├── product.py
├── customer.py
├── order.py
└── errors.py
Bir klasörün Python paketi olduğunu göstermenin alışılmış yolu, içine __init__.py dosyası koymaktır. Bu dosya boş olabilir. İsterseniz içine paket ilk kez import edildiğinde çalışacak kodu ya da paketin dışarıya açacağı adları yazabilirsiniz. Bu derste boş bırakacağız.
app.py paketin dışında durur ve programı başlatan dosyadır. En üstteki project/ klasörüne proje kökü denir.
Modülleri sorumluluğa göre ayırın
Kodu dosyalara ayırırken her sınıfa ayrı bir dosya açmanız gerekmez. Hangi sınıfların aynı modülde duracağına şu sorularla karar verin:
- Bu sınıflar aynı işle mi ilgili?
- Bir modüldeki değişiklik başka kaç modülü etkiliyor?
- Modülün dışındaki kod hangi adları kullanmalı?
Yukarıdaki düzende ürünle ilgili kod product.py, siparişle ilgili kod order.py içinde duruyor. Özel istisnalar errors.py içinde toplandı, çünkü birden çok modül onları kullanıyor.
_round_money gibi tek alt çizgiyle başlayan adlar, sınıflarda olduğu gibi modüllerde de “bu, modülün içinde kullanılan bir yardımcıdır” anlamına gelir. Python bu adlara dışarıdan erişimi engellemez.
Bir modülden ötekine import
C’de başka bir dosyadaki fonksiyonu kullanmak için onun başlık dosyasını #include "product.h" ile eklerdiniz. Python’da import satırı yazarsınız. Paketin dışındaki app.py, paketin adıyla başlayan tam yolu yazar:
from shop.product import ProductPaketin içindeki order.py, aynı paketteki product.py dosyasını şöyle alır:
from .product import ProductBaştaki nokta “bu modülle aynı paketteki” demektir. Noktayla başlayan bu yazıma göreli import denir. product.py de hata sınıfını aynı yolla alır:
from .errors import OutOfStockErrorDöngüsel import riski
a.py b.py’yi, b.py de a.py’yi import ederse iki modül arasında döngüsel bir bağımlılık oluşabilir. Bu çoğu zaman iki modülün işlerinin birbirine fazla karıştığını gösterir. Çözüm tekniklerine bakmadan önce kendinize şunu sorun: “Bu iki modülün gerçekten birbirini import etmesi gerekiyor mu?”
Çalışma dizini neden önemlidir?
C’de derleyiciye dosya adlarını siz verirdiniz: gcc main.c product.c. Derleyici product.h dosyasını bulamazsa No such file or directory hatası verirdi. Python’da import satırındaki modülü Python kendisi arar. Önce nereye bakacağı, programı nasıl başlattığınıza bağlıdır:
py app.pygibi bir dosyayı başlatırsanız önce o dosyanın bulunduğu klasöre bakar.py -m shop.demoile başlatırsanız önce terminalin açık olduğu klasöre bakar. Bu klasöre çalışma dizini denir.
Python modülü orada bulamazsa standart kütüphaneye ve kurulu paketlere bakar. Orada da yoksa ModuleNotFoundError verir.
En güvenli yol, terminali proje kökünde açıp programı oradan başlatmaktır. VS Code’da File > Open Folder ile project/ klasörünü açarsanız Terminal > New Terminal ile açılan terminal zaten orada başlar:
py app.py
macOS ve Linux’ta py yerine python3 yazın. Terminal başka bir klasörde açıksa bu komut app.py dosyasını bulamaz ve can't open file ... No such file or directory hatası verir. Bu bir import hatası değildir: Python başlatılacak dosyayı bulamamıştır.
ModuleNotFoundError görünce hemen paket kurmayın
Önce şunları kontrol edin:
- Terminal şu an hangi klasörde? PowerShell’de
Get-Location(kısacapwd), macOS ve Linux’tapwdyazın. - Programı hangi dosyayla ya da hangi komutla başlattınız? Paketin içindeki bir dosyayı doğrudan başlatırsanız Python paketin kendisini bulamaz.
app.pyile paket klasörü aynı proje kökü altında mı?- Dosya ya da paket adında yazım hatası var mı?
- VS Code doğru klasörü mü açtı ve doğru Python yorumlayıcısını mı kullanıyor?
Kendi yazdığınız shop gibi bir paket bulunamıyorsa çözüm çoğu zaman PyPI’dan (pip install ile paket indirilen ortak depo) bir şey kurmak değil, programı proje kökünden doğru komutla çalıştırmaktır.
python app.py ile python -m package.module
Paketin modüllerini denemek için paketin içinde bir demo.py dosyası olsun:
# shop/demo.py
from .product import Product
print(Product("P1", "Klavye", 2))Bu dosyayı python shop/demo.py diye yolunu vererek başlatırsanız Python onu paketten bağımsız bir script (doğrudan başlatılan program dosyası) olarak çalıştırır ve dosyanın shop paketine ait olduğunu bilmez. Göreli import bu yüzden çalışmaz, ImportError: attempted relative import with no known parent package hatası alırsınız. Göreli import yerine from shop.product import Product yazsanız da sonuç değişmez. Python önce başlatılan dosyanın klasörüne, yani shop/ klasörüne bakar ve orada shop adlı bir paket bulamaz: ModuleNotFoundError.
Bir modülü paketin parçası olarak çalıştırmak için proje kökünden şunu yazarsınız:
python -m shop.demo
-m seçeneği dosya yolu yerine modülün paket içindeki tam adını (shop.demo) alır. Python modülü çalışma dizininden başlayarak bulur ve shop paketinin parçası olarak çalıştırır. Göreli import da böylece çözülür. Windows’ta python komutu çalışmazsa aynı komutları py ile yazın: py -m shop.demo.
Komutları ezberlemeniz gerekmiyor. Bir import hatasını teşhis ederken şu soruyu sormanız yeterli:
“Bu dosya bağımsız script olarak mı, yoksa bir paketin modülü olarak mı çalışıyor?”
app.py ve main guard
4. haftadaki main guard kalıbı (if __name__ == "__main__":, dosya doğrudan çalıştırıldığında __name__ değeri "__main__" olur) çok dosyalı programda da kullanılır. app.py, programı başlatan kodu bir main() fonksiyonuna koyar:
# app.py
from shop.product import Product
def main():
keyboard = Product("P1", "Klavye", 2)
print(keyboard)
if __name__ == "__main__":
main()python -m shop.demo ile çalıştırılan modülde de __name__ değeri "__main__" olur. Başka bir dosyanın import ettiği modül ise kendi adını alır: shop.product gibi. Bu yüzden product.py içindeki deneme kodunu main guard altına koyarsanız app.py o modülü import ettiğinde deneme kodu çalışmaz.
Tip ipucu nedir?
C’de bir fonksiyonun ne aldığını ve ne döndürdüğünü başlık dosyasındaki bildirimden okurdunuz: double calculate_total(double prices[], int n);. Türleri yazmak zorundaydınız ve derleyici çağrıları bu türlere göre denetlerdi. Python’da tür yazmak zorunlu değildir, ama isterseniz tip ipucu ile yazabilirsiniz. Aşağıda prices: list[float], fonksiyonun float değerlerden oluşan bir liste beklediğini söyler. -> float ise bir float döndüreceğini söyler. Hücre ne yazar?
İpucuna uymayan bir değer verirsek ne olur? add iki int bekliyor, ona iki metin veriyoruz. Python bu çağrıyı reddeder mi? Hücre ne yazar?
Tip ipucu:
- Kodu okuyana fonksiyonun ne beklediğini söyler.
- Editöre ve statik analiz araçlarına (kodu çalıştırmadan inceleyen
mypygibi araçlar) bilgi verir. - Program çalışırken türü denetlemez. C derleyicisinin yaptığı tür denetimini Python yapmaz.
T | None ve eski sürüm söz dizimi
C’de aranan ürün bulunamayınca fonksiyon NULL döndürürdü, çağıran kod da NULL kontrolü yapardı. Python’da bunun karşılığı None döndürmektir. T | None tip ipucu “ya T türünde bir değer ya da None” demektir. T yerine herhangi bir tür yazılır. Aranan ürün bulunamazsa None döndüren bir metodun imzası:
def find(self, code: str) -> Product | None:
...Bu yazım Python 3.10 ile geldi. Daha önce yazılmış kodlarda aynı şey şöyle yazılır:
from typing import Optional
def find(self, code: str) -> Optional[Product]:
...Python 3.9’dan eski kaynaklarda list[str] yerine List[str], dict[str, int] yerine Dict[str, int] de görebilirsiniz. Bu derste güncel yazımı kullanıyoruz.
Sınıflarda tip ipucu
Tip ipuçları sınıflarda da yazılır. Aşağıdaki Catalog sınıfının imzalarını okuyun: add bir Product alır ve değer döndürmez (-> None). find bir metin kod alır, Product ya da None döndürür. Katalogda P9 kodlu ürün yok. Hücre ne yazar?
self.products: dict[str, Product] satırı niteliğe de ipucu yazar: anahtarlar metin, değerler Product. find sözlüğün .get() metodunu (3. hafta) kullandığı için anahtar yoksa None döndürür. İmzayı okuyan biri, gövdeye bakmadan metodu nasıl çağıracağını ve sonucu nasıl denetleyeceğini bilir.
None kontrolünü sizin yerinize yapmaz
Product | None yazmak metodun None döndürmesini engellemez. result.price satırına gelmeden önce result değerinin None olup olmadığını kontrol etmek yine programcının işidir. Kontrolü unutursanız ürün bulunamadığında AttributeError alırsınız.
Tanımanız yeterli: dataclass ve Enum
dataclass: çoğunlukla veri tutan sınıflarda her seferinde elle yazdığınız__init__,__repr__ve__eq__kodunu azaltır.Enum: yalnız birkaç değer alabilen bir durumu (örneğin bir siparişin “beklemede” ve “gönderildi” hâlleri) adlarla yazmanızı sağlar.
İki yapı da sınavda sorulmaz.
Soruya dönelim: beş iş Python’da
Bölümün başında mağaza programını dosyalara bölerken beş işi C’de nasıl yapacağınızı sormuştuk. Python’daki karşılıkları şöyle:
| İş | C’de ya da önceki haftalarda | Bu haftaki Python yolu | Dikkat |
|---|---|---|---|
| Kodu ayrı dosyalara koymak | product.c ve product.h gibi dosya çiftleri |
Her sınıf grubu bir .py modülüne, modüller __init__.py bulunan shop/ paketine |
Başlık dosyası yok. Her sınıfa ayrı dosya da gerekmez, sınıflar işlerine göre gruplanır |
| Başka dosyadaki sınıfı kullanmak | #include "product.h", 4. haftada from pricing import final_price |
Paket dışından from shop.product import Product, paket içinden from .product import Product |
İki modül birbirini import ediyorsa işler iyi bölünmemiş olabilir |
| Programı başlatıp “bulunamadı” hatasını çözmek | gcc main.c product.c yazmak, dosya bulunamazsa yolu kontrol etmek |
Proje kökünden py app.py ya da py -m shop.demo |
ModuleNotFoundError görünce paket kurmayın, önce çalışma dizinine ve başlatılan dosyaya bakın |
| Fonksiyonun ne alıp ne döndürdüğünü öğrenmek | Başlık dosyasındaki bildirim: double calculate_total(double prices[], int n); |
Tip ipucu: def calculate_total(prices: list[float]) -> float: |
Python çalışırken türü denetlemez |
| Bulunamayan ürünü bildirmek | NULL döndürmek, çağıranın NULL kontrolü yapması |
Dönüş ipucuna None eklenir, çağıran if result is not None: yazar |
İpucu None gelmesini engellemez, kontrol çağıranın işidir |
Listenizi tabloyla karşılaştırın. C’de dosyaları derleyiciye siz verirdiniz. Python’da import satırındaki modülü Python kendisi arar, nereden aramaya başlayacağı da programı hangi klasörden ve hangi komutla başlattığınıza bağlıdır. Üçüncü işe “dosya yolunu kontrol ederim” yazdıysanız doğru yoldaydınız: Python’da bakacağınız yer, terminalin açık olduğu klasör ve başlattığınız dosyadır. C’deki tür bildirimini derleyici denetliyordu, Python’daki tip ipucunu ise okur ve editör kullanır. Beşinci iş için 12. haftadaki gibi bir istisna yükseltmeyi düşünmüş olabilirsiniz. Aranan ürünün katalogda olmaması olağan bir sonuçsa None döndürmek yeter. Stok yetmemesi gibi bir kural bozulduysa istisna yükseltilir.
Alıştırma: Tip ipuçlarını ekle
Fonksiyonun ne aldığını ve ne döndürdüğünü dict[int, str], int ve str | None ipuçlarıyla imzaya yazın.
Alıştırma: Çalışma dizini hatasını teşhis et
Kendi bilgisayarınızda project/app.py, project/shop/__init__.py, project/shop/product.py ve project/shop/demo.py dosyalarını oluşturun. demo.py içinde from shop.product import Product satırı olsun. Sonra şu üç denemeyi yapın:
- Terminal proje kökündeyken
py app.pyvepy -m shop.demo - Terminal bir üst klasördeyken
py -m shop.demo - Terminal proje kökündeyken
py shop/demo.py
Her denemede Get-Location çıktısına ve hata mesajına bakın. Hata veren denemelerde Python’ın shop paketini hangi klasörde aradığını yazın.
Sıradaki görevde Book | None gibi ipuçlarını kendi bilgisayarınızda çalıştıracaksınız. list[str] ve dict[str, Product] yazımı Python 3.9, Product | None yazımı Python 3.10 ister. Bu derste en az Python 3.10 gerekir. Mümkünse desteği süren 3.11 ya da daha yeni bir sürüm kullanın. 1. haftada kurduğunuz sürümü terminalde yeniden kontrol edin:
py --version
macOS ve Linux’ta python3 --version yazın. Yanlış yorumlayıcı seçiliyse VS Code’da Python: Select Interpreter komutunu kullanın.
Sıra sizde: Üç modüllü sistem
Yerel ortamınızda şu yapıyı oluşturun:
library-project/
├── app.py
└── library/
├── __init__.py
├── book.py
├── member.py
└── library.py
Library.find_book(isbn) metodu Book | None döndürsün. app.py kitabın bulunamadığı, yani None döndüğü durumda kullanıcıya bir mesaj yazdırsın.
Çalışır kod görevi: Python sürümünüzü not edin ve programı proje kökünden çalıştırın. En az bir paket içi göreli import, bir paket dışı import ve bir Book | None dönüşü kullanın. app.py programı main guard altındaki main() fonksiyonundan başlatsın. Ayrıca küçük bir library/demo.py oluşturup proje kökünden py -m library.demo ile çalıştırın.
Tek sayfa özet
- Her
.pydosyası bir modüldür.__init__.pybulunan klasör bir pakettir, bu dosya boş olabilir. - Kodu modüllere işine göre ayırın. Her sınıfa ayrı dosya gerekmez. İki modül birbirini import ediyorsa iş bölümünü gözden geçirin.
- Paket dışından
from shop.product import Product, paket içinden göreli import ilefrom .product import Productyazılır. - Python modülü önce bir dosyayı başlattıysanız o dosyanın klasöründe,
-mile başlattıysanız çalışma dizininde arar. ModuleNotFoundErrorgörünce önce terminalin hangi klasörde olduğuna, hangi dosyayı başlattığınıza ve seçili yorumlayıcıya bakın.python -m shop.demomodülü paketin parçası olarak çalıştırır.python shop/demo.pydosyayı paketten bağımsız bir script olarak başlatır, göreli import çalışmaz.- Tip ipuçları beklenen türü okura ve editöre söyler, program çalışırken türü denetlemez.
T | Nonesonucun bulunamayabileceğini imzada söyler.Nonekontrolünü yine çağıran yapar.- Metot imzası, metodun nasıl kullanılacağını söyler: ne verilir, ne döner, dönüş
Noneolabilir mi. - Bu ders için Python 3.10 ya da daha yeni bir sürüm kullanın.
Bu bölümün kazanımları
Bu bölümü bitiren öğrenci:
- Basit bir paket düzeni kurar ve
__init__.pydosyasının rolünü açıklar. ModuleNotFoundErrorkarşısında önce çalışma dizinini ve proje kökünü denetler.python app.pyilepython -m paket.modularasındaki farkı açıklar.- Kodu modüllere sorumluluklarına göre ayırır.
- Tip ipucunun çalışma zamanında türü denetlemediğini açıklar.
T | Noneyazımını kullanır ve çağıranınNonekontrolünü hâlâ yapması gerektiğini bilir.- Sınıf ve metot imzalarındaki tip ipuçlarından bir nesnenin nasıl kullanılacağını okur.