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:

Mikro uygulamamız bir kampüs kırtasiye satış özeti olacak. Hazır verilen küçük bir veri kümesini işleyecek, hatalı kayıtları ayıracak ve özet sonuçlar üreteceğiz.

Bu hafta neleri yapabilmelisiniz?

Bölümün sonunda:

  • hazır bir kod tabanını okuyup veri akışını açıklayabilmeli,
  • problemi küçük fonksiyonlara ayırabilmeli,
  • verilen kabul kriterlerine göre sınırlı özellik ekleyebilmeli,
  • liste ve sözlük kayıtlarını filtreleyip özetleyebilmeli,
  • string normalleştirme ve sayısal doğrulama yapabilmeli,
  • assert ile küçük davranış kontrolleri yazabilmeli,
  • hatalı girdinin programın geri kalanını bozmasını önleyebilmeli,
  • bir değişiklikten sonra ilgili örnekleri yeniden çalıştırabilmeli,
  • yazdığınız kodun ne yaptığını kısa ve teknik olarak doğru biçimde açıklayabilmelisiniz.

Problem

Bir kırtasiye uygulamasında satış kayıtları şu biçimde geliyor:

kayitlar = [
    {"urun": " Kalem ", "fiyat": "12.5", "adet": "3"},
    {"urun": "Defter", "fiyat": "45", "adet": "2"},
    {"urun": "Silgi", "fiyat": "8", "adet": "abc"},
    {"urun": "Kalem", "fiyat": "12.5", "adet": "1"},
]

Burada iki önemli nokta var:

  • Metin alanlarında gereksiz boşluk olabilir.
  • Sayısal olması gereken alanlar string olarak gelebilir ve bazıları geçersiz olabilir.

İstenen rapor:

Geçerli kayıt sayısı: 3
Geçersiz kayıt sayısı: 1
Toplam ciro: 140.0
Ürün adetleri: {'kalem': 4, 'defter': 2}

Silgi kaydındaki "abc" adet değeri sayıya dönüştürülemediği için o kayıt geçersiz sayılacaktır.

Önce veri akışını düşünün

Koda başlamadan önce problemi şu adımlara ayıralım:

ham kayıt
   ↓
metinleri temizle
   ↓
fiyat ve adedi sayıya dönüştür
   ↓
kayıt geçerli mi?
   ↓
geçerliyse ciro ve ürün adetlerine ekle
   ↓
özet üret

Bu akışın her adımını ayrı fonksiyona ayırmak zorunda değiliz; ancak birbirinden farklı sorumlulukları görünür tutmak kodu kolaylaştırır.

Ham kayıttan metin temizleme, sayısal dönüşüm, doğrulama, geçerli kayıtların biriktirilmesi ve özet üretimine uzanan mikro uygulama veri akışı.

Hazır yardımcı fonksiyon: ürün adını temizlemek

def urun_adi_temizle(urun):
    return urun.strip().lower()

Beklenen davranış:

assert urun_adi_temizle(" Kalem ") == "kalem"
assert urun_adi_temizle("DEFTER") == "defter"

Bu fonksiyon saf bir string dönüşümüdür.

Sayısal alanı güvenli çözmek

  1. haftadaki istisna bilgisini kullanalım:
def pozitif_sayi_coz(metin):
    try:
        sayi = float(metin)
    except ValueError:
        return None

    if sayi < 0:
        return None

    return sayi

Örnek kontroller:

assert pozitif_sayi_coz("12.5") == 12.5
assert pozitif_sayi_coz("0") == 0
assert pozitif_sayi_coz("abc") is None
assert pozitif_sayi_coz("-3") is None

Adet için tam sayı beklememiz gerektiğinden ayrı bir fonksiyon daha anlamlıdır:

def pozitif_tam_sayi_coz(metin):
    try:
        sayi = int(metin)
    except ValueError:
        return None

    if sayi < 0:
        return None

    return sayi

Bu iki fonksiyon benzer görünse de biri float, diğeri int sözleşmesine sahiptir.

Tahmin et — hangi kayıt geçersiz?

Aşağıdaki küçük kontrolü çalıştırmadan önce sonucu tahmin edin:

Hangi iki değer None üretir? Çalıştırıp doğrulayın.

Bir kaydı dönüştürmek

Ham kayıt:

