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.

Çok dosyalı bir Python kod tabanında paket, modül ve sorumluluk ayrımını gösteren diyagram.

Paket ve modül yapısı
ImportantPython sürümünü önce kontrol edin

Bu bölümde kullandığımız:

  • list[str] / dict[str, Product] biçimi Python 3.9+,
  • Product | None biç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 bölümün kapsamı

NoteSınavda sorulur
  • basit paket düzeni ve __init__.py dosyasının rolü
  • ModuleNotFoundError karşısında çalışma dizini denetimi
  • python app.py ile python -m paket.modul ayrımı
  • modül sınırının sorumluluk sınırı olması
  • tip ipucunun çalışma zamanında tür zorlamaması
  • T | None yazımı ve çağıranın None kontrolü
  • ileri referans ve from __future__ import annotations etkisi
  • dataclass ve Enum
  • paket yayımlama, pyproject.toml, sanal ortam yönetimi
  • mypy gibi statik tür denetleyicilerinin ayarları

Bu başlıklar konunun devamıdır; ileride karşınıza çıkar ama bu derste ezberlemeniz beklenmiyor.

Bu dosya bağımsız script olarak mı, bir paketin modülü olarak mı çalışıyor?

Aynı kod, hangi dizinden ve hangi komutla başlatıldığına göre farklı import davranışı gösterir. ModuleNotFoundError gördüğünüzde ilk bakılacak yer bu yüzden paket listesi değil, çalıştığınız dizindir.

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 OutOfStockError

order.py:

from .product import Product

Paketin dışındaki app.py:

from shop.product import Product

Bu 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.

WarningModuleNotFoundError görünce hemen paket kurmayın

Önce şunları kontrol edin:

  1. Terminal şu an hangi klasörde? (pwd; Windows PowerShell’de Get-Location)
  2. app.py ile paket klasörü gerçekten aynı proje kökü altında mı?
  3. Dosya/paket adında yazım hatası var mı?
  4. 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?

Python tip ipuçlarının geliştirme araçlarına bilgi verdiğini ancak çalışma zamanında tür zorlaması yapmadığını gösteren diyagram.

Tip ipucu ve çalışma zamanı

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 engellemez

T | 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 annotations

eklemek 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:
        ...
Notefrom __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.

ImportantTip ipucu sözleşmeyi görünür kılar, hatayı otomatik çözmez

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.

Sınav provası

Önce kendi cevabını seç; sonra cevap anahtarında her şıkkın neden doğru veya yanlış olduğunu oku. Çeldiriciler uydurma değil, bu konuda gerçekten yapılan hatalardır. Sınav maddeleri de bu mantıkla yazılır.

Madde 1. Bir paket dizinindeki __init__.py dosyası hakkında doğru olan hangisidir?

  • A. Boş bırakılırsa paket import edilemez
  • B. Yalnızca PyPI’ya yüklenecek paketlerde gerekir
  • C. Paketin bütün sınıflarını içermek zorundadır
  • D. Dizinin Python paketi olarak kullanılmasında geleneksel ve açık bir sınır oluşturur; boş olabilir
  • E. Paketin ana giriş noktasıdır ve python paket ile çalıştırılır

Madde 2. Kendi yazdığınız shop paketi için ModuleNotFoundError alıyorsanız ilk ne yapılmalıdır?

  • A. Bütün import satırlarını try/except içine almak
  • B. pip install shop komutunu çalıştırmak
  • C. Python’ı yeniden kurmak
  • D. __init__.py dosyasını silmek
  • E. Terminalin hangi klasörde olduğunu ve app.py ile paketin aynı proje kökü altında bulunup bulunmadığını denetlemek

Madde 3. Terminalin hangi dizinde olduğunu öğrenmek için ne kullanılır?

  • A. macOS/Linux’ta pwd, Windows PowerShell’de Get-Location
  • B. import os ve os.name
  • C. pip list
  • D. python --version
  • E. ls (veya dir)

Madde 4. python -m shop.demo ile python shop/demo.py arasındaki fark nedir?

  • A. -m yalnız __init__.py dosyalarını çalıştırır
  • B. -m modülü import sistemi üzerinden paket içindeki tam adıyla bulup çalıştırır; doğrudan dosya yolu vermek paket bağlamını koruyamayabilir
  • C. -m yalnızca kurulu PyPI paketleri için çalışır
  • D. -m dosyayı derleyip çalıştırır, diğeri yorumlar
  • E. İkisi tümüyle aynıdır; -m yalnız daha kısadır

Madde 5. Kodu dosyalara ayırırken hangi ölçüt kullanılmalıdır?

  • A. Alfabetik sıra
  • B. Yazılma tarihine göre gruplama
  • C. Sınıfların aynı kavramsal sorumluluğa ait olup olmadığı ve bir modüldeki değişikliğin kaç modülü etkilediği
  • D. Dosya başına en fazla 100 satır kuralı
  • E. Her sınıf mutlaka kendi dosyasında olmalıdır

