İçeriğe geç
Educora
İleri20 dk22 / 42

Paketler, modüller ve sanal ortamlar

Projeni profesyonel gibi kur: modül ve paket farkı, `__init__.py`, göreli içe aktarmalar, `if __name__ == '__main__'`, `venv`, `pip`, `requirements.txt`, `pyproject.toml` ve sürüm belirteçleri.

Kendini test et
Bu derste öğreneceklerin
  • Modül, paket, sys.path ve sys.modules kavramlarını açıklamak
  • Bir dosyanın hem modül hem betik olarak çalışması için if __name__ == '__main__' kullanmak
  • venv ile sanal ortam oluşturmak, pip ile paket kurmak ve bağımlılıkları kaydetmek
  • requirements.txt ve pyproject.toml dosyalarını okumak ve sürüm belirteçlerini anlamak

Projelerinden biri Django 4, diğeri Django 5 istiyor; paketi tüm bilgisayara kurarsan projelerden biri mutlaka bozulur. Bir takım arkadaşın kodunu çalıştırır ve ModuleNotFoundError alır, çünkü senin kurduğun kütüphaneler onun bilgisayarında yoktur. Gerçek projeler üç kurala uyar: kod modüllere ve paketlere bölünür, her projenin kendi sanal ortamı olur ve bağımlılıklar sürümleriyle birlikte bir dosyaya kaydedilir.

Modüller ve paketler

Modül, herhangi bir .py dosyasıdır. import pricing yazdığında Python dosyayı sys.path listesindeki klasörlerde arar, dosyanın en üst düzeydeki kodunu bir kez çalıştırır ve modül nesnesini sys.modules içinde saklar; sonraki içe aktarmalar hazır nesneyi kullanır. Paket, modüllerden oluşan bir klasördür; sıradan bir pakette __init__.py dosyası bulunur ve paket ilk kez içe aktarıldığında çalışır. Adlar noktalı yazılır: shop.pricing, shop paketindeki pricing modülüdür. Tipik bir proje şöyle görünür:

Text
shop/
├── pyproject.toml
├── requirements.txt
├── .venv/                 (not in git)
├── src/
│   └── shop/
│       ├── __init__.py
│       ├── pricing.py
│       └── cli.py
└── tests/
    └── test_pricing.py
“src layout”: paketin kodu src/ içinde, testler ise ayrıca tests/ içinde durur.

Aşağıdaki örnek geçici bir klasörde küçük bir paket oluşturur ve onu üç farklı yolla içe aktarır (sys.modules.pop satırları yalnızca örneğin yeniden çalıştırılabilmesi içindir):

Python
import importlib
import sys
import tempfile
from pathlib import Path

root = Path(tempfile.mkdtemp())
(root / 'shop').mkdir()
(root / 'shop' / '__init__.py').write_text('from .pricing import final_price\n')
(root / 'shop' / 'pricing.py').write_text(
    'VAT = 0.18\n'
    'print("running pricing.py as", __name__)\n'
    'def final_price(net):\n'
    '    return round(net * (1 + VAT), 2)\n'
)
sys.path.insert(0, str(root))
sys.modules.pop('shop', None)
sys.modules.pop('shop.pricing', None)
importlib.invalidate_caches()

import shop
import shop.pricing
from shop import final_price
print(final_price(100), shop.pricing.VAT)
print(__name__)
▸ Beklenen çıktı
running pricing.py as shop.pricing
118.0 0.18
__main__

pricing.py, üç içe aktarmaya rağmen yalnızca bir kez çalıştı. Modülün içinde __name__ onun tam adıdır (shop.pricing); doğrudan başlatılan dosyada ise '__main__''dir. __init__.py içindeki from .pricing import final_price bir göreli içe aktarmadır: nokta “bu paket” demektir. Bu satır sayesinde kullanıcılar iç yapıyı bilmeden from shop import final_price yazabilir.

if __name__ == '__main__': betik mi, modül mü

Bir dosya hem içe aktarılabilir bir modül hem de çalıştırılabilir bir betik olabilir. if __name__ == '__main__': koşulu, betik kodunu yalnızca dosya doğrudan başlatıldığında çalıştırır, içe aktarıldığında değil. python -m shop.cli komutu modülü paketinin bir parçası olarak bulur ve betik olarak çalıştırır; bu yüzden paket içindeki içe aktarmalar çalışmaya devam eder:

Python
import sys
from shop.pricing import final_price

def main() -> int:
    for arg in sys.argv[1:]:
        print(f'{arg} -> {final_price(float(arg))}')
    return 0

if __name__ == '__main__':
    sys.exit(main())
src/shop/cli.py dosyası
Terminal
$ python -m shop.cli 10 25.5
10 -> 11.8
25.5 -> 30.09

Sanal ortamlar: venv ve pip