{"urun": " Kalem ", "fiyat": "12.5", "adet": "3"}

Program içinde şu yapıya dönüşebilir:

{"urun": "kalem", "fiyat": 12.5, "adet": 3}

Bunu fonksiyonla yapalım:

def kaydi_coz(kayit):
    urun = urun_adi_temizle(kayit["urun"])
    fiyat = pozitif_sayi_coz(kayit["fiyat"])
    adet = pozitif_tam_sayi_coz(kayit["adet"])

    if fiyat is None or adet is None:
        return None

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

None, bu uygulamada “kayıt geçerli biçime dönüştürülemedi” anlamını taşır.

Note

Gerçek bir sistemde geçersiz kaydın neden geçersiz olduğuna dair daha ayrıntılı hata bilgisi tutmak isteyebiliriz. Bu mikro uygulamada kapsamı küçük tutmak için yalnızca None kullanıyoruz.

Alıştırma — kayıt doğrulamasını tamamlayın

Aşağıdaki fonksiyonda eksik olan kontrolü tamamlayın.

Beklenen sonuç:

None

if fiyat is None or adet is None: koşulu yeterlidir.

def pozitif_sayi_coz(metin):
    try:
        sayi = float(metin)
    except ValueError:
        return None
    if sayi < 0:
        return None
    return sayi

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

def kaydi_coz(kayit):
    urun = kayit["urun"].strip().lower()
    fiyat = pozitif_sayi_coz(kayit["fiyat"])
    adet = pozitif_tam_sayi_coz(kayit["adet"])

    if fiyat is None or adet is None:
        return None

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

print(kaydi_coz({"urun": "Silgi", "fiyat": "8", "adet": "abc"}))

Satır toplamı

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

fiyat × adet

Bunu küçük bir fonksiyonla açık hâle getirebiliriz:

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

Kontroller:

assert satir_toplami({"fiyat": 12.5, "adet": 3}) == 37.5
assert satir_toplami({"fiyat": 45, "adet": 2}) == 90

Ürün bazında adet biriktirmek

Geçerli kayıtların ürün adetlerini sözlükte toplayabiliriz:

urun_adetleri = {}

urun = "kalem"
adet = 3

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

Aynı ürün yeniden geldiğinde mevcut miktar üzerine eklenir.

Ana rapor fonksiyonu

Şimdi parçaları birleştirelim:

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

    for ham_kayit in kayitlar:
        kayit = kaydi_coz(ham_kayit)

        if kayit is None:
            gecersiz_sayisi = gecersiz_sayisi + 1
            continue

        gecerli_sayisi = gecerli_sayisi + 1
        toplam_ciro = toplam_ciro + satir_toplami(kayit)

        urun = kayit["urun"]
        adet = kayit["adet"]
        urun_adetleri[urun] = urun_adetleri.get(urun, 0) + adet

    return {
        "gecerli": gecerli_sayisi,
        "gecersiz": gecersiz_sayisi,
        "ciro": toplam_ciro,
        "urun_adetleri": urun_adetleri
    }

Bu fonksiyon yeni sözdizimi içermez. Önceki haftalardaki kalıpları bir araya getirir:

  • for ile kayıtları dolaşma,
  • fonksiyon çağırma,
  • None kontrolü,
  • continue,
  • biriktirici,
  • sözlük sayacı,
  • sözlük sonucu döndürme.

Kod izleme: ilk iki kayıt

Tüm programı bir anda anlamak yerine ilk iki kaydı izleyelim.

Başlangıç:

toplam_ciro = 0
gecerli = 0
gecersiz = 0
urun_adetleri = {}

İlk kayıt:

{"urun": " Kalem ", "fiyat": "12.5", "adet": "3"}

Dönüştürülmüş:

{"urun": "kalem", "fiyat": 12.5, "adet": 3}

Yeni durum:

toplam_ciro = 37.5
gecerli = 1
gecersiz = 0
urun_adetleri = {"kalem": 3}

İkinci kayıt:

{"urun": "Defter", "fiyat": "45", "adet": "2"}

Yeni durum:

toplam_ciro = 127.5
gecerli = 2
gecersiz = 0
urun_adetleri = {"kalem": 3, "defter": 2}

Bu tür izleme, büyük hücrede sonuç yanlış olduğunda hangi adımda sapma başladığını bulmaya yardım eder.

