Neden "piller dahil"?
Python'ın sloganlarından biri "batteries included" (piller dahil): kurulumla birlikte gelen standart kütüphane (standard library) yüzlerce modül içerir. Anadolu Market'te bir analist olarak her gün şunlarla karşılaşırsın: rapor klasörünü taramak, betiğe komut satırından parametre vermek, kuruşu kuruşuna tutar hesaplamak, satış rakamlarının ortalamasını ve sapmasını almak, bir parolayı güvenli saklamak. Bunların hiçbiri için pip ile paket kurman gerekmez. Bu ders, en sık kullanacağın modülleri tek turda gezer; her birini derinlemesine değil, "ne zaman hangisine uzanırım" düzeyinde öğrenirsin.
| İhtiyaç | Modül | Anahtar fonksiyon / sınıf |
|---|---|---|
| Dosya yolu, klasör, okuma-yazma | pathlib, os, shutil, tempfile | Path, mkdir, glob, copy, rmtree |
| Komut satırı argümanı | argparse, sys | ArgumentParser, parse_args, sys.argv |
| Zaman ölçme, bekleme | time | perf_counter, sleep |
| Rastgelelik | random, secrets | seed, choice, sample / token_hex |
| İstatistik ve matematik | statistics, math | mean, median, stdev / ceil, floor, sqrt |
| Metin sarma | textwrap | fill, shorten, dedent |
| Sabit küme | enum | Enum, auto |
| Kesin ondalık, kesir | decimal, fractions | Decimal, quantize / Fraction |
| Özet (hash) | hashlib | sha256, hexdigest |
Dosya sistemi: pathlib, shutil, tempfile
pathlib.Path, dosya yolunu bir nesne olarak temsil eder; / işleciyle parça ekler, .name, .suffix, .parent ile parçalara ayırırsın. os modülü daha eski ve düşük seviyelidir; os.environ ve os.getcwd() gibi birkaç şey için hâlâ gerekir. shutil ise "kabuk yardımcıları": kopyalama, taşıma, klasörü komple silme. Aşağıdaki örnek, geçici bir klasörde (tempfile) küçük bir rapor arşivi kurar; bu klasör bellekte oluşur ve with bloğu bitince otomatik silinir.
import shutil
import tempfile
from pathlib import Path
with tempfile.TemporaryDirectory() as gecici:
kok = Path(gecici) / "raporlar"
kok.mkdir()
for ay in ("ocak", "subat", "mart"):
(kok / f"satis_{ay}.csv").write_text(f"sehir,tutar\nAnkara,{len(ay) * 100}\n", encoding="utf-8")
(kok / "notlar.txt").write_text("Mart raporu eksik kontrol edilecek", encoding="utf-8")
csvler = sorted(p.name for p in kok.glob("*.csv"))
print("CSV dosyaları:", csvler)
print("İlk dosyanın gövdesi:", (kok / "satis_ocak.csv").stem, "| uzantı:", (kok / "satis_ocak.csv").suffix)
yedek = Path(gecici) / "yedek"
shutil.copytree(kok, yedek)
print("Yedekteki dosya sayısı:", len(list(yedek.iterdir())))
shutil.rmtree(yedek)
print("Yedek silindi mi?", not yedek.exists())
print("Geçici klasör de silindi mi?", not Path(gecici).exists())
glob("*.csv") deseni yalnızca o klasöre bakar; alt klasörler dahil aramak için rglob kullan. write_text / read_text dosyayı açıp kapatma işini senin yerine yapar.
klasor + "/" + ad yerine Path(klasor) / ad yaz. Windows'ta ters eğik çizgi, Linux'ta düz çizgi sorununu pathlib kendisi çözer.Komut satırı: sys.argv ve argparse
Betiği terminalden python rapor.py --sehir Ankara --limit 5 diye çalıştırdığında argümanlar sys.argv listesine düşer. Bunları elle ayrıştırmak yerine argparse kullanırsın: tür dönüşümü, varsayılan değer, zorunluluk kontrolü ve --help çıktısı hazır gelir. Testte ya da burada tarayıcıda gerçek terminal olmadığı için parse_args'a listeyi elinle verirsin; bu, argparse'ı test etmenin de standart yoludur.
import argparse
def parser_kur() -> argparse.ArgumentParser:
p = argparse.ArgumentParser(prog="rapor", description="Anadolu Market şehir raporu")
p.add_argument("--sehir", required=True, help="Raporlanacak şehir")
p.add_argument("--limit", type=int, default=10, help="Kaç satır gösterilsin")
p.add_argument("--ozet", action="store_true", help="Yalnızca toplamları yaz")
return p
p = parser_kur()
a = p.parse_args(["--sehir", "Ankara", "--limit", "5"])
print(a.sehir, a.limit, a.ozet)
b = p.parse_args(["--sehir", "İzmir", "--ozet"])
print(b.sehir, b.limit, b.ozet)
print(type(b.limit).__name__)
action="store_true" bayrak (flag) tanımlar: yazılırsa True, yazılmazsa False. type=int sayesinde "5" metni tam sayıya çevrilir; geçersiz bir değer gelirse argparse anlaşılır bir hata basıp çıkar.
sys.argv dışında sys.exit(1) ile betiği hata koduyla bitirir, sys.version ile sürümü okur, sys.path ile modül arama yollarını görürsün. Betiğin "başarısız" bittiğini zamanlayıcıya bildirmenin yolu sıfır dışı bir çıkış kodudur.Para hesabı: neden float değil, Decimal?
Ilgaz Bank'ta 0.1 + 0.2 liranın 0.3 çıkmaması kabul edilemez. Float ikili tabanda saklandığı için bazı ondalıkları tam temsil edemez. decimal.Decimal ise onluk tabanda, senin belirlediğin hassasiyette çalışır. İki kural: Decimal'i string'den kur (float'tan kurarsan hata taşınır) ve sonucu quantize ile kuruşa yuvarla.
from decimal import Decimal, ROUND_HALF_UP
from fractions import Fraction
print("float: ", 0.1 + 0.2)
print("Decimal:", Decimal("0.1") + Decimal("0.2"))
tutar = Decimal("1234.567")
kdv = tutar * Decimal("0.20")
print("KDV ham:", kdv)
print("KDV kuruş:", kdv.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP))
# Yanlış kullanım: float'tan Decimal
print("float'tan:", Decimal(0.1))
# Kesir: 3 ortağın payı tam olarak 1/3
pay = Fraction(1, 3)
print("3 pay toplamı:", pay * 3, "| ondalık:", float(pay))
ROUND_HALF_UP ticari yuvarlamadır (0.005 yukarı). Varsayılan ROUND_HALF_EVEN bankacı yuvarlaması olarak bilinir; muhasebe kuralına göre hangisini kullanacağını iş birimine sor. Fraction ise oran ve pay hesaplarında kayıpsız sonuç verir.
Decimal("10") * 0.2 yazarsan TypeError alırsın. Çarpanı da Decimal yap: Decimal("10") * Decimal("0.2"). Tam sayıyla çarpmak serbesttir.Sayılar ve rastgelelik: statistics, math, random, time
Küçük bir satış listesinin özetini çıkarmak için pandas'a gerek yok. statistics ortalama, medyan ve standart sapmayı verir; math yuvarlama ve kök işlemlerini; random örneklem seçmeyi; time.perf_counter ise bir işin kaç saniye sürdüğünü ölçmeyi sağlar.
import math
import random
import statistics
import time
satislar = [1250, 980, 1430, 1120, 2990, 1010, 1180]
print("ortalama:", round(statistics.mean(satislar), 2))
print("medyan: ", statistics.median(satislar))
print("sapma: ", round(statistics.stdev(satislar), 2))
print("en büyük tam sayı sayfa:", math.ceil(len(satislar) / 3), "| kök:", math.sqrt(144))
random.seed(42)
print("denetim için seçilen 3 satış:", random.sample(satislar, 3))
t0 = time.perf_counter()
toplam = sum(i * i for i in range(100_000))
print("süre (sn) sıfırdan büyük mü?", time.perf_counter() - t0 > 0)
random.seed(42) deterministik sonuç verir: aynı tohumla aynı örneklem çıkar. Test yazarken ve raporu tekrarlanabilir kılarken bu şarttır. Medyanın ortalamadan küçük olması, 2990 gibi bir uç değerin ortalamayı yukarı çektiğini anlatır.
Metin, sabit küme, özet: textwrap, enum, hashlib, secrets
Son grup birbirinden bağımsız ama hepsi günlük işte çıkar. textwrap.fill uzun açıklamayı satırlara böler, shorten listede sığmayan başlığı kısaltır. enum.Enum, "ONAYLI / BEKLEMEDE / RED" gibi sabit kümeleri yazım hatasına kapalı hale getirir. hashlib bir metnin parmak izini (hash) üretir; parola doğrulamada parolayı değil, tuzla (salt) birleştirilmiş özetini saklarsın. Tuz üretmek için random değil, kriptografik secrets modülü kullanılır.
import hashlib
import secrets
import textwrap
from enum import Enum, auto
aciklama = "Anadolu Market, müşterinin iade talebini 14 gün içinde değerlendirir ve sonucu e-posta ile bildirir."
print(textwrap.fill(aciklama, width=40))
print(textwrap.shorten(aciklama, width=30, placeholder="..."))
class Durum(Enum):
BEKLEMEDE = auto()
ONAYLI = auto()
RED = auto()
basvuru = Durum.ONAYLI
print(basvuru, "|", basvuru.name, "|", basvuru.value, "|", basvuru is Durum["ONAYLI"])
tuz = secrets.token_hex(8)
parola = "Gizli123"
ozet = hashlib.sha256((tuz + parola).encode("utf-8")).hexdigest()
print("tuz uzunluğu:", len(tuz), "| özet uzunluğu:", len(ozet))
print("aynı parola aynı özeti verir mi?", ozet == hashlib.sha256((tuz + parola).encode("utf-8")).hexdigest())
print("parola değişince özet değişir mi?", ozet != hashlib.sha256((tuz + "gizli123").encode("utf-8")).hexdigest())
Özet tek yönlüdür: özetten parolaya dönülemez. Tuz, iki müşteri aynı parolayı seçse bile saklanan özetlerin farklı olmasını sağlar. Gerçek sistemlerde sha256 yerine yavaş ve tuzlu parola algoritmaları (bcrypt, argon2) tercih edilir; sha256'yı dosya bütünlüğü ve veri parmak izi için kullan.
random; token, tuz, geçici şifre, sıfırlama bağlantısı için secrets. random'ın çıktısı tahmin edilebilir, secrets'ınki işletim sisteminin kriptografik kaynağından gelir.Derinleş: Standart kütüphanenin "gizli kahramanları"
İlk turda dosya sistemi, argparse, Decimal ve hashlib gibi büyük modülleri gördün. Ama iş yerinde en çok zaman kazandıran modüller çoğu zaman daha küçükler: sıralama anahtarını tek satırda veren operator, pahalı hesabı önbelleğe alan functools.lru_cache, eşik tablosunda arama yapan bisect, kopyalama tuzağını çözen copy, yazım hatasını düzelten difflib. PCAP sınavında ve mülakatlarda da sıkça çıkarlar. (collections ve itertools için ayrı bir ders var; tarih için tarih-saat, regex için regex dersine bak.)
| İhtiyaç | Modül | Anahtar araç |
|---|---|---|
| Sözlük/nesne listesini alana göre sırala | operator | itemgetter, attrgetter |
| Aynı girdiyle tekrar tekrar çağrılan pahalı hesap | functools | lru_cache, cache, cached_property |
| Eşik/kademe tablosu (kargo, vergi dilimi) | bisect | bisect_right, bisect_left, insort |
| İç içe veriyi bağımsız kopyala | copy | copy, deepcopy |
| Benzer metni bul, iki metni karşılaştır | difflib | get_close_matches, unified_diff |
| Basit ve güvenli metin şablonu | string | Template, safe_substitute |
| Benzersiz kimlik, ikili veriyi metne çevirme, sıkıştırma | uuid, base64, zlib, gzip | uuid4, b64encode, compress |
| Okunur döküm, hatayı bilinçli yutma | pprint, contextlib | pprint, suppress |
| Dış komut çalıştırma | subprocess, shlex | run, split, quote |
operator ve functools: kısa ve hızlı fonksiyonlar
itemgetter("sehir", "tutar"), lambda s: (s["sehir"], s["tutar"]) ile aynı işi yapar ama daha okunur ve biraz daha hızlıdır. lru_cache ise bir fonksiyonun sonuçlarını argümanlara göre saklar; aynı argümanla ikinci çağrı hesap yapmadan döner. Yalnızca saf (pure) fonksiyonlarda kullan: sonucu saate, veritabanına ya da dış duruma bağlı fonksiyonu önbelleğe almak bayat veri döndürür. Argümanlar hashlenebilir olmalıdır (liste değil, demet).
from operator import itemgetter, attrgetter
from functools import lru_cache
from dataclasses import dataclass
siparisler = [
{"no": "S-3", "sehir": "İzmir", "tutar": 80},
{"no": "S-1", "sehir": "Ankara", "tutar": 120},
{"no": "S-2", "sehir": "Ankara", "tutar": 45},
]
for s in sorted(siparisler, key=itemgetter("sehir", "tutar")):
print(s["no"], s["sehir"], s["tutar"])
@dataclass
class Musteri:
ad: str
puan: int
print([m.ad for m in sorted([Musteri("Can", 3), Musteri("Ayşe", 9)], key=attrgetter("puan"), reverse=True)])
@lru_cache(maxsize=128)
def taksit_tablosu(tutar_kurus: int, ay: int) -> tuple:
print(" hesaplanıyor:", tutar_kurus, ay)
taksit = tutar_kurus // ay
return tuple([taksit] * (ay - 1) + [tutar_kurus - taksit * (ay - 1)])
taksit_tablosu(100000, 3)
print(taksit_tablosu(100000, 3)) # ikinci çağrı önbellekten
print(taksit_tablosu.cache_info())
@functools.cache sınırsız lru_cache'tir. Sınıflarda bir kez hesaplanıp saklanacak özellik için @functools.cached_property kullan (ör. raporun toplamı). Önbelleği boşaltmak için fonk.cache_clear().bisect: kademeli tablolarda arama
Kargo ücreti desiye, indirim sepet tutarına, vergi dilimi gelire göre değişir. Uzun bir if/elif zinciri yerine sıralı sınır listesi ve bisect kullanırsın: O(log n) arama yapar ve tablo değişince yalnızca listeyi güncellersin. bisect_right eşit değeri sağdaki dilime, bisect_left soldakine koyar; "1000 TL ve üzeri" gibi kurallarda fark burada çıkar.
from bisect import bisect_right, bisect_left, insort
sinirlar = [500, 1000, 2500] # TL
oranlar = [0, 5, 10, 15] # yüzde
for sepet in (499, 500, 999, 1000, 3000):
print(sepet, "TL -> %", oranlar[bisect_right(sinirlar, sepet)])
print("bisect_left(1000):", bisect_left(sinirlar, 1000), "| bisect_right(1000):", bisect_right(sinirlar, 1000))
kuyruk = [10, 30, 50]
insort(kuyruk, 35) # sıralı listeye sırayı bozmadan ekle
print(kuyruk)
copy: yüzeysel ve derin kopya
copy.copy (ya da list.copy(), dict.copy()) yalnızca dış kabı kopyalar; içteki liste ve sözlükler paylaşılır. Şablon bir sepetten yeni sepet üretirken bu, bir müşterinin ürününün diğerinin sepetine düşmesi demektir. copy.deepcopy her seviyeyi kopyalar.
import copy
sablon = {"musteri": None, "kalemler": [], "kargo": {"firma": "Anadolu Kargo"}}
s1 = copy.copy(sablon)
s1["kalemler"].append("laptop")
print("yüzeysel kopya sonrası şablon:", sablon["kalemler"])
sablon["kalemler"].clear()
s2 = copy.deepcopy(sablon)
s2["kalemler"].append("mouse")
s2["kargo"]["firma"] = "Hızlı Kargo"
print("derin kopya sonrası şablon:", sablon)
difflib, string.Template, uuid, base64, zlib
Müşteri formuna "Ankra" yazdı; difflib.get_close_matches en yakın şehri önerir. string.Template kullanıcıların düzenleyebileceği e-posta şablonları için f-string'ten güvenlidir: şablon metni kod çalıştıramaz, yalnızca $ad gibi yer tutucuları doldurur. uuid4 tahmin edilemeyen benzersiz kimlik üretir; zlib + base64 ise ikili veriyi sıkıştırıp JSON'a ya da URL'ye konabilecek metne çevirir.
import difflib, uuid, base64, zlib
from string import Template
sehirler = ["Ankara", "Antalya", "Adana", "İzmir", "İstanbul"]
print(difflib.get_close_matches("Ankra", sehirler, n=1))
print(difflib.get_close_matches("Istanbul", sehirler, n=1, cutoff=0.6))
t = Template("Sayın $ad, $no numaralı siparişiniz $gun gün içinde teslim edilecek.")
print(t.substitute(ad="Ayşe Yılmaz", no="S-1001", gun=3))
print(t.safe_substitute(ad="Can")) # eksik alanı olduğu gibi bırakır
kimlik = uuid.uuid4()
print("uuid uzunluğu:", len(str(kimlik)), "| sürüm:", kimlik.version)
rapor = ("Ankara;120\n" * 200).encode("utf-8")
paket = base64.b64encode(zlib.compress(rapor)).decode("ascii")
print(len(rapor), "bayt ->", len(paket), "karakter")
print(zlib.decompress(base64.b64decode(paket)) == rapor)
eski = ["Ankara;120", "İzmir;80"]
yeni = ["Ankara;125", "İzmir;80"]
print("\n".join(difflib.unified_diff(eski, yeni, "dun.csv", "bugun.csv", lineterm="")))
cryptography paketi) gerekir. Benzer şekilde uuid1 makinenin MAC adresini ve zamanı içerir; dışarıya açık kimliklerde uuid4 kullan.subprocess ve shlex: dış komut çalıştırmak
Betikten git, pg_dump ya da başka bir program çalıştırman gerekir. subprocess.run doğru araçtır; tarayıcıda alt süreç açılamadığı için aşağıdaki kodu kendi bilgisayarında dene. Komutu her zaman liste olarak ver; shell=True ile kullanıcı girdisi birleştirmek komut enjeksiyonuna kapı açar.
import subprocess, shlex
sonuc = subprocess.run(["git", "log", "-1", "--format=%h %s"],
capture_output=True, text=True, check=True, timeout=10)
print(sonuc.stdout.strip())
print(shlex.split('rapor.py --sehir "Kahramanmaraş Merkez" --limit 5'))
print(shlex.quote("dosya adı; rm -rf /")) # kabuğa güvenli metin
Kendi bilgisayarında dene: check=True sıfır dışı çıkış kodunda CalledProcessError fırlatır, timeout asılı kalan komutu keser. Komut bulunamazsa FileNotFoundError alırsın; çoğu zaman program PATH'te değildir.
argparse ile tarih alır, subprocess ile veritabanı yedeği tetikler, bisect ile kargo kademesini hesaplar, lru_cache ile döviz kurunu tek sefer çeker, sonucu gzip ile sıkıştırıp uuid4 adlı dosyaya yazar. Sunucuya hiç paket kurmadan çalıştığı için güvenlik ekibinin onayı da çabuk alınmıştır. İlk soru hep şudur: "Bu iş için standart kütüphanede bir şey var mı?"Standart kütüphane ne zaman yetmez?
Tur bitti; şimdi sınırlar. Bir işi standart kütüphaneyle çözebiliyorsan dış paket ekleme: bağımlılık, güvenlik ve sürüm yükü taşırsın. Ama şu durumlarda dış pakete geçmek doğru karardır: argparse ile 20'den fazla alt komut yönetiyorsan typer ya da click; binlerce satırlık istatistikte statistics yerine numpy ve pandas; parola saklamada hashlib yerine argon2-cffi veya bcrypt; karmaşık tarih işlerinde dateutil. Karar ölçütün şu: "Standart kütüphane bu işi yapabiliyor mu ve kod hâlâ okunur mu?"
Bir de kurumsal kodda sık görülen üç küçük kalıbı aklında tut. Birincisi, betiğin her zaman main(argv=None) fonksiyonuyla başlaması: argv None ise sys.argv[1:] kullanır, değilse verileni; böylece aynı fonksiyon hem terminalden hem testten çağrılır. İkincisi, para alanlarını veritabanından okuduğun anda Decimal'e çevirip raporun sonuna kadar Decimal tutmak; float'a yalnızca grafik çizerken dönmek. Üçüncüsü, Enum üyelerini veritabanında .name olarak saklamak; sayısal .value saklarsan üye sırası değişince veri bozulur.