Python Stringler (Metinler) ve Temel Metin İşlemleri
Stringler (Metinler) ve Temel Metin İşlemleri
7. derste Sayılar ve Matematiksel İşlemler konusunu işlemiştik; Python’ın hesap makinesi tarafını baştan sona kurcalamıştık. Güzel günlerdi. Ama dürüst olalım: gerçek hayatta bir programın günlük mesaisinin büyük kısmı sayılarla değil, metinle geçer. Kullanıcı adları, e-posta adresleri, log satırları, API’den dönen cevaplar, ürün başlıkları… Hepsi metin. Python’da metnin tipi str‘dir, yani string. Bu derste str’nin nasıl yaratıldığını, nasıl okunduğunu ve nasıl işlendiğini tek tek göreceğiz. Yıllar önce bana saatler kaybettiren iki klasiği de anlatacağım; sen o tuzaklara düşmeyesin diye.
str: Metnin Python’daki Karşılığı
Python’da metni üç farklı tırnak biçimiyle yazabilirsin: tek tırnak ('...'), çift tırnak ("...") ve üçlü tırnak ("""...""" ya da '''...'''). Üçü de aynı str tipini üretir; aralarında davranış farkı yok, sadece yazım kolaylığı farkı var.
# Üç tırnak biçimi de aynı str tipini üretir
ad = 'Zeynep'
mesaj = "Merhaba dünya"
siir = """Birinci satır
İkinci satır
Üçüncü satır"""
# Üçü de aynı tipte
print(type(ad)) # <class 'str'>
print(type(mesaj)) # <class 'str'>
print(type(siir)) # <class 'str'>
Arka planda ne oluyor? Python tırnak işaretlerini görünce metnin nerede başlayıp nerede bittiğini tespit ediyor ve bellekte bir str nesnesi oluşturuyor. Tek ile çift tırnak arasında teknik fark sıfır; seçim tamamen zevk meselesi. Üçlü tırnak ise özel: içine satır atlayarak yazılan metni olduğu gibi koruyor, \n yazmaya gerek bırakmıyor.
Pratik kural şu: metnin içinde kesme işareti varsa dışarıda çift tırnak kullan. 'Python'ın gücü' yazarsan Python ikinci kesme işaretinde stringin bittiğini sanıp kafası karışır. "Python'ın gücü" ise tertemiz çalışır. Tersi de geçerli: içeride çift tırnak varsa dışarıya tek tırnak koyarsın.
Kaçış Karakterleri: Görünmeyen Tuşlar
Metnin içine satır sonu, sekme ya da tırnak işaretinin kendisini gömmek istediğinde kaçış karakterleri devreye girer. Ters eğik çizgi (backslash, \) Python’a “sıradaki karakter özel anlamda” der.
# \n satır sonu, \t sekme, \" çift tırnak, \\ ters eğik çizgi demektir
yol = "C:\\kullanicilar\\zeynep\\belgeler"
kartvizit = "Ad: Zeynep\nŞehir: İzmir\tMeslek: Mühendis"
alinti = "Python'ın deyimiyle: \"Basit, açık olmaktan iyidir.\""
print(yol) # C:\kullanicilar\zeynep\belgeler
print(kartvizit) # 'Ad: Zeynep' ayrı satırda; 'Meslek' önünde sekme boşluğu
print(alinti) # Python'ın deyimiyle: "Basit, açık olmaktan iyidir."
Arka planda ne var? Backslash’ten sonra gelen harf, klavyede basamayacağın bir karaktere dönüşüyor: \n yeni satır, \t sekme, \" ve \' ise stringi kesmeyen sıradan tırnaklar. Windows dosya yollarında ters eğik çizgi kullanıldığı için orada \\ yazmak şart. Tek \ bırakırsan Python \k gibi tanımsız bir kaçışla karşılaşır; ya hata alırsın ya da istemediğin bir karakter çıkıverir.
İndeksleme: Metnin İçinde Adres Sormak
String, karakterlerin sıralandığı bir dizidir. Her karakterin bir indeks numarası vardır ve numaralandırma sıfırdan başlar. İlk karakter metin[0], ikincisi metin[1]. Programcılıkta saymaya sıfırdan başlanır; garip ama gerçek, bir kez alıştın mı bir daha bırakamazsın.
sehir = "İstanbul"
print(sehir[0]) # 'İ' -> ilk karakter
print(sehir[1]) # 's' -> ikinci karakter
print(sehir[-1]) # 'l' -> son karakter, negatif indeks sondan sayar
print(sehir[-2]) # 'u' -> sondan ikinci karakter
print(sehir[0:5]) # 'İstan' -> 0'dan 4. indekse kadar dilim
print(sehir[8]) # IndexError: string index out of range
Arka planda ne oluyor? Python sehir[0] gördüğünde stringin ilk karakterini alıp sana tek karakterlik yeni bir string döndürüyor. Negatif indeksler sondan sayar; -1 uzunluktan bağımsız olarak her zaman son karakterdir. sehir[0:5] ise bir dilimleme (slicing); 0’dan başlar, 5. indeksten hemen önce durur. Son satırdaki hata da dersimizin ilk klasiği.
İlk acı tecrübemi anlatayım. Yıllar önce, ilk Python işlerimden birinde, sipariş dosyasındaki her satırın son karakterini kontrol eden bir betik yazmıştım. Satırlar hep dolu olduğu için betik günlerce sorunsuz çalıştı. Ta ki bir gece dosyanın sonuna boş bir satır eklenene dek. Gece yarısı çalışan raporlama o gece IndexError ile çöktü; sabah kimse rapor alamadı. Sebebi çok basitti: boş stringin hiç karakteri yoktur, [0]‘ı bile yoktur. O gün öğrendim: indeksle erişmeden önce uzunluğu ölç, boş string ihtimalini hep hesaba kat.
len() ile Uzunluğu Ölçmek
len() fonksiyonu bir stringin kaç karakter tuttuğunu söyler. Boşluklar da sayılır, Türkçe karakterler de; Python 3 her Unicode karakteri tek karakter kabul eder.
kullanici_adi = "zeynep_k"
bos = ""
print(len(kullanici_adi)) # 8
print(len("Python")) # 6
print(len(bos)) # 0
# Boş olup olmadığını böyle de test edebilirsin
print(len(bos) == 0) # True
Arka planda ne var? len() stringin içindeki karakterleri sayıp bir tam sayı döndürüyor; len("İ") sana 1 verir, çünkü Python 3’te harfler tek tek sayılır. Sıfır uzunluğu, “bu değişken bomboş” demenin en güvenilir kanıtıdır. Form doğrulamada, dosya okumada, API cevaplarında bu kontrolü sürekli kullanacaksın; input() ile birleşince tadından yenmeyecek.
Metin Metotları: upper(), lower(), strip(), replace(), split(), join()
str tipi, üzerinde iş yapan hazır metotlarla doludur. Metot, bir değerin üzerine nokta ile çağırdığın fonksiyondur: metin.metot(). En sık kullanılan altılıyı tek örnekte toplayalım:
mesaj = " Merhaba, Dünya! "
print(mesaj.strip()) # 'Merhaba, Dünya!' -> iki uçtaki boşluklar gitti
print(mesaj.lower()) # ' merhaba, dünya! ' -> hepsi küçük harf
print(mesaj.upper()) # ' MERHABA, DÜNYA! ' -> hepsi büyük harf
print(mesaj.replace("Dünya", "Türkiye")) # ' Merhaba, Türkiye! '
print(mesaj.split(",")) # [' Merhaba', ' Dünya! '] -> parçalara böldü
# join, split'in tersidir: listedeki öğeleri ayraçla yapıştırır
meyveler = ["elma", "armut", "kiraz"]
print("-".join(meyveler)) # 'elma-armut-kiraz'
Arka planda neler oluyor? strip() iki uçtaki boşlukları, satır sonlarını ve sekmeleri kırpıyor; lower() ve upper() harf boyutlarını çeviriyor. replace() aranan her parçayı yeni değerle değiştirip yeni bir string üretiyor. split() verdiğin ayraca göre metni bölüp bir liste döndürüyor (listeleri ileriki modüllerde ayrıntılı işleyeceğiz), join() ise tam tersini yapıp liste öğelerini ayraçla birleştiriyor. Pratik gücünü şöyle düşün: “Zeynep;32;İzmir” gibi bir CSV satırını split(";") ile üç parçaya ayırır, sonra her parçayı strip() ile temizlersin.
in ile Alt Metin Aramak
Bir stringin içinde bir parçanın var olup olmadığını in operatörü söyler. Sonuç True ya da False olur, yani bool bir değer döner.
cumle = "Python öğrenmek sabır işidir"
print("Python" in cumle) # True
print("java" in cumle) # False -> küçük harfle aranınca bulunamadı
print("sabır" not in cumle) # False -> var olduğu için 'not in' yanlış der
# Pratik kullanım: e-posta adresinde @ işareti kontrolü
eposta = "zeynep@ornek.com"
print("@" in eposta) # True
Arka planda ne var? in, aradığı parçayı metnin içinde soldan sağa tarıyor ve ilk bulduğu anda True dönüyor. Arama büyük-küçük harf duyarlıdır; "python" in cumle yazsaydın sonuç False olurdu. O yüzden aramadan önce iki tarafı da lower() ile küçük harfe çevirmek, projelerde sık göreceğin bir alışkanlıktır.
Değiştirilemezlik: Stringlerle Güreşemezsin
Gelelim str’nin en çok şaşırılan özelliğine: stringler değiştirilemezdir (immutable). Yaratıldıktan sonra tek bir karakterini bile yerinde değiştiremezsin. Denemek bedava:
isim = "zeynep"
# Klasik yeni başlayan hatası: karakteri yerinde değiştirmeye çalışmak
isim[0] = "Z"
# TypeError: 'str' object does not support item assignment
Python sana açıkça “str nesneleri öğe atamayı desteklemiyor” diyor. Bellekteki “zeynep” olduğu yerde duruyor; ona dokunmak yasak.
İkinci acı tecrübemi anlatayım. Yıllar önce Python’a yeni başladığım haftalarda, dosya adlarındaki boşlukları alt çizgiye çeviren küçük bir araç yazmıştım. Kodun kalbi şu satırdı: dosya_adi.replace(" ", "_"). Program çalışıyordu, hata yoktu, ama dosya adları aynen kalıyordu. İki saat boyunca her satırı kontrol ettim; sonunda anladım: replace() orijinal stringe dokunmuyor, değiştirilmiş halini yeni bir string olarak döndürüyor, ben de dönen sonucu hiçbir yere kaydetmiyordum. Çözüm iki kelimeydi: sonucu atamak.
Değişiklik Yapmanın Doğru Yolu: Yeni String Üretmek
Madem stringler kilitli, değişiklik nasıl yapılacak? Cevap basit: metodu çağırırsın, dönen yeni stringi bir değişkene bağlarsın. Orijinal yerinde durur, yenisi doğar.
mesaj = "Merhaba Dünya"
# Doğru yol: sonucu yeni bir değişkene kaydetmek
yeni_mesaj = mesaj.replace("Dünya", "Türkiye")
print(mesaj) # 'Merhaba Dünya' -> orijinal aynen duruyor
print(yeni_mesaj) # 'Merhaba Türkiye' -> yeni string doğdu
# Aynı ismi yeniden bağlamak da serbest
mesaj = mesaj.upper()
print(mesaj) # 'MERHABA DÜNYA'
Arka planda ne oluyor? mesaj.replace(...) bellekte yepyeni bir string yaratıp onu döndürüyor; sen yeni_mesaj ismini o nesneye bağlıyorsun. mesaj = mesaj.upper() satırında eski string hâlâ bellekte ama ona bağlı isim kalmadı; Python’ın çöp toplayıcısı onu vakti gelince temizliyor. Büyük metinleri döngüde + ile büyütmek yerine parçaları listede toplayıp sonunda join() ile birleştirmenin daha hızlı olmasının sebebi de işte bu yeni-nesne-yaratma maliyeti.
Küçük Bir Uygulama: Kirli Veriyi Temizlemek
Öğrendiklerimizi gerçek bir senaryoda birleştirelim. Bir formdan gelen ürün adını temizleyip standart bir hale getireceğiz:
# Formdan geldiği haliyle kirli bir değer
urun_adi = " Kablosuz Kulaklık PRO-X "
# Önce iki ucu kırp, sonra her şeyi küçük harfe çevir
temiz = urun_adi.strip().lower()
print(temiz)
# 'kablosuz kulaklık pro-x'
# Boşlukları tireye çevirip URL dostu bir slug üret
slug = temiz.replace(" ", "-")
print(slug)
# 'kablosuz-kulaklık-pro-x'
# Orijinalin hâlâ kirli durduğunu doğrula
print(urun_adi)
# ' Kablosuz Kulaklık PRO-X '
Arka planda ne oluyor? strip() ile lower() zincirlenebiliyor; çünkü her metot bir string döndürüyor ve bir sonraki metot o sonuç üzerinde çağrılıyor. replace() de aynı zincirin devamı. En sonda urun_adi hâlâ kirli haliyle duruyor; bütün temizlik yeni stringler üzerinde yürüdü. Web sitelerinde gördüğün “slug” denen URL dostu metinler, özünde tam olarak böyle üretiliyor.
Sık Sorulan Sorular
upper() çağırdım ama değişkenim değişmedi. Kodda hata mı var?
Hayır, Python’ın çalışma şekli bu. Stringler değiştirilemez olduğundan upper(), strip(), replace() gibi metotlar orijinale dokunmaz; sonucu yeni bir string olarak döndürür. Dönen değeri bir değişkene atamayı unutma: metin = metin.upper().
Tek tırnak mı yoksa çift tırnak mı kullanmalıyım?
Teknik olarak fark yok; ikisi de aynı str tipini üretir. Önemli olan bir proje içinde tutarlı kalmak. Metnin içinde kesme işareti varsa dışarıda çift tırnak, içeride çift tırnak varsa dışarıda tek tırnak kullanmak en pratik kuraldır.
strip() yalnızca boşlukları mı siler?
Yalnızca boşluğu değil; satır sonu (\n), sekme (\t) ve satır başı (\r) dahil tüm boşluk benzeri karakterleri iki uçtan temizler. Sadece sol uç için lstrip(), sağ uç için rstrip() kullanılır.
Sırada Ne Var?
Artık metinleri kesebiliyor, temizleyebiliyor ve içinde arama yapabiliyorsun. Ama bir eksiğimiz var: değişkenlerin değerini metnin ortasına zarifçe gömmeyi öğrenmedik. + ile birleştirme iş görüyor ama uzadıkça dağılıyor. Bir sonraki derste f-string ile Biçimlendirme konusunu işliyoruz; para tutarlarını, yüzdeleri ve tarihleri metnin içinde şık biçimde yazmayı orada öğreneceksin.