Alıştırma — rapor biriktiricilerini tamamlayın

Aşağıdaki sürümde rapor fonksiyonunun üç güncellemesi eksik.

Beklenen sözlük:

{
  'gecerli': 3,
  'gecersiz': 1,
  'ciro': 140.0,
  'urun_adetleri': {'kalem': 4, 'defter': 2}
}

Geçerli kayıtta: gecerli_sayisi = gecerli_sayisi + 1, toplam_ciro = toplam_ciro + satir_toplami(kayit) ve ürün için get() kalıbı gerekir.

def urun_adi_temizle(urun):
    return urun.strip().lower()

def pozitif_sayi_coz(metin):
    try:
        sayi = float(metin)
    except ValueError:
        return None
    if sayi < 0:
        return None
    return sayi

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

def kaydi_coz(kayit):
    urun = urun_adi_temizle(kayit["urun"])
    fiyat = pozitif_sayi_coz(kayit["fiyat"])
    adet = pozitif_tam_sayi_coz(kayit["adet"])

    if fiyat is None or adet is None:
        return None

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

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

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

    for ham_kayit in kayitlar:
        kayit = kaydi_coz(ham_kayit)

        if kayit is None:
            gecersiz_sayisi = gecersiz_sayisi + 1
            continue

        gecerli_sayisi = gecerli_sayisi + 1
        toplam_ciro = toplam_ciro + satir_toplami(kayit)

        urun = kayit["urun"]
        adet = kayit["adet"]
        urun_adetleri[urun] = urun_adetleri.get(urun, 0) + adet

    return {
        "gecerli": gecerli_sayisi,
        "gecersiz": gecersiz_sayisi,
        "ciro": toplam_ciro,
        "urun_adetleri": urun_adetleri
    }

kayitlar = [
    {"urun": " Kalem ", "fiyat": "12.5", "adet": "3"},
    {"urun": "Defter", "fiyat": "45", "adet": "2"},
    {"urun": "Silgi", "fiyat": "8", "adet": "abc"},
    {"urun": "Kalem", "fiyat": "12.5", "adet": "1"},
]

print(satis_raporu(kayitlar))

Küçük regresyon kontrolleri

Bir değişiklik yaptıktan sonra daha önce çalışan davranışların bozulmadığını kontrol etmek için birkaç assert yazabiliriz.

assert urun_adi_temizle(" Kalem ") == "kalem"
assert pozitif_sayi_coz("12.5") == 12.5
assert pozitif_sayi_coz("abc") is None
assert pozitif_tam_sayi_coz("3") == 3
assert pozitif_tam_sayi_coz("-1") is None

Rapor için:

test_rapor = satis_raporu([
    {"urun": "Kalem", "fiyat": "10", "adet": "2"}
])

assert test_rapor["gecerli"] == 1
assert test_rapor["gecersiz"] == 0
assert test_rapor["ciro"] == 20
assert test_rapor["urun_adetleri"]["kalem"] == 2

Bu kontroller tam bir test sistemi değildir; fakat küçük bir değişiklik sonrası temel davranışın korunup korunmadığını hızlıca gösterir.

Hata ayıklama senaryosu

Rapor ciro = 152 gibi beklenmeyen bir sonuç verirse doğrudan bütün fonksiyonları değiştirmeyin.

Şu sırayı izleyin:

  1. Beklenen sonucu elle hesaplayın.
  2. Geçerli kayıtların hangileri olduğunu belirleyin.
  3. kaydi_coz() çıktısını her kayıt için inceleyin.
  4. Her geçerli kayıt için satir_toplami() sonucunu kontrol edin.
  5. Biriktiricinin hangi yinelemede yanlışlaştığını bulun.
  6. Tek bir düzeltme yapıp aynı veriyle yeniden çalıştırın.
  7. assert kontrollerini yeniden çalıştırın.

Bu süreç 13. haftadaki sistematik hata ayıklama yaklaşımının uygulamasıdır.

Sınırlı özellik ekleme

Hazır uygulamaya şu özelliklerden yalnızca birini eklemeyi deneyin:

  • ciroya en fazla katkı yapan ürün adını bulmak,
  • stoku olmayan kayıtları ayrı saymak,
  • ürün adı boş string ise kaydı geçersiz saymak,
  • ciroyu iki ondalık basamakla raporlamak.

