Bütünleştirici mikro uygulama

Bu bölümde yeni bir Python kavramı öğrenmeyeceğiz. Amaç, ilk 13 haftada öğrendiğimiz yapıları küçük ama gerçekçi bir problem üzerinde birlikte kullanmak ve aralarındaki bağlantıyı görünür hâle getirmektir.

Mikro uygulamamız bir kampüs kırtasiye satış raporu olacak. Bu kez veri kodun içine gömülü bir listeden gelmeyecek: CSV dosyasını okuyacak, ham kayıtları dönüştürecek, geçersiz kayıtları ayıracak, rapor üretecek ve temel davranışları küçük testlerle doğrulayacağız.

Bu uygulamada özellikle şunları yeniden kullanacağız:

Bu hafta neleri yapabilmelisiniz?

Bölümün sonunda:

  • Bir CSV dosyasından programa giren verinin akışını açıklayabilmeli,
  • Bir programı veri.py, rapor.py ve main.py gibi anlamlı modüllere ayırabilmeli,
  • Ham string alanlarını program içindeki uygun türlere dönüştürebilmeli,
  • Türkçe I / İ farkının normalleştirmeyi nasıl etkileyebileceğini fark edebilmeli,
  • Geçersiz bir dönüşümü None ile bildirmenin çağıran koda yüklediği sorumluluğu açıklayabilmeli,
  • is None ile başarısız dönüşümü kontrol edebilmeli,
  • Kayıtlardan toplam, grup özeti ve maksimum değer üretebilmeli,
  • Dosya bulunamaması gibi beklenen bir çalışma zamanı sorununu kontrollü biçimde ele alabilmeli,
  • Bir değişiklikten sonra assert kontrollerini yeniden çalıştırabilmeli,
  • AI tarafından önerilen küçük bir kod değişikliğini kabul kriterleri ve testlere göre inceleyebilmelisiniz.

Gereksinim, kabul kriteri, kod değişikliği ve test arasındaki doğrulama zinciri.

Gereksinimden teste

Problem ve veri dosyası

Kırtasiye satışları satislar.csv adlı bir dosyada tutuluyor. Türkçe Excel kurulumlarında sık görülen bir biçimi özellikle kullanalım: alanlar noktalı virgülle ayrılmış, ondalık değerlerde virgül kullanılmış olsun.

urun;fiyat;adet
 Kalem ;12,5;3
Defter;45;2
İĞNE;5,5;4
iğne;5,5;2
Silgi;8;abc
Cetvel;10;0

Bu küçük veri kümesinde bilinçli olarak birkaç durum var:

  • " Kalem " başta ve sonda boşluk içeriyor.
  • İĞNE ve iğne aynı ürün olarak gruplanmalı.
  • "12,5" ve "5,5" doğrudan float() ile dönüştürülemez; kontrollü biçimde ondalık virgülü ele almamız gerekir.
  • "abc" geçerli adet değildir.
  • 0 adetli satış, bu uygulamanın iş kuralına göre geçersizdir.

Beklenen özet:

Geçerli kayıt sayısı: 4
Geçersiz kayıt sayısı: 2
Toplam ciro: 160.50 TL
Ürün adetleri: {'kalem': 3, 'defter': 2, 'iğne': 6}
Ciroya en fazla katkı: defter (90.00 TL)
Note

Bu örnekte 12,5 gibi tek ondalık virgül içeren kontrollü veriyi replace(",", ".") ile dönüştüreceğiz. 1.234,56 gibi daha karmaşık yerel sayı biçimleri için bu basit yöntem genel çözüm değildir.

Uygulamanın dosya yapısı

Bu bölümde önceki haftalarda ayrı ayrı gördüğümüz dosya, modül ve test fikirlerini aynı küçük proje içinde birleştiriyoruz:

hafta14/
├── satislar.csv
├── veri.py
├── rapor.py
├── main.py
└── test_kontrolleri.py

satislar.csv dosyasından veri.py ve rapor.py modüllerine, oradan main.py ile çıktı raporuna ve test kontrollerine giden akış.

Bütünleştirici mikro uygulamanın akışı

Akışın zihinsel modeli:

satislar.csv
    ↓
veri.py: CSV'yi oku
    ↓
rapor.py: temizle → dönüştür → doğrula → özetle
    ↓
main.py: programı çalıştır ve raporu yazdır
    ↓
test_kontrolleri.py: temel davranışları doğrula
Tip

Buradaki amaç “çok dosyalı proje yapmak” değildir. Amaç sorumlulukları ayırmaktır: veriyi alma, hesaplama ve programı başlatma farklı işlerdir.

