Neden biçimlendirme?
Ilgaz Bank'ın müşteri hizmetleri ekibi her sabah bir hesap özeti görmek istiyor: hesap adı, bakiye, son işlem. print(ad, bakiye) ile çıkan "Ayşe 12500.5" satırı doğru ama okunmaz; bakiyeler alt alta hizalanmıyor, binlikler ayrılmıyor, kuruşlar bazen bir bazen iki hane. Raporun işe yaraması için metni biçimlendirmen (formatting) gerekir. Python'da bunun ana aracı f-string; bu derste süslü parantezin içindeki iki noktadan sonrasını, yani biçim belirtecini (format spec) öğreneceksin.
f-string hatırlatma ve biçim belirteci
f"..." içinde {ifade} yazarsın; Python ifadeyi hesaplayıp metne gömer. İki nokta ile belirteç eklersin: {deger:belirtec}. Belirtecin genel sırası şöyledir: dolgu + hizalama + genişlik + binlik + .ondalık + tür. Her parça isteğe bağlıdır.
bakiye = 12500.5
print(f"[{bakiye}]") # ham
print(f"[{bakiye:.2f}]") # 2 ondalık
print(f"[{bakiye:,.2f}]") # binlik ayırıcı + 2 ondalık
print(f"[{bakiye:12,.2f}]") # 12 karakter genişlik, sağa hizalı (sayı varsayılanı)
print(f"[{bakiye:<12,.2f}]") # sola hizalı
print(f"[{bakiye:^12,.2f}]") # ortalı
print(f"[{bakiye:*>12,.2f}]") # yıldızla doldur, sağa hizala
Sayılar varsayılan olarak sağa, metinler sola hizalanır. Bu raporlarda tam istediğin şeydir: sütun başlığı solda, tutarlar sağda alt alta.
| Belirteç | Anlamı | Örnek | Sonuç |
|---|---|---|---|
:.2f | Sabit 2 ondalık | f"{3.14159:.2f}" | 3.14 |
:, | Binlik ayırıcı (virgül) | f"{1234567:,}" | 1,234,567 |
:_ | Binlik ayırıcı (alt çizgi) | f"{1234567:_}" | 1_234_567 |
:.1% | Yüzde, 1 ondalık (100 ile çarpar) | f"{0.1575:.1%}" | 15.8% |
:10 | En az 10 karakter genişlik | f"{'ad':10}" | ad ve 8 boşluk |
:>10 / :<10 / :^10 | Sağa / sola / ortaya hizala | f"{'ad':^6}" | " ad " |
:05d | Tam sayı, sıfırla 5 haneye doldur | f"{42:05d}" | 00042 |
:+.1f | İşareti her zaman göster | f"{250.0:+.1f}" | +250.0 |
:e | Bilimsel gösterim | f"{1500000:.2e}" | 1.50e+06 |
oran = 0.1575
siparis_no = 42
fark = -1250.75
print(f"Faiz oranı: {oran:.2%}")
print(f"Sipariş no: {siparis_no:06d}")
print(f"Değişim: {fark:+,.2f} TL")
print(f"Başlık: {'Özet':-^30}")
print(f"{'Hesap':<12}{'Bakiye':>14}")
print(f"{'Ayşe':<12}{12500.5:>14,.2f}")
print(f"{'Mehmet Can':<12}{830:>14,.2f}")
f"{bakiye=}" yazımı hem adı hem değeri basar: bakiye=12500.5. Değişken takibi için print'e en hızlı yol., ayırıcısı İngiliz biçimidir (1,234.50). Türkçe "1.234,50" için en pratik yol biçimlendirip karakterleri takas etmektir: f"{x:,.2f}".replace(",", "X").replace(".", ",").replace("X", "."). locale modülü de var ama sistem ayarına bağlıdır, tarayıcıda ve sunucuda farklı davranabilir.format() ve str.format
f-string Python 3.6'da geldi; eski kodda ve bazı özel durumlarda iki akraba görürsün. format(deger, belirtec) fonksiyonu tek bir değeri biçimlendirir; belirteci bir değişkende tutabildiğin için "kullanıcı hangi ondalık sayısını istediyse" gibi dinamik senaryolarda işe yarar. "...{}...".format(x) yöntemi ise şablonu önceden hazırlayıp sonra doldurmak için kullanılır, örneğin bir ayar dosyasından okunan mesaj kalıbında.
x = 1234.5678
print(format(x, ",.2f"))
print(format(x, ">15.1f"))
ondalik = 3
print(f"{x:.{ondalik}f}") # f-string içinde iç içe belirteç
sablon = "Sayın {ad}, {tarih} itibarıyla bakiyeniz {bakiye:,.2f} TL'dir."
print(sablon.format(ad="Ayşe Yılmaz", tarih="28.10.2026", bakiye=12500.5))
print("{0} - {1} - {0}".format("A", "B"))
Günlük kodda tercih sırası: önce f-string, şablon dışarıdan geliyorsa str.format, tek değer ve dinamik belirteç için format(). % işaretli eski biçimlendirmeyi ("%s" % ad) yalnızca eski kodda görürsün; yeni kodda kullanma.
Çok satırlı metin
Üç tırnak, satır sonlarını koruyan metin oluşturur ve f-string ile birleşebilir. Bir hesap özetinin başlığı için biçilmiş kaftan:
musteri = "Ayşe Yılmaz"
hesap_no = "TR33 0006 1005 1978 6457 8413 26"
bakiye = 12500.5
baslik = f"""{'ILGAZ BANK':^40}
{'Hesap Özeti':^40}
{'=' * 40}
Müşteri : {musteri}
IBAN : {hesap_no}
Bakiye : {bakiye:,.2f} TL
{'=' * 40}"""
print(baslik)
Girintili bir fonksiyon içinde üç tırnaklı metin yazdığında satır başlarındaki boşluklar da metne girer; o durumda textwrap.dedent yardımcı olur. Tek satırlık uzun metinleri bölmek içinse yan yana yazılan tırnaklı parçalar otomatik birleşir: ("a" "b") → "ab".
Basit tablo yazdırma
Gerçek iş: hesap listesinden hizalı bir tablo üret. Kalıp hep aynıdır: başlık satırı, ayraç çizgisi, döngüde her satır aynı genişliklerle, en altta toplam. Genişlikleri bir yerde sabit tutarsan başlık ve satırlar asla kaymaz.
hesaplar = [
("Ayşe Yılmaz", "Vadesiz", 12500.5, 3),
("Mehmet Can", "Vadeli", 830, 0),
("Şirin Öztürk", "Vadesiz", 148200.75, 12),
("İlker Ağaoğlu", "Kredi", -5400, 1),
]
G_AD, G_TUR, G_BAK, G_ISL = 16, 9, 14, 6
print(f"{'Müşteri':<{G_AD}}{'Tür':<{G_TUR}}{'Bakiye (TL)':>{G_BAK}}{'İşlem':>{G_ISL}}")
print("-" * (G_AD + G_TUR + G_BAK + G_ISL))
toplam = 0
for ad, tur, bakiye, islem in hesaplar:
print(f"{ad:<{G_AD}}{tur:<{G_TUR}}{bakiye:>{G_BAK},.2f}{islem:>{G_ISL}}")
toplam += bakiye
print("-" * (G_AD + G_TUR + G_BAK + G_ISL))
print(f"{'TOPLAM':<{G_AD + G_TUR}}{toplam:>{G_BAK},.2f}")
Genişlikleri de süslü parantezle gömebildiğine dikkat et: {ad:<{G_AD}}. Sütun genişliğini verinin en uzun elemanından hesaplamak istersen max(len(h[0]) for h in hesaplar) + 2 yeter.
Türkçe karakter ve ljust/rjust
str.ljust(n), str.rjust(n) ve str.center(n) f-string hizalamasının yöntem hâlidir; ikinci argümanla dolgu karakteri alırlar. Bir listeyi sütun sütun yazdırırken f-string kadar sık kullanılır.
print("Şube".ljust(10, ".") + "|")
print("İzmir".rjust(10) + "|")
print("Çağrı".center(11, "*") + "|")
print(len("İzmir"), len("Izmir"))
print(len("Şirin Öztürk"), len("Sirin Ozturk"))
for sehir in ["İstanbul", "Çorum", "Iğdır", "Muş"]:
print(sehir.ljust(10) + "|" + str(len(sehir)).rjust(3))
Python 3 metinleri Unicode olarak tutar; len("İzmir") 5'tir, hizalama Türkçe harflerle de doğru çalışır. İki yerde dikkat: büyük-küçük dönüşümünde "ı".upper() "I", ama "I".lower() "i" verir (Türkçe noktasız ı Unicode'un genel kuralında yoktur), bu yüzden şehir adlarını karşılaştırırken casefold yerine sabit bir sözlükten yararlanmak daha güvenlidir. Ve terminal fontu bazı karakterleri farklı genişlikte çizebilir; hizalama karakter sayısına göredir, piksele göre değil.
:.2f kuruşu düzgün gösterir ama arka planda 0.1 + 0.2 hâlâ 0.30000000000000004'tür. Gösterim için f-string, hesap için (özellikle bankacılıkta) decimal.Decimal kullanılır. Orta seviyede ayrıntısına gireceğiz; şimdilik biçimlendirmenin hesabı değil görünümü düzelttiğini unutma.Belirteç dilinin tamamı
Derste belirtecin en sık kullanılan parçalarını gördün. Resmi adıyla biçim belirteci mini dili (format specification mini-language) şu sırayla okunur ve her parça isteğe bağlıdır:
[[dolgu]hizalama][işaret][#][0][genişlik][gruplama][.duyarlık][tür]
| Parça | Seçenekler | Örnek | Sonuç |
|---|---|---|---|
| Hizalama | < > ^ = (işaretten sonra doldur) | f"{-3.14:=+9.2f}" | - 3.14 |
| İşaret | + (her zaman), - (varsayılan), boşluk | f"{12.5:+.1f}" | +12.5 |
# | Alternatif biçim: önek ekler | f"{255:#x}" | 0xff |
0 | Sayıyı sıfırla doldur | f"{42:06d}" | 000042 |
| Gruplama | , ya da _ | f"{1234567:_}" | 1_234_567 |
| Tür | d f e g % b o x s | f"{10:08b}" | 00001010 |
degisim = [12.5, -3.25, 0.0]
for d in degisim:
print(f"{d:+7.2f}%") # işaret her zaman görünür
siparis_no = 42
print(f"AM-{siparis_no:06d}") # sıfır dolgulu numara
print(str(siparis_no).zfill(6)) # metin yöntemiyle aynı iş
print(f"{255:#x} {255:#o} {10:08b}") # onaltılık, sekizlik, ikilik
print(f"{0.256:.1%}", f"{1234.5:g}", f"{0.00001234:g}")
g türü "akıllı" gösterimdir: sayının büyüklüğüne göre sabit ya da bilimsel gösterimi seçer ve gereksiz sıfırları atar. Fatura, sipariş ve müşteri numaralarında ise sıfır dolgusu (06d) standarttır; sıralamada "AM-000042"nin "AM-000100"den önce gelmesini sağlar.
İç içe alanlar: dinamik genişlik
Genişlik ve ondalık sayısı da birer ifade olabilir: belirtecin içine yeni bir {} yazarsın. Sütun genişliğini veriden hesaplayan tablolar böyle kurulur.
urunler = [("çay", 5.5), ("filtre kahve", 120), ("su", 7.25)]
genislik = max(len(ad) for ad, _ in urunler) + 2
ondalik = 2
print(f"{'Ürün':<{genislik}}{'Fiyat':>10}")
print("-" * (genislik + 10))
for ad, fiyat in urunler:
print(f"{ad:.<{genislik}}{fiyat:>10.{ondalik}f}")
!r, !s ve = ile hata ayıklama
Alanın sonuna !r eklersen değer repr() ile, yani "programcı gözüyle" gösterilir: metinler tırnaklı, görünmeyen boşluklar ve özel karakterler görünür hale gelir. Kullanıcıdan ya da dosyadan gelen bir değerin neden eşleşmediğini anlamanın en hızlı yolu budur. = eki ise ifadenin kendisini de yazar.
sehir = "İzmir " # sonda gizli boşluk
print(f"[{sehir}] [{sehir!r}]")
print(f"{sehir == 'İzmir'=}")
print(f"{len(sehir)=}, {sehir.strip()!r}")
satir = "Ayşe\tİzmir\n"
print(f"{satir!r}") # sekme ve satır sonu görünür
Eski % biçimlendirme ve logging
Yeni kodda f-string yazarsın ama % biçimini tanıman gerekir: eski projelerde ve özellikle logging modülünde hâlâ kullanılır. %s metin, %d tam sayı, %.2f ondalık demektir; değerler % işaretinden sonra demet olarak verilir. Tür uyuşmazsa (%d'ye metin vermek) TypeError alırsın.
ad, tutar, adet = "Ayşe", 1250.5, 3
print("%s: %d ürün, %.2f TL" % (ad, adet, tutar))
print("%-8s|%8.1f|" % (ad, tutar)) # - sola hizalar
# logging.info("Sipariş %s alındı", no) → değer, mesaj gerçekten yazılacaksa birleştirilir
Logging'de logging.info(f"... {no}") yerine logging.info("... %s", no) yazılmasının sebebi budur: mesaj seviyesi kapalıysa birleştirme hiç yapılmaz. Ayrıntısını Loglama dersinde göreceksin.
Dışarıdan gelen şablonlar: string.Template
SMS ya da e-posta metnini pazarlama ekibi yazıyorsa şablon kullanıcıdan gelir. Böyle şablonlarda f-string ve str.format risklidir: format şablonu nesnelerin niteliklerine erişebilir ({musteri.__class__}). string.Template yalnızca $ad biçimindeki basit yer tutucuları doldurur; safe_substitute eksik alanları hata vermeden olduğu gibi bırakır.
from string import Template
sablon = Template("Sayın $ad, $tutar TL tutarlı siparişiniz $kargo ile yola çıktı.")
print(sablon.substitute(ad="Ayşe Kaya", tutar="1.250,00", kargo="Anadolu Kargo"))
print(sablon.safe_substitute(ad="Can")) # eksikler aynen kalır
try:
sablon.substitute(ad="Can")
except KeyError as h:
print("Eksik alan:", h)
Uzun metinler: textwrap
Ürün açıklaması ya da adres gibi uzun metinleri sabit genişlikte satırlara bölmek için textwrap.fill, tek satıra sığdırıp sonunu kısaltmak için textwrap.shorten kullanılır. İkisi de kelimeleri bölmez.
import textwrap aciklama = "Paslanmaz çelik gövdeli, 1.7 litre kapasiteli, otomatik kapanma özellikli su ısıtıcısı." print(textwrap.fill(aciklama, width=30)) print(textwrap.shorten(aciklama, width=40, placeholder="..."))
bicim.py gibi küçük bir modülde fonksiyon olarak yazar (tl(), yuzde(), fatura_no()) ve tüm raporlar onları kullanır. Daha büyük HTML/e-posta şablonları için Jinja2'yi Şablonla rapor üretme dersinde göreceksin.__format__ ve özel biçimler
Belirteçler yalnızca sayı ve metin için değildir. datetime nesneleri kendi belirteçlerini tanır: f"{tarih:%d.%m.%Y}". Kendi sınıfını yazdığında __format__ yöntemini tanımlayarak f"{hesap:kisa}" gibi özel biçimler kurabilirsin. Bu, raporlama kütüphanelerinin arkasındaki mekanizmadır.
Gerçek raporlarda ne kullanılır?
Düz metin tablo, log dosyaları ve terminal araçları için hâlâ standarttır. E-posta ve web raporlarında aynı veriyi HTML tablosuna, Excel'e ya da PDF'e dökersin; pandas'ın to_string, to_markdown ve to_excel yöntemleri bu dersteki kalıbın endüstriyel sürümüdür. tabulate ve rich gibi üçüncü parti paketler de renkli, çerçeveli terminal tabloları üretir. Temel mantık değişmez: genişlik, hizalama, ondalık.
Biçimlendirme ve güvenlik
Kullanıcıdan gelen metni doğrudan str.format şablonu olarak kullanma: kullanici_metni.format(...) yazımı, kötü niyetli bir şablonun nesne özelliklerine erişmesine kapı açar. Şablon senin, veri kullanıcının olsun; f-string bu açıdan daha güvenlidir çünkü şablon kodun içindedir.