← Kursa Dön
📄 Text · 12 min

Modül ve Import Sistemi

Programın büyüdükçe her şeyi tek dosyaya yazmak sürdürülemez hale gelir. 500 satırlık bir dosyada kaybolursun, fonksiyon isimlerini hatırlayamazsın, farklı projelerde aynı kodu tekrar yazarsın. İşte modüller tam bu sorunu çözer.


Modül Nedir?

Modül, Python kodunu içeren bir dosyadır. Her .py dosyası bir modüldür. Bu kadar basit.

🧰 Araç Kutusu Analojisi

Modülleri bir araç kutusu gibi düşün. Evde tamir yapacaksan tüm aletleri bir torbaya atmazsın — tornavida takımı bir bölmede, çekiçler bir bölmede, boya malzemeleri ayrı bir kutuda. Her kutunun içinde ne olduğunu bilirsin ve sadece ihtiyacın olanı alırsın.

Python modülleri de böyle:

  • math kutusu → matematiksel aletler

  • os kutusu → işletim sistemi aletleri

  • json kutusu → JSON işleme aletleri

  • senin_modul.py → senin özel aletlerin

Her alete tek tek erişebilirsin (from math import sqrt) ya da tüm kutuyu alabilirsin (import math).

Kendi Modülünü Oluştur

# matematik_yardimci.py
"""Matematik yardımcı fonksiyonları."""

PI = 3.14159265358979

def daire_alan(yaricap):
    """Dairenin alanını hesaplar."""
    return PI * yaricap ** 2

def daire_cevre(yaricap):
    """Dairenin çevresini hesaplar."""
    return 2 * PI * yaricap

def faktoriyel(n):
    """n! hesaplar."""
    if n <= 1:
        return 1
    return n * faktoriyel(n - 1)

def asal_mi(sayi):
    """Sayının asal olup olmadığını kontrol eder."""
    if sayi < 2:
        return False
    for i in range(2, int(sayi ** 0.5) + 1):
        if sayi % i == 0:
            return False
    return True

Bu dosya artık bir modül. Başka dosyalardan kullanabiliriz.


import Yöntemleri

Python'da modül import etmenin birkaç yolu var. Her birinin yeri ve zamanı farklı.

1. import modül_adı

import matematik_yardimci

alan = matematik_yardimci.daire_alan(5)
print(f"Alan: {alan}")

cevre = matematik_yardimci.daire_cevre(5)
print(f"Çevre: {cevre}")

print(f"PI: {matematik_yardimci.PI}")

Tüm modülü import eder. Her kullanımda modül_adı.fonksiyon yazarsın. Avantajı: Nereden geldiği açık, isim çakışması olmaz.

2. from modül import öğeler

from matematik_yardimci import daire_alan, PI

alan = daire_alan(5)    # Doğrudan kullanabilirsin
print(f"PI: {PI}")
# faktoriyel(5)  # NameError — import etmedik!

Sadece ihtiyacın olan öğeleri import eder. Kısa ve okunabilir. Ama dikkat: aynı isimde başka bir fonksiyonunuz varsa çakışma olur.

3. from modül import * (Kaçın!)

from matematik_yardimci import *

# Tüm public isimler import edilir
alan = daire_alan(5)
print(asal_mi(17))

Bu kullanımdan kaçın. Neden? Hangi isimlerin import edildiğini bilemezsin, isim çakışmaları oluşabilir, IDE'ler kodu analiz edemez.

# Tehlikeli senaryo
from modül_a import *   # count() fonksiyonu var
from modül_b import *   # count() fonksiyonu da burada var!
count()                  # Hangisi çalışır? modül_b'ninki!

4. as ile Takma Ad (Alias)

import matematik_yardimci as mat

alan = mat.daire_alan(5)
print(mat.PI)

# Yaygın alias'lar
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

Uzun modül isimlerini kısaltmak için kullanılır. Topluluğun benimsediği standart kısaltmalar var (np, pd, plt).

# from ... import ... as da çalışır
from matematik_yardimci import daire_alan as alan_hesapla

sonuc = alan_hesapla(10)

Import Stili Önerileri