veri.py: CSV kayıtlarını okumak

  1. haftadaki Excel/CSV ayrıntılarını burada gerçekten kullanıyoruz:
import csv


def kayitlari_oku(dosya_yolu):
    kayitlar = []

    with open(
        dosya_yolu,
        "r",
        encoding="utf-8-sig",
        newline=""
    ) as dosya:
        okuyucu = csv.DictReader(dosya, delimiter=";")

        for satir in okuyucu:
            kayitlar.append(satir)

    return kayitlar

Burada veri.py henüz sayısal dönüşüm yapmıyor. CSV’den gelen ham alanları string olarak okuyor ve dönüştürme sorumluluğunu rapor.py dosyasına bırakıyor.

  • encoding="utf-8-sig", dosyanın başında BOM varsa ilk sütun adının \ufeffurun olmasını engeller.
  • delimiter=";", noktalı virgülle ayrılmış CSV’yi doğru sütunlara böler.
  • newline="", csv modülünün satır sonlarını kendisinin yönetmesini sağlar.

Türkçe ürün adını normalleştirmek

  1. haftada gördüğümüz önemli noktayı uygulamanın içine taşıyalım. Yalnızca .lower() kullanmak Türkçe İ için beklenmedik sonuç verebilir.
def urun_adi_temizle(urun):
    donusum = str.maketrans({"I": "ı", "İ": "i"})
    return urun.strip().translate(donusum).lower()

Kontroller:

assert urun_adi_temizle(" Kalem ") == "kalem"
assert urun_adi_temizle("İĞNE") == "iğne"
assert urun_adi_temizle("iğne") == "iğne"
assert urun_adi_temizle("ISPARTA") == "ısparta"

Böylece İĞNE ve iğne raporda iki ayrı anahtar hâline gelmez.

Warning

Bu normalleştirme Türkçe metin iş kuralına göre tasarlanmıştır. E-posta, kullanıcı adı veya uluslararası ürün kodlarında aynı dönüşüm otomatik olarak doğru kabul edilmemelidir.

Sayısal alanları güvenli çözmek

Fiyat için 0 değerini kabul edeceğiz; bu nedenle fonksiyonun adını sözleşmeyle uyumlu seçelim:

def negatif_olmayan_sayi_coz(metin):
    try:
        sayi = float(metin.strip().replace(",", "."))
    except (ValueError, AttributeError):
        return None

    if sayi < 0:
        return None

    return sayi

Adet ise gerçekten pozitif tam sayı olmalı; 0 adetli satış geçersizdir:

def pozitif_tam_sayi_coz(metin):
    try:
        sayi = int(metin.strip())
    except (ValueError, AttributeError):
        return None

    if sayi <= 0:
        return None

    return sayi

Bu adlandırma önemlidir. Önceki sürümde pozitif_sayi_coz("0") değeri 0 döndürüyor olsaydı, fonksiyon adı ile davranış arasında sözleşme çelişkisi oluşurdu.

None ve is None

  1. haftada None değerini “sonuç üretilemedi” sinyali olarak kullandık. Burada aynı kalıp var:
fiyat = negatif_olmayan_sayi_coz("abc")

if fiyat is None:
    print("Fiyat geçersiz")

None özel ve tekil bir Python nesnesidir. Bu nedenle None kontrolünde yaygın ve önerilen yazım:

if deger is None:
    ...

ve tersi:

if deger is not None:
    ...

şeklindedir.

Important

Bir fonksiyon geçersizlik için None döndürüyorsa çağıran kodun bunu kontrol etmesi gerekir. Kontrol edilmezse sorun daha sonra, örneğin None * 3 gibi daha anlaşılmaz bir yerde ortaya çıkabilir.

Bir ham kaydı dönüştürmek

Şimdi tek bir CSV kaydını program içi kayda dönüştürelim:

def kaydi_coz(kayit):
    urun_metni = kayit.get("urun")
    fiyat_metni = kayit.get("fiyat")
    adet_metni = kayit.get("adet")

    if urun_metni is None or fiyat_metni is None or adet_metni is None:
        return None

    urun = urun_adi_temizle(urun_metni)
    fiyat = negatif_olmayan_sayi_coz(fiyat_metni)
    adet = pozitif_tam_sayi_coz(adet_metni)

    if urun == "" or fiyat is None or adet is None:
        return None

    return {
        "urun": urun,
        "fiyat": fiyat,
        "adet": adet
    }

