Modül/paket düzeni ve temel tip ipuçları
Tek dosyada birkaç sınıfla başlamak öğrenme için uygundur; ancak kod büyüdükçe sorumlulukları dosyalara ayırmak gerekir. Bu hafta küçük bir OOP sistemini modüllere/pakete bölecek ve temel tip ipucu (type hint) kullanımıyla arayüzleri daha okunabilir hâle getireceğiz.
Bu bölümde kullandığımız:
list[str]/dict[str, Product]biçimi Python 3.9+,Product | Nonebiçimi Python 3.10+ gerektirir.
Bu ders için minimum Python 3.10, tercihen güncel desteklenen 3.11+ sürümü kullanın. 1. haftada kurduğunuz ortamı terminalde yeniden doğrulayın:
# Windows
py --version
# macOS / Linux
python3 --version
Yanlış yorumlayıcı seçiliyse VS Code’da Python: Select Interpreter komutunu kullanın.
Bu hafta neleri yapabilmelisiniz?
- Sınıfları sorumluluğa göre farklı modüllere ayırabilmeli,
- Küçük bir Python paket yapısını okuyup çalıştırabilmeli,
__init__.pydosyasının rolünü açıklayabilmeli,- Paket içi ve paket dışı import kullanımını ayırt edebilmeli,
- Çalışma dizininin import davranışını nasıl etkilediğini fark edebilmeli,
python -m ...ile dosyayı doğrudan çalıştırma arasındaki temel farkı açıklayabilmeli,- Temel
str,int,list[T],dict[K, V]veT | Nonetip ipuçlarını kullanabilmeli, - İleri referans gerektiren tip ipuçlarını tanıyabilmeli,
- Type hint’in çalışma zamanı doğrulaması olmadığını açıklayabilmelisiniz.
Basit paket düzeni
project/
├── app.py
└── shop/
├── __init__.py
├── product.py
├── customer.py
├── order.py
└── errors.py
__init__.py, bu dizinin bir Python paketi olarak kullanılmasında geleneksel ve açık sınır oluşturur. Boş olabilir. İstenirse paket yüklenirken çalışacak başlangıç kodunu veya dışarıya sunulacak adları da içerebilir; fakat başlangıç düzeyinde boş bırakmak çoğu zaman yeterlidir.
Paket içindeki product.py:
from .errors import OutOfStockErrororder.py:
from .product import ProductPaketin dışındaki app.py:
from shop.product import ProductBu hafta ileri paketleme/PyPI ayrıntılarını ezberlemiyoruz; ancak import’un hangi paket bağlamından ve hangi çalışma dizininden yürütüldüğünü anlamamız gerekir.
Çalışma dizini neden önemlidir?
Yukarıdaki yapı için terminalinizi project/ dizininde açın:
project/
├── app.py
└── shop/
ve buradan:
py app.py
veya macOS/Linux’ta:
python3 app.py
çalıştırın. Terminal başka bir dizindeyse shop paketi beklediğiniz import yolunda olmayabilir ve ModuleNotFoundError görebilirsiniz.
ModuleNotFoundError görünce hemen paket kurmayın
Önce şunları kontrol edin:
- Terminal şu an hangi klasörde? (
pwd; Windows PowerShell’deGet-Location) app.pyile paket klasörü gerçekten aynı proje kökü altında mı?- Dosya/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 sorun çoğu zaman PyPI’dan bir şey yüklememek, proje kökünden doğru biçimde çalıştırmaktır.
python app.py ile python -m package.module
Bir dosyayı doğrudan çalıştırdığınızda Python onu bir dosya/script bağlamında başlatır:
python app.py
Bir modülü paket bağlamında çalıştırmak için:
python -m shop.demo
kullanılabilir. -m, modülü import sistemi üzerinden paket içindeki tam adıyla bulup çalıştırır. Özellikle göreli import kullanan paket modüllerini python shop/demo.py diye doğrudan çalıştırmak yerine proje kökünden python -m shop.demo çalıştırmak doğru paket bağlamını koruyabilir.
Bu haftada amaç komutları ezberlemek değil, şu teşhis sorusunu öğrenmektir:
“Bu dosya bağımsız script olarak mı, yoksa bir paketin modülü olarak mı çalışıyor?”
Modül sınırı = sorumluluk sınırı
Dosyaya ayırma “her sınıf ayrı dosyada olsun” demek değildir. Şunları sorun:
- Bu sınıflar aynı kavramsal sorumluluğa mı ait?
- Bir modüldeki değişiklik başka kaç modülü etkiliyor?
- Dışarıdaki kod hangi adları kullanmalı?
Python’da _round_money gibi tek alt çizgili adlar yine “modül içi yardımcı” niyeti belirtir; teknik erişim yasağı değildir.
Tip ipucu nedir?
Tip ipucu:
- okuyucuya niyet gösterir,
- editör ve statik analiz araçlarına bilgi verir,
- ancak varsayılan olarak çalışma zamanında türü zorlamaz.
def add(a: int, b: int) -> int:
return a + b
print(add("a", "b")) # type hint bunu otomatik engellemezT | None ve eski sürüm sözdizimi
Modern sözdizimi:
def find(self, code: str) -> Product | None:
...Python 3.10 öncesi kodlarda benzer niyet şu biçimde görülebilir:
from typing import Optional
def find(self, code: str) -> Optional[Product]:
...Python 3.9 öncesindeki eski kaynaklarda ayrıca list[str] yerine List[str], dict[str, int] yerine Dict[str, int] görebilirsiniz. Bu dersin kodunu geriye uyumluluk için karmaşıklaştırmayacağız; güncel ortam kullanacağız.
İleri referans (forward reference)
Bir tip ipucunda henüz tanımı tamamlanmamış sınıf adına ihtiyaç duyabilirsiniz:
class Member:
def choose_friend(self) -> "Member | None":
...Tırnak içindeki annotation daha sonra çözümlenebilen bir ileri referans olarak kullanılabilir. Modern projelerde dosyanın başına:
from __future__ import annotationseklemek annotation’ların değerlendirilmesini erteleyerek birçok ileri referansı daha rahat yazmayı sağlar:
from __future__ import annotations
class Member:
def choose_friend(self) -> Member | None:
...from __future__ import annotations eski Python’a yeni sözdizimi eklemez
Bu özellik annotation değerlendirmesini erteler; örneğin Python 3.9 yorumlayıcısının X | None sözdizimini 3.10 gibi parse etmesini sağlamaz. Bu yüzden ders ortamı yine Python 3.10+ olmalıdır.
Sınıflarda tip ipucu
Product | None, aramanın başarısız olabileceğini görünür kılar. Çağıran yine None durumunu kontrol etmelidir.
Product | None yazmak None değerini ortadan kaldırmaz. product.code erişiminden önce uygun kontrol yine programcının sorumluluğundadır.
Döngüsel import riski
a.py, b.py’yi; b.py de a.py’yi import ederse döngüsel bağımlılık oluşabilir. Bu bazen iki modülün sorumluluklarının fazla iç içe geçtiğini gösterir. İleri çözüm tekniklerinden önce “bu iki modül gerçekten birbirini bilmek zorunda mı?” sorusunu sorun.
dataclass ve Enum kavram radarı
dataclass: veri ağırlıklı sınıflarda tekrar eden__init__,__repr__, eşitlik gibi kodları azaltabilir.Enum: sınırlı durum değerlerini anlamlı adlarla temsil edebilir.
Bu geçiş sürümünde tanıma düzeyindedir.
Alıştırma — Tip ipuçlarını ekle
Sözleşmeyi dict[int, str], int ve str | None ile görünür hâle getirin.
Alıştırma — Çalışma dizini hatasını teşhis et
project/shop/product.py ve project/app.py yapısını oluşturun. Önce proje kökünden çalıştırın. Sonra terminali üst klasöre taşıyıp aynı komutu deneyin. Oluşan farkı pwd/Get-Location çıktısıyla açıklayın. Amacımız hatayı ezberden düzeltmek değil, import bağlamını teşhis etmektir.
Küçük üretim görevi — Üç modüllü sistem
Yerel ortamınızda:
library-project/
├── app.py
└── library/
├── __init__.py
├── book.py
├── member.py
└── library.py
oluşturun. Library.find_book(isbn) metodu Book | None döndürsün. app.py içinde None durumunu yönetin.
Çalışır kod teslimi: Python sürümünüzü kaydedin; 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. Ayrıca küçük bir library/demo.py oluşturup proje kökünden python -m library.demo (Windows’ta gerekirse py -m library.demo) ile çalıştırın.
Kendinizi kontrol edin
list[T]veT | Noneiçin hangi Python sürümleri gerekir?__init__.pyne işe yarar; boş olabilir mi?- Çalışma dizini
ModuleNotFoundErrorile nasıl ilişkili olabilir? python -m package.modulehangi bağlamı korur?- Type hint çalışma zamanında yanlış türü otomatik engeller mi?
T | Noneneyi ifade eder?- İleri referans nedir?
from __future__ import annotationsneyi yapar, neyi yapmaz?
Bu haftadan akılda kalması gerekenler
- Bu ders için Python 3.10+ kullanın; sürümü ve seçili yorumlayıcıyı doğrulayın.
__init__.pypaket sınırını görünür kılar ve boş olabilir.- Import hatalarında önce çalışma dizini, proje yapısı ve yorumlayıcıyı kontrol edin.
python -mbir modülü paket/import bağlamında çalıştırır.- Tip ipuçları sözleşme ve araç desteğidir; runtime doğrulaması değildir.
T | Nonebulunamama durumunu görünür kılar.- İleri referanslar sınıfların birbirine/self’e tip düzeyinde referans vermesinde kullanılır.