Özellik eklerken önce kabul kriterini yazın.

Örneğin:

Ürün adı boş veya yalnızca boşluklardan oluşuyorsa kayıt geçersiz olmalı.

Sonra önce bir başarısız örnek hazırlayın:

bos_ad = {"urun": "   ", "fiyat": "10", "adet": "1"}

Ancak bundan sonra kodu değiştirin.

Tip

Yeni özellik eklemek, çalışan kodu baştan yazmak anlamına gelmez. En küçük gerekli değişikliği yapmak ve ardından mevcut kontrolleri yeniden çalıştırmak daha güvenlidir.

Kodunuzu açıklayın

Bir programın yalnızca çıktısını göstermek yeterli değildir. Kısa bir teknik açıklama da yapabilmelisiniz.

Örnek:

kaydi_coz fonksiyonu ürün adını normalleştirir ve sayısal alanları dönüştürür. Fiyat veya adet dönüştürülemiyorsa None döndürür. satis_raporu yalnızca geçerli kayıtları ciro ve ürün adetleri biriktiricilerine ekler; geçersiz kayıtları ayrı sayaçta tutar.

Bu açıklama:

  • fonksiyon adlarını kullanır,
  • veri akışını belirtir,
  • gereksiz satır satır kod tekrarı yapmaz,
  • sonucun neden doğru olduğunu anlatır.

Ders sonu kontrolü

Mikro uygulamanız için şu sorulara cevap verebilmelisiniz:

  • Ham veri ile temizlenmiş veri arasındaki fark nedir?
  • Geçersiz kayıt hangi noktada elenir?
  • Neden string sayı alanları dönüştürülür?
  • None burada hangi anlamda kullanılıyor?
  • Ciro hangi değişkende ve nasıl biriktiriliyor?
  • Aynı ürünün farklı kayıtları nasıl tek sözlük anahtarında toplanıyor?
  • Fonksiyonların hangileri saf hesaplama/dönüşüm yapıyor?
  • Bir sonuç yanlışsa ilk hangi küçük fonksiyonu veya ara değeri kontrol edersiniz?
  • Yeni özellik eklendikten sonra hangi mevcut davranışların yeniden doğrulanması gerekir?

Dönem özeti

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

problemi anla
   ↓
girdi–işlem–çıktıyı belirle
   ↓
küçük çözüm adımları oluştur
   ↓
kodu oku ve tahmin et
   ↓
çalıştır ve gözlemle
   ↓
sınırları ve hataları kontrol et
   ↓
küçük fonksiyonlara ayır
   ↓
veriyi uygun yapıda tut
   ↓
sonucu 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,
  • ifadeler ve tür dönüşümleri,
  • koşullar,
  • while ve for,
  • fonksiyonlar,
  • string,
  • liste, demet, sözlük ve küme,
  • dosya ve CSV,
  • istisnalar ve modüller.

Daha ileri derslerde yeni yapılar öğreneceksiniz. Ancak temel alışkanlık aynı kalacaktır: kodu yalnızca yazmak değil, davranışını açıklamak ve doğrulamak.

Kendinizi kontrol edin

  1. Mikro uygulamada neden ham kaydı doğrudan hesaplamaya sokmak yerine önce dönüştürüyoruz?
  2. kaydi_coz() neden geçersiz kayıtta None döndürüyor?
  3. continue rapor döngüsünde hangi amaçla kullanılıyor?
  4. urun_adetleri.get(urun, 0) + adet kalıbı ne yapıyor?
  5. Neden satir_toplami() ayrı bir fonksiyon olabilir?
  6. Bir mantık hatasında bütün programı değiştirmek yerine ara değerleri izlemek neden daha iyi yaklaşımdır?
  7. assert kontrolleri yeni özellikten sonra ne işe yarar?
  8. “Ürün adı boşsa kayıt geçersiz” özelliğini eklemeden önce hangi örnek girdiyi hazırlardınız?
  9. Bu mikro uygulamada ilk 13 haftadan en az beş farklı kavramın nerede kullanıldığını açıklayın.
  10. Programın doğru sonuç vermesi ile kodun anlaşılır ve doğrulanabilir olması neden aynı şey değildir?
Back to top