Burada üç ayrı doğrulama türü birleşiyor:

  • Beklenen sütun var mı?
  • Ürün adı boş mu?
  • Sayısal alanlar sözleşmeye uygun mu?

Tahmin et — hangi kayıtlar geçerli?

Aşağıdaki kodu çalıştırmadan önce her satırın sonucunu tahmin edin:

İlk iki kayıt sözlüğe dönüşmeli; son iki kayıt None üretmelidir.

Satır toplamı ve ürün bazında ciro

Geçerli bir kaydın ciro katkısı:

def satir_toplami(kayit):
    return kayit["fiyat"] * kayit["adet"]

Aynı ürünü hem adet hem ciro bakımından gruplayacağız:

urun_adetleri[urun] = urun_adetleri.get(urun, 0) + adet
urun_cirolari[urun] = urun_cirolari.get(urun, 0) + satir_cirosu

Bu, 10. ve 11. haftalardaki sözlük biriktirme kalıbının doğrudan uygulamasıdır.

Maksimum arama kalıbını yeniden kullanmak

Ciroya en fazla katkı yapan ürünü hazır max() kullanmadan, daha önce öğrendiğimiz maksimum arama kalıbıyla bulalım:

def en_yuksek_cirolu_urun(urun_cirolari):
    if len(urun_cirolari) == 0:
        return None

    en_urun = None
    en_ciro = None

    for urun, ciro in urun_cirolari.items():
        if en_ciro is None or ciro > en_ciro:
            en_urun = urun
            en_ciro = ciro

    return en_urun, en_ciro

Boş sözlükte anlamlı bir maksimum olmadığı için None döndürüyoruz. Bu tercih, “veri yok” durumunu 0 gibi gerçek bir sayısal sonuçla karıştırmaz.

rapor.py: ana rapor fonksiyonu

Parçaları birleştirelim:

def satis_raporu(ham_kayitlar):
    toplam_ciro = 0
    gecerli_sayisi = 0
    gecersiz_sayisi = 0
    urun_adetleri = {}
    urun_cirolari = {}

    for ham_kayit in ham_kayitlar:
        kayit = kaydi_coz(ham_kayit)

        if kayit is None:
            gecersiz_sayisi = gecersiz_sayisi + 1
            continue

        gecerli_sayisi = gecerli_sayisi + 1

        urun = kayit["urun"]
        adet = kayit["adet"]
        satir_cirosu = satir_toplami(kayit)

        toplam_ciro = toplam_ciro + satir_cirosu
        urun_adetleri[urun] = urun_adetleri.get(urun, 0) + adet
        urun_cirolari[urun] = urun_cirolari.get(urun, 0) + satir_cirosu

    return {
        "gecerli": gecerli_sayisi,
        "gecersiz": gecersiz_sayisi,
        "ciro": toplam_ciro,
        "urun_adetleri": urun_adetleri,
        "urun_cirolari": urun_cirolari,
        "lider": en_yuksek_cirolu_urun(urun_cirolari)
    }

Bu fonksiyonda yeni bir sözdizimi yoktur. Önceki haftalardan gelen kalıplar birleşmiştir:

  • for ile kayıtları dolaşma,
  • None kontrolü,
  • continue,
  • Sayaç ve biriktirici,
  • Sözlükle gruplama,
  • Fonksiyon çağrıları,
  • Maksimum arama.

Tarayıcıda bütün akışı deneyin

Gerçek projede satislar.csv dosyasını open() ile okuyacağız. Tarayıcı hücresinde ise aynı CSV metnini StringIO ile simüle edebiliriz:

Beklenen ana sonuçlar:

Geçerli kayıt sayısı: 4
Geçersiz kayıt sayısı: 2
Toplam ciro: 160.50 TL
Ürün adetleri: {'kalem': 3, 'defter': 2, 'iğne': 6}
Ciro lideri: ('defter', 90.0)

main.py: dosya hatasını yönetmek ve raporlamak

Gerçek dosyalarla çalışan ana program:

from veri import kayitlari_oku
from rapor import satis_raporu


def main():
    try:
        kayitlar = kayitlari_oku("satislar.csv")
    except FileNotFoundError as hata:
        print("Satış dosyası bulunamadı:", hata)
        return

    rapor = satis_raporu(kayitlar)

    print("Geçerli kayıt sayısı:", rapor["gecerli"])
    print("Geçersiz kayıt sayısı:", rapor["gecersiz"])
    print(f"Toplam ciro: {rapor['ciro']:.2f} TL")
    print("Ürün adetleri:", rapor["urun_adetleri"])

    if rapor["lider"] is not None:
        urun, ciro = rapor["lider"]
        print(f"Ciroya en fazla katkı: {urun} ({ciro:.2f} TL)")