# DOĞRU — standart kütüphaneler, üçüncü parti, yerel modüller ayrı gruplar
import os
import sys
import json

import requests
import numpy as np

from matematik_yardimci import daire_alan
from veritabani import baglan

# YANLIŞ — karışık import
from os import *
import requests, json, sys
from matematik_yardimci import *

PEP 8'e göre import'lar şu sırada ve gruplar halinde yazılır:

  1. Standart kütüphane (os, sys, json)

  2. Üçüncü parti paketler (requests, numpy)

  3. Yerel modüller (matematik_yardimci)

Her grup arasında boş satır bırak.


__name__ == "__main__" Pattern'i

Python'da en sık karşılaşacağın pattern'lerden biri:

if __name__ == "__main__":
    # Bu kod sadece dosya doğrudan çalıştırıldığında çalışır
    pass

Ama bu ne demek?

__name__ Değişkeni

Her Python dosyasının özel bir __name__ değişkeni var:

  • Dosya doğrudan çalıştırılırsa: __name__ = "__main__"

  • Dosya import edilirse: __name__ = modül adı

# hesapla.py
print(f"__name__ = {__name__}")

def topla(a, b):
    return a + b

if __name__ == "__main__":
    # Test kodu — sadece doğrudan çalışınca
    print(topla(3, 5))
# Doğrudan çalıştır
$ python hesapla.py
__name__ = __main__
8

# Başka dosyadan import et
$ python -c "import hesapla"
__name__ = hesapla
# topla(3, 5) ÇALIŞMAZ — if bloğu devreye girmez

Neden Kullanılır?

# veritabani.py
"""Veritabanı yardımcı modülü."""

def baglan(host, port):
    """Veritabanına bağlanır."""
    print(f"Bağlanılıyor: {host}:{port}")
    return {"baglanti": True}

def sorgu_calistir(baglanti, sql):
    """SQL sorgusu çalıştırır."""
    print(f"Sorgu: {sql}")
    return []

# Bu pattern olmadan test kodu her import'ta çalışır!
if __name__ == "__main__":
    # Modülü test et
    conn = baglan("localhost", 5432)
    sonuc = sorgu_calistir(conn, "SELECT * FROM users")
    print(f"Sonuç: {sonuc}")

if __name__ == "__main__": olmadan baglan() ve sorgu_calistir() test kodları, modül import edildiğinde de çalışırdı. Bu pattern ile test kodunu izole edersin.

Bu pattern ayrıca bir dosyanın hem modül hem de script olarak kullanılmasını sağlar. Çok yaygın bir Python convention'ıdır.


Modül Arama Sırası: sys.path