Madde 6. a.py b.py’yi, b.py de a.py’yi import ediyorsa ilk sorulacak soru nedir?

  • A. “Hangi dosya önce yazıldı?”
  • B. “Import satırlarını fonksiyon içine taşısam çözülür mü?”
  • C. “Python sürümünü yükseltmeli miyim?”
  • D. “Bu iki modül gerçekten birbirini bilmek zorunda mı?” — döngüsel bağımlılık çoğu zaman sorumlulukların fazla iç içe geçtiğini gösterir
  • E. “Dosyaları tek dosyada birleştirsem olur mu?”

Madde 7. Aşağıdaki program ne yazdırır?

def add(a: int, b: int) -> int:
    return a + b

print(add("a", "b"))
  • A. None
  • B. 0
  • C. TypeError; ipucu int dışındaki türleri reddeder
  • D. Sözdizimi hatası
  • E. ab; tip ipucu çalışma zamanında türü zorlamaz

Madde 8. Tip ipucu ne sağlar?

  • A. Okuyucuya niyeti gösterir ve editör ile statik analiz araçlarına bilgi verir
  • B. Programı daha hızlı çalıştırır
  • C. Sınıfların otomatik olarak __eq__ kazanmasını sağlar
  • D. Değerleri kendiliğinden belirtilen türe dönüştürür
  • E. Yanlış türde argüman verilmesini engeller

Madde 9. def find(self, code: str) -> Product | None: imzası çağırana ne söyler?

  • A. Metodun hiçbir zaman None döndürmeyeceğini
  • B. Aramanın başarısız olabileceğini; çağıranın None durumunu kontrol etmesi gerektiğini
  • C. Python’ın None dönüşünde kendiliğinden hata yükselteceğini
  • D. Metodun iki değer birden döndürdüğünü
  • E. code parametresinin isteğe bağlı olduğunu

Madde 10. def choose_friend(self) -> "Member | None": yazımında tırnak ne işe yarar?

  • A. Annotation’ı bir yorum satırına çevirir
  • B. Dönüş değerinin metin olduğunu bildirir
  • C. Henüz tanımı tamamlanmamış sınıf adına ileri referans (forward reference) kurar; annotation daha sonra çözümlenir
  • D. Python 3.10 öncesinde | sözdizimini geçerli kılar
  • E. Metodun çalışma zamanında tür denetimi yapmasını sağlar

Madde 11. from __future__ import annotations satırı ne yapar, ne yapmaz?

  • A. İleri referansları kullanılamaz hâle getirir
  • B. typing modülünü kendiliğinden import eder
  • C. Python 3.9’da X | None yazımını geçerli kılar
  • D. Annotation’ların değerlendirilmesini erteler; ancak eski bir Python sürümüne yeni sözdizimi eklemez
  • E. Bütün tip ipuçlarını çalışma zamanında zorunlu kılar

Madde 1 — Doğru: D. İstenirse paket yüklenirken çalışacak kodu veya dışarıya sunulacak adları da içerebilir; başlangıçta boş bırakmak yeterlidir.

  • A yanlış: Boş dosya da sınırı belirtmeye yeter.
  • B yanlış: Yerel paketlerde de kullanılır.
  • C yanlış: Sınıflar ayrı modüllerde durur.
  • E yanlış: Paket çalıştırma için __main__.py kullanılır.

Madde 2 — Doğru: E. Kendi yazdığınız bir paket bulunamıyorsa sorun çoğu zaman PyPI’dan bir şey kurmamak değil, proje kökünden doğru çalıştırmamaktır.

  • A yanlış: Hatayı gizler; nedeni ortadan kaldırmaz.
  • B yanlış: Paket sizin yazdığınızsa PyPI’da yoktur; yanlış bir paket kurulabilir.
  • C yanlış: Yorumlayıcıyla ilgili bir sorun değildir.
  • D yanlış: Paket sınırını kaldırır; sorunu büyütür.

Madde 3 — Doğru: A. Çalışma dizini import yolunu doğrudan etkilediği için teşhisin ilk adımıdır.

  • B yanlış: İşletim sistemi adını verir; çalışma dizinini değil.
  • C yanlış: Kurulu paketleri listeler.
  • D yanlış: Yorumlayıcı sürümünü verir; dizini değil.
  • E yanlış: İçeriği gösterir ama tam yolu doğrudan söylemez.