if __name__ == "__main__":
    main()

Burada 13. haftadaki iki fikir birlikte görülür:

  • FileNotFoundError beklenen bir dosya problemi olarak ele alınır.
  • Main guard sayesinde main.py doğrudan çalıştırıldığında main() çağrılır.

Küçük regresyon kontrolleri

Kod çalışıyor görünse bile kritik davranışları açık beklentilerle kontrol edelim:

assert urun_adi_temizle("İĞNE") == "iğne"
assert urun_adi_temizle("iğne") == "iğne"

assert negatif_olmayan_sayi_coz("12,5") == 12.5
assert negatif_olmayan_sayi_coz("0") == 0
assert negatif_olmayan_sayi_coz("-1") is None
assert negatif_olmayan_sayi_coz("abc") is None

assert pozitif_tam_sayi_coz("3") == 3
assert pozitif_tam_sayi_coz("0") is None
assert pozitif_tam_sayi_coz("-2") is None

assert kaydi_coz({"urun": " ", "fiyat": "10", "adet": "1"}) is None

rapor = satis_raporu([
    {"urun": "İĞNE", "fiyat": "5,5", "adet": "4"},
    {"urun": "iğne", "fiyat": "5,5", "adet": "2"},
])

assert rapor["gecerli"] == 2
assert rapor["gecersiz"] == 0
assert rapor["urun_adetleri"]["iğne"] == 6
assert abs(rapor["ciro"] - 33.0) < 0.001

Son satırda doğrudan == yerine tolerans kullanılması, 7. haftada gördüğümüz kayan noktalı sayı testleriyle aynı ilkedir.

Hata ayıklama senaryosu

Program beklenen 160.50 TL yerine başka bir sonuç verirse bütün kodu aynı anda değiştirmeyin.

Şu sırayı izleyin:

  1. CSV’de kaç satır olduğunu kontrol edin.
  2. kayitlari_oku() sonrasında ilk birkaç ham kaydı inceleyin.
  3. Her ham kayıt için kaydi_coz() sonucunu kontrol edin.
  4. Silgi ve Cetvel kayıtlarının neden geçersiz olduğunu doğrulayın.
  5. İĞNE ve iğne değerlerinin aynı anahtara dönüştüğünü kontrol edin.
  6. Her geçerli kayıt için satir_toplami() sonucunu hesaplayın.
  7. urun_cirolari sözlüğünün her turdaki durumunu izleyin.
  8. Tek bir hipotezi sınayacak küçük değişiklik yapın.
  9. Tüm assert kontrollerini yeniden çalıştırın.

Bu süreç, 13. haftadaki sistematik debugging yaklaşımını gerçek bir veri akışına uygular.

AI ile kontrollü kod inceleme

Bu aşamada AI’den küçük bir değişiklik veya alternatif çözüm istemek mümkündür; fakat çıktıyı doğrudan kabul etmek dersin hedefi değildir.

Örneğin AI şu değişikliği önerebilir:

def pozitif_tam_sayi_coz(metin):
    sayi = int(metin)
    if sayi < 0:
        return None
    return sayi

İlk bakışta kısa ve makul görünüyor. Ancak kabul kriterlerimize göre iki sorun vardır:

  • "abc" için ValueError üretir; kontrollü başarısızlık yoktur.
  • 0 değeri kabul edilir; oysa bu uygulamada adet pozitif olmalıdır.

Dolayısıyla AI çıktısını değerlendirirken şu üç soruyu kullanın:

  1. Sözleşmeye uyuyor mu?
  2. Sınır ve geçersiz durum testlerini geçiyor mu?
  3. Mevcut davranışlardan birini bozuyor mu?
Important

AI’nin ürettiği kodun daha kısa veya daha “profesyonel” görünmesi, doğru olduğu anlamına gelmez. Bu derste kabul ölçütü açıklanabilir ve doğrulanmış davranıştır.

Sınırlı özellik ekleme

Hazır projeye aşağıdakilerden yalnızca birini eklemeyi deneyin:

  • Fiyatı 0 olan kayıtları ayrıca saymak,
  • Belirli bir ciro eşiğinin üzerindeki ürünleri listelemek,
  • Raporu rapor.txt dosyasına yazmak,
  • Geçersiz kayıtların nedenlerini ayrı sayaçlarda toplamak,
  • Aynı ürün için farklı fiyatlar gelirse bunu uyarı olarak raporlamak.