import modül yazdığında Python bu modülü nerede arar?

  1. Önce mevcut dizin (script'in bulunduğu klasör)

  2. PYTHONPATH ortam değişkenindeki dizinler

  3. Standart kütüphane dizinleri

  4. Site-packages (pip ile yüklenen paketler)

import sys

# Python'ın modül aradığı dizinleri gör
for yol in sys.path:
    print(yol)

Çıktı gibi bir şey olur:

/home/kullanici/projeler/uygulama   ← mevcut dizin
/usr/lib/python3.11
/usr/lib/python3.11/lib-dynload
/home/kullanici/.local/lib/python3.11/site-packages
/usr/lib/python3.11/site-packages

sys.path'e Dizin Ekleme

import sys

# Runtime'da dizin ekle
sys.path.insert(0, "/home/kullanici/ozel_moduller")

# Şimdi o dizindeki modülleri import edebilirsin
import ozel_modul

Bu çalışır ama iyi bir pratik değil. Daha iyi yöntemler:

  • Paketi pip install -e . ile kur (editable mode)

  • PYTHONPATH ortam değişkenini ayarla

  • Düzgün proje yapısı kullan (bunu ileride göreceğiz)

Modül Bulunamazsa

try:
    import olmayan_modul
except ModuleNotFoundError:
    print("Modül bulunamadı! pip install ile yükleyin.")

Üçüncü parti paketlerin yüklü olup olmadığını kontrol etmek için kullanışlı.


dir() ile Modül İçeriğini Keşfetme

dir() fonksiyonu bir modülün (veya herhangi bir nesnenin) tüm isimlerini listeler:

import math

# math modülündeki tüm isimler
print(dir(math))
# ['__doc__', '__name__', ..., 'ceil', 'cos', 'e', 'exp', 'floor', 
#  'log', 'pi', 'pow', 'sin', 'sqrt', 'tan', ...]

__ ile başlayanlar özel isimler. Bunları filtreleyebilirsin:

import json

# Sadece public isimleri göster
public = [isim for isim in dir(json) if not isim.startswith("_")]
print(public)
# ['JSONDecodeError', 'JSONDecoder', 'JSONEncoder', 'dump', 'dumps', 'load', 'loads', ...]

help() ile Dokümantasyon

import math

# Modül hakkında bilgi
help(math)

# Belirli fonksiyon hakkında bilgi
help(math.sqrt)
# sqrt(x, /)
#     Return the square root of x.

Modül Metadata'sı

import json

print(json.__name__)     # json
print(json.__file__)     # /usr/lib/python3.11/json/__init__.py
print(json.__doc__[:80]) # JSON (JavaScript Object Notation)...
print(json.__version__)  # Bazı modüllerde var — hepsi değil

__all__: Public API Tanımlama

from modül import * kullanıldığında hangi isimlerin import edileceğini __all__ listesi belirler:

# yardimcilar.py
"""Yardımcı fonksiyonlar modülü."""

__all__ = ["topla", "cikar", "carp"]  # Sadece bunlar public

def topla(a, b):
    return a + b

def cikar(a, b):
    return a - b

def carp(a, b):
    return a * b

def _dahili_yardimci():
    """Bu private — dışarıdan kullanılmamalı."""
    pass

def gizli_fonksiyon():
    """__all__'da yok — from * ile import edilmez."""
    pass
from yardimcilar import *

topla(1, 2)            # ✅ Çalışır — __all__'da var
cikar(5, 3)            # ✅ Çalışır
gizli_fonksiyon()      # ❌ NameError — __all__'da yok
_dahili_yardimci()     # ❌ NameError — private ve __all__'da yok

Ama doğrudan import hâlâ çalışır:

from yardimcilar import gizli_fonksiyon  # ✅ Çalışır!

__all__ sadece from ... import *'ı etkiler. Doğrudan import'u engellemez — bu Python'ın felsefesi: "Hepimiz yetişkiniz."

💡 İpucu: Her modülde __all__ tanımlamak iyi bir pratik. Bu, modülünün public API'sini açıkça belirtir ve from ... import * kullanımını güvenli hale getirir. Ama yine de from ... import * yerine açık import tercih et.


Circular Import Sorunu ve Çözümleri

İki modül birbirini import ederse circular import oluşur:

# modül_a.py
from modül_b import fonksiyon_b

def fonksiyon_a():
    return "A"

# modül_b.py
from modül_a import fonksiyon_a

def fonksiyon_b():
    return "B"

Bu çalışmaz! Python modülü yüklerken diğerini import etmeye çalışır, ama o da ilkini bekler — kısır döngü.

Çözüm 1: Import'u Fonksiyon İçine Taşı

# modül_a.py
def fonksiyon_a():
    from modül_b import fonksiyon_b  # Lazy import
    return fonksiyon_b() + " ve A"

Import ihtiyaç anında yapılır. Performans kaybı ihmal edilebilir (Python modülü cache'ler).

Çözüm 2: Modül Yapısını Yeniden Düzenle

# Ortak kodları üçüncü bir modüle taşı
# ortak.py
def ortak_fonksiyon():
    return "ortak"

# modül_a.py
from ortak import ortak_fonksiyon

# modül_b.py  
from ortak import ortak_fonksiyon

Çözüm 3: Import'u Dosya Sonunda Yap

# modül_a.py
def fonksiyon_a():
    return "A"

# Dosya sonunda import — tüm tanımlamalar yapıldıktan sonra
from modül_b import fonksiyon_b

En iyi çözüm genellikle Çözüm 2 — modül yapısını yeniden düzenlemek. Circular import genellikle kötü tasarımın işaretidir.


Relative Import

Paket (package) içindeki modüller birbirini relative import ile import edebilir:

mypackage/
├── __init__.py
├── core.py
├── utils.py
└── sub/
    ├── __init__.py
    └── helper.py
# mypackage/core.py
from . import utils              # Aynı dizindeki modül
from .utils import format_tarih  # Aynı dizindeki modülden fonksiyon
from .sub import helper          # Alt paketten modül
from .sub.helper import yardim   # Alt paketten fonksiyon
# mypackage/sub/helper.py
from .. import core              # Üst dizindeki modül
from ..utils import format_tarih # Üst dizindeki modülden fonksiyon
  • . = mevcut paket (dizin)

  • .. = üst paket

  • ... = iki üst paket (nadiren kullanılır)

Relative vs Absolute Import

# Absolute import — tam yol
from mypackage.utils import format_tarih
from mypackage.sub.helper import yardim

# Relative import — göreli yol
from .utils import format_tarih
from .sub.helper import yardim

PEP 8 absolute import'u tercih eder — daha açık ve okunabilir. Ama paket içi import'larda relative import da kabul edilebilir, özellikle paket adı uzunsa veya paket yeniden adlandırılabiliyorsa.

⚠️ Dikkat: Relative import sadece paket içinde çalışır. Doğrudan python dosya.py ile çalıştırılan script'lerde relative import çalışmaz. Script'i paket içinden çalıştırmak için python -m paket.modül kullan.


reload(): Modülü Yeniden Yükleme

Python bir modülü ilk import'ta yükler ve cache'ler. Aynı modülü tekrar import etsen bile yeniden yüklenmez:

import matematik_yardimci
# ... matematik_yardimci.py dosyasını değiştirdin ...
import matematik_yardimci  # YENİDEN YÜKLEMEZ — cache'den gelir

Geliştirme sırasında (özellikle REPL/Jupyter'da) modülü değiştirip yeniden yüklemek isteyebilirsin:

import importlib
import matematik_yardimci

# Modülü değiştirdikten sonra:
importlib.reload(matematik_yardimci)
# Şimdi güncel hali yüklendi

reload() Dikkat Edilecekler

import importlib
import mymodule

# reload ÖNCE modül import edilmiş olmalı
importlib.reload(mymodule)

# from ... import ile alınan isimler GÜNCELLENMEZler
from mymodule import fonksiyon  # Bu eski kalır!
importlib.reload(mymodule)       # mymodule güncellendi
# fonksiyon hâlâ ESKİ versiyonu gösterir!

# Doğru yöntem:
import mymodule
importlib.reload(mymodule)
mymodule.fonksiyon()  # Güncel versiyonu kullanır

reload() genellikle geliştirme ve debug amaçlı kullanılır. Production kodunda kullanma.


Modül Türleri

Python'da üç tür modül var:

1. Yerleşik Modüller (Built-in)

C ile yazılmış, Python yorumlayıcısına gömülü:

import sys
print(sys.builtin_module_names)
# ('_abc', '_io', '_thread', 'builtins', 'sys', ...)

2. Standart Kütüphane Modülleri

Python ile birlikte gelen modüller:

import os, json, math, datetime, collections, pathlib
# Hepsi Python kurulumu ile gelir — pip gerekmez

3. Üçüncü Parti Modüller

pip ile yüklenen paketler:

import requests   # pip install requests
import numpy      # pip install numpy
import flask      # pip install flask

Paketler (Packages)

Birden fazla modülü organize eden dizin yapısı pakettir. Bir dizinin paket olması için __init__.py dosyası içermesi gerekir (Python 3.3+'da opsiyonel ama önerilen).

mypackage/
├── __init__.py        ← Paketi tanımlar
├── core.py
├── utils.py
└── models/
    ├── __init__.py    ← Alt paket
    ├── user.py
    └── product.py
# __init__.py — Paket public API'si
from .core import ana_fonksiyon
from .utils import yardimci_fonksiyon

__all__ = ["ana_fonksiyon", "yardimci_fonksiyon"]
# Kullanım
import mypackage
mypackage.ana_fonksiyon()

from mypackage import ana_fonksiyon
from mypackage.models.user import User

__init__.py'nin Rolü

__init__.py bir paket import edildiğinde ilk çalışan dosyadır. Genellikle:

  • Public API'yi tanımlar (__all__)

  • Alt modüllerden önemli isimleri import eder

  • Paket seviyesi değişkenler tanımlar (__version__)

  • Boş bırakılır (sadece dizini paket olarak işaretler)

# mypackage/__init__.py
"""MyPackage — Harika bir paket."""

__version__ = "1.0.0"
__author__ = "Ali Yılmaz"

from .core import Engine
from .utils import helper

# Kullanıcı şöyle yapabilir:
# from mypackage import Engine
# print(mypackage.__version__)

Namespace Paketleri (Python 3.3+)

Python 3.3'ten itibaren __init__.py olmadan da paket oluşturabilirsin — bunlara namespace package denir:

myns/
├── modül_a.py
└── modül_b.py
# __init__.py YOK!
from myns import modül_a

Namespace paketleri birden fazla dizine yayılabilir — büyük organizasyonlarda faydalı. Ama basit projelerde __init__.py kullanmaya devam et.


Pratik: Küçük Bir Kütüphane

Öğrendiklerimizle bir yardımcı kütüphane oluşturalım:

textutils/
├── __init__.py
├── clean.py
├── analyze.py
└── transform.py
# textutils/clean.py
"""Metin temizleme fonksiyonları."""

def bosluk_temizle(metin):
    """Fazla boşlukları temizler."""
    return " ".join(metin.split())

def noktalama_temizle(metin):
    """Noktalama işaretlerini kaldırır."""
    import string
    return metin.translate(str.maketrans("", "", string.punctuation))
# textutils/analyze.py
"""Metin analiz fonksiyonları."""

from collections import Counter

def kelime_say(metin):
    """Kelimeleri sayar."""
    return len(metin.split())

def en_sik_kelimeler(metin, n=5):
    """En sık kullanılan n kelimeyi döndürür."""
    kelimeler = metin.lower().split()
    return Counter(kelimeler).most_common(n)
# textutils/transform.py
"""Metin dönüştürme fonksiyonları."""

def bas_harfler_buyuk(metin):
    """Her kelimenin baş harfini büyük yapar."""
    return metin.title()

def ters_cevir(metin):
    """Metni tersine çevirir."""
    return metin[::-1]
# textutils/__init__.py
"""TextUtils — Metin işleme kütüphanesi."""

__version__ = "1.0.0"

from .clean import bosluk_temizle, noktalama_temizle
from .analyze import kelime_say, en_sik_kelimeler
from .transform import bas_harfler_buyuk

__all__ = [
    "bosluk_temizle", "noktalama_temizle",
    "kelime_say", "en_sik_kelimeler",
    "bas_harfler_buyuk"
]

Kullanım:

from textutils import bosluk_temizle, kelime_say, en_sik_kelimeler

metin = "  Python   çok   güzel   bir   dil.  Python  ile  her  şey  mümkün.  "
temiz = bosluk_temizle(metin)
print(temiz)
print(f"Kelime sayısı: {kelime_say(temiz)}")
print(f"En sık: {en_sik_kelimeler(temiz, 3)}")

Özet

Bu derste Python modül sistemini öğrendik:

  • Modül bir .py dosyasıdır. import ile yüklersin, from ... import ile belirli öğeleri alırsın. as ile takma ad verirsin. from ... import *'dan kaçın.

  • `__name__ == "__main__"` pattern'i bir dosyanın hem modül hem script olarak kullanılmasını sağlar. Doğrudan çalıştırılınca test kodu çalışır, import edilince çalışmaz.

  • sys.path Python'ın modül arama yollarını içerir. Mevcut dizin, PYTHONPATH, standart kütüphane ve site-packages sırasıyla aranır.

  • `__all__` listesi modülün public API'sini tanımlar ve from ... import * ile hangi isimlerin geleceğini belirler.

  • Circular import iki modülün birbirini import etmesinden kaynaklanır. Çözüm: lazy import (fonksiyon içinde), üçüncü modüle çıkarma veya yapıyı yeniden düzenleme.

  • Paketler __init__.py içeren dizinlerdir. İç içe modülleri organize eder. Relative import (., ..) paket içi modülleri birbirine bağlar.