Sanal ortam olmadan tüm projeler tek bir site-packages klasörünü paylaşır ve paket sürümleri çakışır. Sanal ortam, projenin içinde kendi Python yorumlayıcısı ve kendi paketleri olan bir klasördür (genellikle .venv). Onu standart venv modülü oluşturur, paketleri ise pip kurar:

  1. 1
    Oluştur

    Proje klasöründe: python -m venv .venv.

  2. 2
    Etkinleştir

    Windows: .venv\Scripts\activate; macOS ve Linux: source .venv/bin/activate. Komut isteminin başında (.venv) görünür.

  3. 3
    Kur

    python -m pip install requests — paket yalnızca bu ortama kurulur.

  4. 4
    Kaydet

    python -m pip freeze > requirements.txt, kurulu paketlerin tam sürümlerini bir dosyaya yazar.

  5. 5
    Yeniden kur

    Takım arkadaşın kendi ortamını oluşturur ve python -m pip install -r requirements.txt çalıştırır.

  6. 6
    Çık

    deactivate komutu seni normal komut istemine döndürür.

Terminal
$ python -m venv .venv
$ source .venv/bin/activate
(.venv) $ python -m pip list
Package Version
------- -------
pip     25.2
(.venv) $ python -m pip install requests
Collecting requests
...
Installing collected packages: urllib3, idna, charset-normalizer, certifi, requests
Successfully installed certifi-2024.8.30 charset-normalizer-3.4.0 idna-3.10 requests-2.32.3 urllib3-2.2.3
(.venv) $ python -m pip freeze
certifi==2024.8.30
charset-normalizer==3.4.0
idna==3.10
requests==2.32.3
urllib3==2.2.3
Yeni ortam boştur; requests kendi bağımlılıklarını da getirir. Sürüm numaraları kurulum gününe göre farklı olacaktır.

requirements.txt ve pyproject.toml

requirements.txt basit bir listedir: her satırda bir paket, isteğe bağlı olarak bir sürüm belirteciyle. Uygulamalarda tam sürümleri (==) sabitlemek yaygındır; böylece program her bilgisayarda aynı kütüphanelerle çalışır:

Text
# requirements.txt
requests==2.32.3
fastapi>=0.110,<1.0
python-dotenv~=1.0
BelirteçAnlamı
==2.32.3tam olarak bu sürüm
>=0.110,<1.0aralık: 0.110 ya da daha yeni, ama 1.0'dan eski
~=1.4uyumlu sürüm: >=1.4 ve <2.0
(yok)herhangi bir sürüm — riskli

Modern bir projenin ana dosyası **pyproject.toml**'dur: ad, sürüm, desteklenen Python sürümleri, bağımlılıklar, yalnızca geliştirme için gereken araçlar ve komut satırı betikleri burada yazılır. Proje klasöründe python -m pip install -e . komutu paketi “düzenlenebilir” kipte kurar: kod değiştikçe yeniden kurmak gerekmez ve [project.scripts] bölümündeki shop komutu hemen çalışır.

Text
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"

[project]
name = "shop"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = ["requests>=2.31"]

[project.optional-dependencies]
dev = ["pytest>=8"]

[project.scripts]
shop = "shop.cli:main"
pyproject.toml dosyası (TOML biçimi). Geliştirme araçları python -m pip install -e ".[dev]" ile kurulur.
Alıştırma

parse_requirements(text) fonksiyonunu yaz: bir requirements.txt metninden {paket: belirteç} sözlüğü döndürsün. Boş satırları ve # ile başlayan yorumları atla; belirteç yoksa değer 'any version' olsun.

Alıştırma · Python
import re

requirements = '''
# web
requests==2.32.3
fastapi>=0.110,<1.0

python-dotenv~=1.0
numpy
'''

def parse_requirements(text):
    result = {}
    # go through the lines, skip blanks and comments,
    # split each line into the package name and the specifier
    return result

for name, spec in parse_requirements(requirements).items():
    print(f'{name}: {spec}')
▸ Beklenen çıktı
requests: ==2.32.3
fastapi: >=0.110,<1.0
python-dotenv: ~=1.0
numpy: any version
Alıştırma

Sürümleri karakter dizisi olarak sıralamak yanlıştır: '0.10.0', '0.9.3''ten önce gelir. Sürümlerin doğru sıralanması ve en yenisinin yazdırılması için version_key fonksiyonunu yaz.

Alıştırma · Python
versions = ['0.10.0', '0.9.3', '1.0.0', '0.1.0', '0.9.12']

def version_key(v):
    # turn '0.10.0' into something that compares correctly
    return v

print(sorted(versions))
print(sorted(versions, key=version_key))
print('latest:', max(versions, key=version_key))
▸ Beklenen çıktı
['0.1.0', '0.10.0', '0.9.12', '0.9.3', '1.0.0']
['0.1.0', '0.9.3', '0.9.12', '0.10.0', '1.0.0']
latest: 1.0.0

Önemli noktalar

  • Modül bir .py dosyası, paket ise modüllerden oluşan bir klasördür; modülün kodu ilk içe aktarmada bir kez çalışır ve sys.modules'ta saklanır.
  • __name__, doğrudan çalıştırılan dosyada '__main__''dir; if __name__ == '__main__': betik kodunun içe aktarmada çalışmasını engeller.
  • Her projeye kendi sanal ortamını ver: python -m venv .venv, etkinleştir, python -m pip install ....
  • Bağımlılıklar requirements.txt ya da pyproject.toml'a kaydedilir; ==, >=,< ve ~= sürümleri sınırlar.
  • .venv git'e girmez: ortam her zaman bağımlılık dosyasından yeniden oluşturulur.

Kendini test et

10 soru. Her doğru cevap XP kazandırır.

1 / 10
python -m venv .venv komutu ne yapar?