Özellik eklemeden önce kabul kriterini yazın ve en az bir başarısız örnek hazırlayın.

Örneğin:

Aynı ürün iki farklı fiyatla gelirse rapor bu ürünü "fiyat tutarsızlığı" olarak işaretlemeli.

Sonra veri örneği:

[
    {"urun": "Kalem", "fiyat": "10", "adet": "1"},
    {"urun": "Kalem", "fiyat": "12", "adet": "1"},
]

Ancak bundan sonra kodu değiştirin.

Yerel çalışma dosyaları

Bu bölümdeki çok dosyalı örneği kendi bilgisayarınızda çalıştırmak için proje dosyaları ders deposunda birlikte verilmiştir:

Aynı klasörde terminal açıp önce:

python test_kontrolleri.py

ardından:

python main.py

çalıştırabilirsiniz. Windows’ta kurulumunuza göre python yerine py kullanabilirsiniz.

Ders sonu kontrolü

Mikro uygulama için şu sorulara cevap verebilmelisiniz:

  1. satislar.csv verisi neden doğrudan hesaplamaya sokulmuyor?
  2. utf-8-sig ve delimiter=";" hangi gerçek dosya problemlerini çözüyor?
  3. urun_adi_temizle() neden yalnızca .lower() kullanmıyor?
  4. negatif_olmayan_sayi_coz() ile pozitif_tam_sayi_coz() sözleşmeleri neden farklıdır?
  5. None burada ne anlama geliyor ve neden is None ile kontrol ediliyor?
  6. 0 adetli satış neden bu uygulamada geçersiz kabul edildi?
  7. urun_adetleri ve urun_cirolari sözlükleri hangi biriktirme kalıbını kullanıyor?
  8. Maksimum ciro ürünü nasıl bulunuyor?
  9. FileNotFoundError hangi modülde ve neden ele alınıyor?
  10. AI tarafından üretilen bir değişikliği kabul etmeden önce hangi testleri çalıştırırsınız?

Dönem özeti

Bu ders boyunca programlamayı yalnızca Python sözdizimi olarak ele almadık. Şu çalışma biçimini geliştirdik:

problemi anla
   ↓
girdi–işlem–çıktıyı belirle
   ↓
kabul kriterlerini ve sınırları düşün
   ↓
küçük çözüm adımları oluştur
   ↓
kodu oku ve tahmin et
   ↓
çalıştır ve gözlemle
   ↓
hataları ve veri kalitesini kontrol et
   ↓
küçük fonksiyonlara ve gerekirse modüllere ayır
   ↓
veriyi uygun yapıda tut
   ↓
sonucu testlerle doğrula

Öğrendiğimiz Python araçları bu yaklaşımı uygulamak için kullandığımız araçlardır:

  • Değişkenler ve veri türleri,
  • İfadeler ve tür dönüşümleri,
  • Koşullar,
  • while ve for,
  • Fonksiyonlar, kapsam ve varsayılan parametreler,
  • String işlemleri ve metin normalleştirme,
  • Liste, demet, sözlük ve küme,
  • Dosya ve CSV,
  • İstisnalar, modüller ve main guard,
  • assert, sınır durumları ve sistematik hata ayıklama.

Daha ileri derslerde yeni yapılar ve daha güçlü geliştirme araçları göreceksiniz. AI ve coding agent’lar da daha fazla sorumluluk üstlenebilir. Ancak temel ilke değişmez: üretilen kodu anlayabilmek, davranışını açıklayabilmek ve doğrulayabilmek.

Kendinizi kontrol edin

  1. float("12,5") neden doğrudan çalışmaz ve bu uygulamada nasıl ele alındı?
  2. "İĞNE".lower() neden Türkçe normalleştirme için tek başına güvenilir değildir?
  3. if deger is None: koşulu hangi durumu temsil eder?
  4. continue rapor döngüsünde hangi amaçla kullanılıyor?
  5. urun_cirolari.get(urun, 0) + satir_cirosu kalıbı ne yapıyor?
  6. “Veri yok” durumunda maksimum fonksiyonunun None döndürmesi neden 0 döndürmekten daha açıklayıcı olabilir?
  7. F-string içindeki :.2f ne sağlar?
  8. Modüllere ayırmanın hata ayıklama ve test açısından yararı nedir?
  9. Bir değişiklikten sonra neden eski assert kontrolleri de yeniden çalıştırılmalıdır?
  10. AI’nin verdiği bir çözüm çalışıyor görünse bile hangi nedenlerle reddedilebilir?
Back to top