İş isteği
Anadolu Market operasyon müdürü senden şunu istiyor: "Her sabah dünkü siparişlerin özetini istiyorum: toplam ciro, hangi şehre ne kadar sattık, en çok satan ürün hangisi. Düz metin olsun, e-postaya yapıştırayım." Elinde sipariş kayıtları var; bu derste isteği adım adım çalışan bir programa çevireceksin. Alıştırmalar projenin parçalarıdır: her biri bir fonksiyon, sonuncusu hepsini birleştirir.
Veri
Her sipariş bir sözlük: id, musteri, sehir, urun, adet, birim_fiyat. Sipariş tutarı adet × birim_fiyat. Veriyi kodun içinde küçük bir liste olarak tutuyoruz; gerçekte CSV'den gelir, onu da 6. derste öğrendin.
SIPARISLER = [
{"id": 1001, "musteri": "Ayşe", "sehir": "Ankara", "urun": "Kulaklık", "adet": 2, "birim_fiyat": 899.90},
{"id": 1002, "musteri": "Mehmet", "sehir": "İzmir", "urun": "Klavye", "adet": 1, "birim_fiyat": 1249.00},
{"id": 1003, "musteri": "Elif", "sehir": "Ankara", "urun": "Mouse", "adet": 3, "birim_fiyat": 349.50},
{"id": 1004, "musteri": "Can", "sehir": "İstanbul", "urun": "Kulaklık", "adet": 1, "birim_fiyat": 899.90},
{"id": 1005, "musteri": "Zeynep", "sehir": "İzmir", "urun": "Mouse", "adet": 2, "birim_fiyat": 349.50},
{"id": 1006, "musteri": "Burak", "sehir": "Ankara", "urun": "Monitör", "adet": 1, "birim_fiyat": 4599.00},
]
print(len(SIPARISLER), "sipariş")
print(SIPARISLER[0])
Adım 1: Planı fonksiyonlara böl
Büyük işi tek seferde yazmaya kalkma. Önce isteği parçala; her parça tek iş yapan, girdi alıp çıktı döndüren bir fonksiyon olsun:
siparis_tutari(siparis): tek siparişin tutarı.toplam_ciro(siparisler): tüm tutarların toplamı.sehre_gore_ciro(siparisler): şehir → toplam tutar sözlüğü.en_cok_satan(siparisler): adet toplamı en yüksek ürün ve adedi.rapor_metni(siparisler): yukarıdakileri çağırıp biçimli metin üretir.raporu_kaydet(metin, yol): metni dosyaya yazar.
Hesap fonksiyonları (1-4) saf: yalnızca veri alır, sonuç döndürür, hiçbir şey yazdırmaz. Sunum (5) ve dosya (6) ayrıdır. Böylece her hesabı tek başına test edebilir, raporun biçimini değiştirirken hesaba dokunmazsın.
Adım 2: Tek siparişin tutarı ve toplam ciro
SIPARISLER = [
{"id": 1001, "sehir": "Ankara", "urun": "Kulaklık", "adet": 2, "birim_fiyat": 899.90},
{"id": 1002, "sehir": "İzmir", "urun": "Klavye", "adet": 1, "birim_fiyat": 1249.00},
{"id": 1003, "sehir": "Ankara", "urun": "Mouse", "adet": 3, "birim_fiyat": 349.50},
]
def siparis_tutari(siparis):
"""adet × birim fiyat, 2 ondalığa yuvarlanmış."""
return round(siparis["adet"] * siparis["birim_fiyat"], 2)
def toplam_ciro(siparisler):
toplam = 0.0
for s in siparisler:
toplam += siparis_tutari(s)
return round(toplam, 2)
print(siparis_tutari(SIPARISLER[0]))
print(toplam_ciro(SIPARISLER))
Küçük fonksiyon büyüğünün içinde kullanılır; tutar hesabı değişirse (örneğin indirim eklenirse) yalnız siparis_tutari değişir.
Adım 3: Şehre göre gruplama
Döngüler dersindeki sayaç kalıbının toplama versiyonu: anahtar şehir, değer biriken tutar.
def sehre_gore_ciro(siparisler):
ciro = {}
for s in siparisler:
sehir = s["sehir"]
ciro[sehir] = round(ciro.get(sehir, 0.0) + siparis_tutari(s), 2)
return ciro
print(sehre_gore_ciro(SIPARISLER))
Adım 4: En çok satan ürün
Önce ürün → toplam adet sözlüğü kur, sonra en büyüğü bul. max fonksiyonuna key vererek sözlüğü değerine göre tarayabilirsin.
def en_cok_satan(siparisler):
adetler = {}
for s in siparisler:
adetler[s["urun"]] = adetler.get(s["urun"], 0) + s["adet"]
if not adetler:
return None, 0
urun = max(adetler, key=adetler.get)
return urun, adetler[urun]
print(en_cok_satan(SIPARISLER))
max boş sözlükte hata verir. Erken dönüşle (None, 0) döndürmek, raporun "Dün sipariş olmadı" diyebilmesini sağlar. Her hesap fonksiyonunda "boş gelirse ne olur?" diye sor.Adım 5: Bozuk kayıtlara dayanıklılık
Gerçek veride adet metin olarak gelebilir, birim_fiyat eksik olabilir. Hesabı çökertmek yerine bozuk kayıtları ayır ve raporda sayısını göster. Temizleme işini de ayrı bir fonksiyona ver:
def temizle(ham_siparisler):
"""Geçerli siparişleri ve atlanan kayıt sayısını döndürür."""
temiz, atlanan = [], 0
for s in ham_siparisler:
try:
kayit = dict(s)
kayit["adet"] = int(s["adet"])
kayit["birim_fiyat"] = float(s["birim_fiyat"])
if kayit["adet"] <= 0:
raise ValueError("adet pozitif olmalı")
temiz.append(kayit)
except (KeyError, ValueError, TypeError):
atlanan += 1
return temiz, atlanan
ham = [
{"id": 1, "sehir": "Ankara", "urun": "Mouse", "adet": "3", "birim_fiyat": "349.5"},
{"id": 2, "sehir": "İzmir", "urun": "Klavye", "adet": "yok", "birim_fiyat": 1249},
{"id": 3, "sehir": "Bursa", "urun": "Kulaklık", "adet": 1},
]
temiz, atlanan = temizle(ham)
print(len(temiz), "geçerli,", atlanan, "atlandı")
print(temiz[0])
Adım 6: Rapor metni ve dosya
Metin biçimlendirme dersindeki hizalama ve join kalıbı. Rapor fonksiyonu hesap yapmaz; hesap fonksiyonlarını çağırır ve satırları dizer.
from datetime import date
def rapor_metni(siparisler, tarih, atlanan=0):
toplam = toplam_ciro(siparisler)
sehirler = sehre_gore_ciro(siparisler)
urun, adet = en_cok_satan(siparisler)
satirlar = [
f"ANADOLU MARKET GÜNLÜK RAPOR - {tarih:%d.%m.%Y}",
"=" * 40,
f"Sipariş sayısı : {len(siparisler)}",
f"Toplam ciro : {toplam:,.2f} TL",
"",
"Şehre göre ciro",
]
for sehir, ciro in sorted(sehirler.items(), key=lambda x: x[1], reverse=True):
satirlar.append(f" {sehir:<12}{ciro:>14,.2f} TL")
satirlar.append("")
satirlar.append(f"En çok satan : {urun} ({adet} adet)")
if atlanan:
satirlar.append(f"Atlanan kayıt : {atlanan}")
return "\n".join(satirlar)
rapor = rapor_metni(SIPARISLER, date(2024, 3, 5), atlanan=1)
print(rapor)
with open("gunluk_rapor.txt", "w", encoding="utf-8") as f:
f.write(rapor + "\n")
print("\nDosyadan geri okundu, satır sayısı:", len(open("gunluk_rapor.txt", encoding="utf-8").read().splitlines()))
Kontrol listesi
| Soru | Neden |
|---|---|
| Her fonksiyon tek iş mi yapıyor? | Test ve değişiklik kolaylığı |
| Hesap fonksiyonları print ediyor mu? | Etmemeli; sunumdan ayrı kalmalı |
| Boş liste verince çöküyor mu? | Sipariş olmayan gün de rapor ister |
| Bozuk kayıt tüm raporu durduruyor mu? | Durdurmamalı; sayılıp raporlanmalı |
| Para 2 ondalıkla ve binlik ayırıcıyla mı? | Müdür Excel'den alışkın |
Derinleş: aynı iskeletle yeni projeler
Sipariş raporunu bitirdin. Asıl kazanım raporun kendisi değil, iskelet: oku → temizle → hesapla → sun → kaydet. İş hayatında karşına çıkacak küçük otomasyonların çoğu bu beş adımın farklı verilerle tekrarıdır. Bu bölümde iskeleti açıkça adlandırıyor, programı bilgisayarında çalışacak bir dosya düzenine sokuyor ve dört yeni proje varyasyonu veriyoruz. Alıştırmalarda bunlardan birini, Ilgaz Bank gün sonu raporunu, adım adım rehberle sıfırdan inşa edeceksin.
Beş adımlık iskelet
| Adım | Sipariş raporu | Banka gün sonu | Stok uyarısı | İK mesai özeti |
|---|---|---|---|---|
| Oku | CSV → sözlük listesi | hareket CSV metni | stok sözlüğü | giriş-çıkış kayıtları |
| Temizle | temizle | hatalı satırı say | negatif adedi ele | eksik saati ayır |
| Hesapla | ciro, şehir, en çok satan | bakiye, şüpheli işlem | eşik altı ürünler | toplam ve fazla mesai |
| Sun | rapor_metni | banka_raporu | sipariş listesi | kişi başı tablo |
| Kaydet | txt dosyası | tarihli txt | tedarikçiye CSV | bordroya CSV |
Kural aynı: her adım ayrı fonksiyon; hesap fonksiyonları saf (yazdırmaz, dosyaya dokunmaz, girdiyi değiştirmez), girdi/çıktı yalnızca uçlarda. Böylece "rapor biçimini değiştir" isteği hesaplara, "yeni veri kaynağı" isteği rapora dokunmaz.
Programın girişi: main ve __name__ koruması
Fonksiyonları yazdın; peki program nereden başlar? Python dosyası iki şekilde kullanılabilir: doğrudan çalıştırılır (python rapor.py) ya da başka bir dosya onu import eder (testler gibi). if __name__ == "__main__": bloğu yalnızca birinci durumda çalışır. Ayarlanabilir değerleri dosyanın başına BÜYÜK_HARFLİ sabitler olarak koy; ileride bunları ayar dosyasından ya da komut satırından okuyacaksın.
ESIK_TL = 10_000 # ayarlar en üstte, tek yerde
CIKTI = "gun_sonu.txt"
def oku():
return [("TR01", "cekme", 1200.5), ("TR02", "havale", 15000.0), ("TR01", "yatirma", 5000.0)]
def supheli(hareketler, esik):
return [h for h in hareketler if h[1] != "yatirma" and h[2] >= esik]
def rapor(hareketler, liste):
return f"{len(hareketler)} hareket, {len(liste)} şüpheli"
def main():
hareketler = oku()
liste = supheli(hareketler, ESIK_TL)
metin = rapor(hareketler, liste)
with open(CIKTI, "w", encoding="utf-8") as f:
f.write(metin + "\n")
print(metin)
if __name__ == "__main__":
main()
import rapor dediği anda rapor üretilir ve dosya yazılır. main içine alınca import yalnız fonksiyonları tanımlar; testler onları tek tek çağırır.Kendi projeni test et: küçük assert seti
Henüz pytest bilmiyorsun ama bilmen gerekmiyor: bilinen küçük girdiler için beklenen çıktıyı assert ile yazan bir testler() fonksiyonu, kodu her değiştirdiğinde "bir şeyi bozdum mu?" sorusunu saniyede cevaplar. En az üç durum test et: normal veri, boş veri, bozuk veri.
def sehre_gore(siparisler):
sonuc = {}
for sehir, tutar in siparisler:
sonuc[sehir] = round(sonuc.get(sehir, 0) + tutar, 2)
return sonuc
def testler():
assert sehre_gore([("Ankara", 10.1), ("Ankara", 0.2)]) == {"Ankara": 10.3}, "kuruş yuvarlama"
assert sehre_gore([]) == {}, "boş gün"
assert sehre_gore([("İzmir", 5)]) == {"İzmir": 5}, "tek kayıt"
print("Tüm testler geçti")
testler()
Bilgisayarında proje düzeni
Tarayıcıda tek hücrede çalıştın; gerçek projede birkaç dosya olur. Basit ve yeterli düzen:
gun_sonu_raporu/ ├── rapor.py # fonksiyonlar + main() ├── test_rapor.py # assert'li testler (sonra pytest) ├── veri/ │ └── hareketler.csv ├── cikti/ # üretilen raporlar (git'e eklenmez) └── README.md # ne yapar, nasıl çalışır
cd gun_sonu_raporu python rapor.py # Windows python3 rapor.py # macOS / Linux python test_rapor.py # testler
| Belirti | Neden | Çözüm |
|---|---|---|
can't open file 'rapor.py' | Terminal başka klasörde | Önce cd ile proje klasörüne geç |
FileNotFoundError: veri/hareketler.csv | Göreli yol çalışma klasörüne göre | Path(__file__).parent / "veri" / ... |
python komutu Microsoft Store'u açıyor | Windows takma adı | py rapor.py ya da kurulumu PATH'e ekle |
| Türkçe harfler bozuk | encoding verilmemiş | Her open'a encoding="utf-8" |
Dört varyasyon: kendine meydan oku
- Ilgaz Bank gün sonu (alıştırmalarda rehberli): hareket CSV'sini oku, hesap bakiyelerini güncelle, eşiği aşan çekme/havaleleri işaretle, raporu yaz.
- Stok uyarı raporu: ürün başına farklı kritik eşik, eşiği olmayana varsayılan eşik; kritik ürünleri adede göre sırala, tedarikçiye gönderilecek listeyi üret.
- İK mesai özeti: çalışan, gün ve saat kayıtlarından kişi başı toplam saat ve günlük 9 saati aşan fazla mesai.
- Dönem karşılaştırma: dünkü ve bugünkü şehir cirolarını karşılaştır, yüzde değişimi hesapla; dün satış olmayan şehirde yüzde yerine "yeni" yaz.
kayit = s yazıp kayit["adet"] = int(...) dersen, çağıranın orijinal listesini de değiştirirsin; aynı veriyle ikinci rapor farklı çıkar. Önce kopyala: kayit = dict(s). Bakiye sözlüğü için de aynısı: bakiye = dict(acilis).Bitirmeden önce: projeyi "teslim edilebilir" yapmak
- README'de üç şey: ne yapar, nasıl çalıştırılır (komut), girdi dosyasının biçimi.
- Rapor dosyasının adında tarih:
gun_sonu_2026-10-27.txt. - Hatalı satır sayısı raporda görünür; sıfır değilse ayrıca hatalı satırları bir dosyaya yaz.
- Sihirli sayılar (10000, 9 saat) sabit olarak en üstte.
from datetime import date
RAPOR_TARIHI = date(2026, 10, 27) # gerçek betikte date.today()
ham = ["1,TR01,5000", "2,TR01,abc", "3,TR02,300", "4,TR02"]
gecerli, hatalilar = [], []
for no, satir in enumerate(ham, start=1):
parca = satir.split(",")
if len(parca) == 3 and parca[2].isdigit():
gecerli.append(parca)
else:
hatalilar.append(f"{no}: {satir}")
ad = f"gun_sonu_{RAPOR_TARIHI.isoformat()}.txt"
with open(ad, "w", encoding="utf-8") as f:
f.write(f"Geçerli hareket: {len(gecerli)}\n")
if hatalilar:
with open(ad.replace(".txt", "_hatalar.txt"), "w", encoding="utf-8") as f:
f.write("\n".join(hatalilar) + "\n")
print("Rapor:", ad)
print("Hatalı satırlar:", hatalilar)
Bu program gerçekte nasıl çalışır?
Orta seviyede aynı projeyi CSV modülüyle dosyadan okuyacak, dataclass ile siparişi tip güvenli bir nesneye çevirecek, collections.Counter ve defaultdict ile gruplama kodunu yarıya indirecek, unittest ile her fonksiyona test yazacaksın. İleri seviyede ise pandas ile groupby("sehir")["tutar"].sum() tek satır olacak. Temel bu: parçala, saf tut, biçimi ayır.
Zamanlama
"Her sabah" isteği kodun içine girmez; işletim sisteminin zamanlayıcısı (cron, Görev Zamanlayıcı) betiği 07:00'de çalıştırır. Betik if __name__ == "__main__": bloğunda veriyi okur, raporu üretir, dosyaya yazar ve çıkar. Betiğin kendisi "ne zaman" sorusunu bilmez; bu, sorumlulukları ayırmanın bir başka örneğidir.