Madde 4 — Doğru: B. Özellikle göreli import kullanan modüllerde proje kökünden -m ile çalıştırmak doğru paket bağlamını korur.

  • A yanlış: Belirtilen modülü çalıştırır.
  • C yanlış: Yerel paketler için de kullanılır.
  • D yanlış: İkisinde de aynı yürütme modeli kullanılır.
  • E yanlış: Bağlam farkı gerçektir ve göreli import’ları etkiler.

Madde 5 — Doğru: C. Dosyaya ayırma “her sınıf ayrı dosyada olsun” demek değildir; modül sınırı bir sorumluluk sınırıdır.

  • A yanlış: Düzenleme tercihidir; tasarım ölçütü değildir.
  • B yanlış: Kavramsal ilişkiyle ilgisi yoktur.
  • D yanlış: Satır sayısı sorumluluğu ölçmez.
  • E yanlış: Mekanik bir kuraldır; birlikte değişen sınıfları gereksizce ayırır.

Madde 6 — Doğru: D. İleri çözüm tekniklerinden önce tasarımın kendisi sorgulanmalıdır.

  • A yanlış: Yazım sırası bağımlılığı açıklamaz.
  • B yanlış: Belirtiyi gizler; sorumluluk dağılımını düzeltmez.
  • C yanlış: Sürümle ilgili bir sorun değildir.
  • E yanlış: Döngüyü kaldırır ama modül sınırını tümüyle yok eder.

Madde 7 — Doğru: E. İpucu okuyucuya ve statik analiz araçlarına bilgi verir; yorumlayıcı çağrıyı engellemez ve str toplaması birleştirme yapar.

  • A yanlış: Fonksiyon birleştirme sonucunu döndürür.
  • B yanlış: Sessiz bir dönüşüm yapılmaz.
  • C yanlış: Böyle bir çalışma zamanı denetimi yoktur.
  • D yanlış: Annotation’lar geçerli sözdizimidir.

Madde 8 — Doğru: A. Varsayılan olarak çalışma zamanında tür zorlaması yapmaz; kazanç görünürlüktedir.

  • B yanlış: Yürütme hızını değiştirmez.
  • C yanlış: Bu dataclass gibi araçların işidir.
  • D yanlış: Dönüşüm yapılmaz.
  • E yanlış: Engellemez; yalnız araçlar uyarabilir.

Madde 9 — Doğru: B. Tip ipucu sözleşmeyi görünür kılar ama None değerini ortadan kaldırmaz. product.code erişiminden önce kontrol yine programcının sorumluluğundadır.

  • A yanlış: | None tam olarak bu olasılığı bildirir.
  • C yanlış: Böyle bir otomatik denetim yoktur.
  • D yanlış: Tek değer döner; iki olası tür bildirilir.
  • E yanlış: İpucu dönüş değerine aittir; parametre zorunludur.

Madde 10 — Doğru: C. Sınıf gövdesi içinde sınıfın kendi adı henüz bağlanmamıştır; tırnak bu sorunu çözer.

  • A yanlış: Annotation olarak kalır; yalnız değerlendirilmesi ertelenir.
  • B yanlış: Bildirilen tür Member | Nonedır.
  • D yanlış: Tırnak parse sorununu çözmez; yalnız değerlendirmeyi erteler.
  • E yanlış: Çalışma zamanı denetimi eklemez.

Madde 11 — Doğru: D. Python 3.9 yorumlayıcısının X | None ifadesini 3.10 gibi ayrıştırmasını sağlamaz; bu yüzden ders ortamı yine Python 3.10+ olmalıdır.

  • A yanlış: Birçok ileri referansı tırnaksız yazmayı kolaylaştırır.
  • B yanlış: Import eklemez.
  • C yanlış: Ayrıştırma sorununu çözmez.
  • E yanlış: Tam tersine, değerlendirmeyi erteler.

Tek sayfa özet

  • Bu ders için Python 3.10+ kullanın; sürümü ve seçili yorumlayıcıyı doğrulayın.
  • __init__.py paket 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 -m bir 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 | None bulunamama durumunu görünür kılar.
  • İleri referanslar sınıfların birbirine/self’e tip düzeyinde referans vermesinde kullanılır.

Bu bölümün kazanımları

Bu bölümü bitiren öğrenci:

  • Basit bir paket düzeni kurar ve __init__.py dosyasının rolünü açıklar.
  • ModuleNotFoundError karşısında önce çalışma dizinini ve proje kökünü denetler.
  • python app.py ile python -m paket.modul çalıştırmalarını ayırır.
  • Modül sınırını sorumluluk sınırı olarak kurar.
  • Tip ipucunun çalışma zamanında tür zorlamadığını belirler.
  • T | None yazımını kullanır ve çağıranın None kontrolünü hâlâ yapması gerektiğini bilir.
  • İleri referansı tanır ve from __future__ import annotations etkisini açıklar.
Back to top