Dosyalar ve basit CSV
Şimdiye kadar verileri doğrudan kodun içine yazdık. Bu değerler yalnızca kodda duruyordu; program kapanınca ya da kod değişince onları saklayan ayrı bir yer yoktu. Gerçek uygulamalarda veri çoğu zaman bir dosyadan okunur, sonuç da bir dosyaya yazılır.
Bu hafta önce metin dosyası açmayı, okumayı ve yazmayı öğreneceğiz. Sonra satır ve sütunlardan oluşan basit CSV dosyalarını okuyacağız. Sayfadaki çalıştırılabilir örneklerde bilgisayarınızdaki dosyalara dokunmayacağız; dosya yerine bellekte duran bir metni dosya gibi okuyacağız. Kendi bilgisayarınızda çalıştıracağınız gerçek dosya kodunu da ayrıca göstereceğiz.
Bu bölümün kapsamı
with open(...)kullanımı ve"r","w","a"kipleriread(),readlines()ve dosya üzerindeforfarkı- Kodlama seçeneğinin etkisi ve BOM sorunu
- CSV’de sütun ayıracını
delimiterile belirleme csv.DictReaderile okunan alanların metin gelmesi ve dönüştürülmesi- Ondalık virgül sorunu ve
replace()çözümünün sınırı - Okuma, dönüştürme ve hesaplama adımlarının ayrılması
csv.writerile yazma seçeneklerinin ayrıntısıpathlibile yol işlemlerinewline=""seçeneğinin satır sonlarını nasıl işlediği (kalıbın parçası olarak yazılır, ayrıntısı sorulmaz)- İkili (binary) dosya kipleri
Bu başlıklar konunun devamıdır; ileride karşınıza çıkar ama bu derste ezberlemeniz beklenmiyor.
Excel’den gelen CSV neden tek sütun görünüyor?
Excel’de hazırladığınız bir tabloyu CSV olarak kaydedip Python’la okudunuz ve her satır tek bir sütun olarak geldi. Böyle dosyalarda üç sorun sık görülür: sütunlar virgülle değil noktalı virgülle ayrılmış olabilir, dosyanın başında görünmeyen bir işaret bulunabilir, ondalık sayılar 12,5 gibi virgülle yazılmış olabilir. Üçü ayrı sorunlardır ve ayrı ayrı çözülür.
Dosya neden gerekir?
Bir programın şu verilerle çalıştığını düşünelim:
puanlar = [70, 82, 91, 65]Bu liste kodun içinde yazılı. Oysa puanlar başka bir programdan gelmiş ya da bir kullanıcı onları daha önce kaydetmiş olabilir. O zaman veri bir dosyada durur.
puanlar.txt:
70
82
91
65
Program bu dosyayı okuyup veriyi işleyebilir.
with open(...) ile dosya açmak
with open("puanlar.txt", "r", encoding="utf-8") as dosya:
icerik = dosya.read()
print(icerik)Burada:
"puanlar.txt"dosya yoludur,"r"okuma kipidir,encoding="utf-8"dosyanın kodlamasını (encoding) belirtir. Kodlama, dosyada saklanan byte’ların hangi harflere karşılık geldiğini söyleyen kuraldır;ğ,şgibi harflerin doğru okunması buna bağlıdır,as dosyaaçılan dosya nesnesine verdiğimiz addır.
with bloğu bitince Python dosyayı kendiliğinden kapatır.
Bu derste dosyaları with open(...) kalıbıyla açacağız.
Okuma, yazma ve ekleme kipleri
open() çağrısındaki ikinci argüman kipi (mode) belirler, yani dosyayla ne yapacağınızı:
| Kip | Anlam |
|---|---|
"r" |
Dosyayı okumak |
"w" |
Dosyaya yazmak; dosya varsa önce eski içeriği silinir |
"a" |
Dosyanın sonuna ekleme yapmak (append) |
"w" örneği:
with open("sonuc.txt", "w", encoding="utf-8") as dosya:
dosya.write("İşlem tamamlandı\n")"a" örneği:
with open("gunluk.txt", "a", encoding="utf-8") as dosya:
dosya.write("Yeni kayıt\n")"a" var olan içeriği silmez, yeni satırı sona ekler. Bir günlüğe ya da rapora satır eklerken işe yarar.
"w" ile yanlış dosyayı açarsanız içindeki veri silinir. "a" ise program her çalıştığında yeniden ekler; başlık satırını da her seferinde yazarsanız dosyada aynı başlık birkaç kez görünür. Kipi seçmeden önce dosyada sonunda neyin kalması gerektiğini düşünün.
Dosya yolu ve çalışma dizini
Dosya adının önüne klasör adı da yazabilirsiniz:
open("veriler/puanlar.txt", "r", encoding="utf-8")Python bu yolu programın çalışma dizinine göre arar. Çalışma dizini, programın o anda içinde çalıştığı klasördür. Dosya orada bulunamazsa FileNotFoundError alırsınız.
Bu hatayı aldığınızda iki soruya bakın:
Program hangi dizinden çalışıyor?
Dosya yolu o dizine göre doğru mu?
read(), readlines() ve satır satır okuma
Bir dosyayı üç farklı biçimde okuyabiliriz.
read() → tek bir string
with open("puanlar.txt", "r", encoding="utf-8") as dosya:
icerik = dosya.read()
print(type(icerik)) # strread() dosyanın henüz okunmamış kısmını, yeni açılmış bir dosyada bütün içeriği tek bir string olarak döndürür.
Bu yüzden şu döngü
with open("puanlar.txt", "r", encoding="utf-8") as dosya:
for deger in dosya.read():
print(deger)satırlar üzerinde değil, karakterler üzerinde dolaşır.
readlines() → string listesi
with open("puanlar.txt", "r", encoding="utf-8") as dosya:
satirlar = dosya.readlines()
print(type(satirlar)) # listListenin her elemanı bir satırı tutan string’dir. Satırların sonunda \n (satır sonu karakteri) bulunur; dosyanın son satırında olmayabilir.
Dosya nesnesi üzerinde for → satır satır
with open("puanlar.txt", "r", encoding="utf-8") as dosya:
for satir in dosya:
print(satir.strip())Satır satır çalışırken bu derste çoğunlukla for satir in dosya yazacağız. Büyük dosyalarda hangisinin daha hızlı olduğuna girmeyeceğiz. Bilmeniz gereken şu: hangi yazım karakterleri, hangisi tek bir string’i, hangisi satır listesini verir.
Tarayıcıda dosyayı taklit etmek
StringIO, bellekteki bir metni dosya gibi okunabilen bir nesneye çevirir. Üzerinde for ile satır satır dolaşabilirsiniz. Kendi bilgisayarınızda gerçek bir dosyayla çalışırken bunun yerine with open(...) yazarsınız.
Alıştırma: satırlardaki sayıları toplayın
Her satırı sayıya çevirip toplama ekleyin. Beklenen sonuç 500’dür.
deger = int(satir.strip()) ve toplam = toplam + deger kullanabilirsiniz.
from io import StringIO
veri = """120
145
98
137
"""
dosya = StringIO(veri)
toplam = 0
for satir in dosya:
deger = int(satir.strip())
toplam = toplam + deger
print(toplam)CSV nedir?
CSV, bir tabloyu düz metin olarak saklamanın yaygın bir yoludur. Dosyanın her satırı tablonun bir satırıdır; sütunların arasına bir ayraç konur. Adı “comma-separated values” (virgülle ayrılmış değerler) ifadesinden gelir; ama ayraç her zaman virgül değildir.
Basit örnek:
ad,puan,bolum
Deniz,78,Bilgisayar
Ayse,92,Siber
Mert,55,Bilgisayar
Bir CSV satırını split(",") ile kendiniz bölerseniz içinde virgül geçen alanlarda yanlış sonuç alabilirsiniz. Böyle bir alan CSV’de "Ankara, Çankaya" gibi tırnak içinde yazılır ve split() tırnağı tanımaz. Bu yüzden Python’ın standart kütüphanesindeki csv modülünü kullanacağız.
csv.reader ve newline=""
import csv
with open("ogrenciler.csv", "r", encoding="utf-8", newline="") as dosya:
okuyucu = csv.reader(dosya)
for satir in okuyucu:
print(satir)newline="" yazınca satır sonlarını csv modülü kendi kurallarıyla işler. Python belgeleri bu kalıbı önerir: böylece Windows’ta da öteki işletim sistemlerinde de CSV okurken ya da yazarken fazladan boş satır ve satır sonu karışıklığı çıkmaz.
newline="" sütunları ayırmaz. Sütunlar arasındaki virgülü ya da noktalı virgülü, yani sütun ayıracını delimiter belirler; newline yalnızca satır sonlarıyla ilgilidir.
CSV açarken newline="" yazmayı alışkanlık hâline getirin. Nasıl çalıştığı sınavda sorulmaz.
csv.DictReader
import csv
with open("ogrenciler.csv", "r", encoding="utf-8", newline="") as dosya:
okuyucu = csv.DictReader(dosya)
for kayit in okuyucu:
print(kayit["ad"], kayit["puan"])DictReader her satırı bir sözlük olarak verir. Sözlüğün anahtarları dosyanın ilk satırındaki sütun adlarıdır (ad, puan, bolum). Değerlerin hepsi string olarak gelir: "78" bir sayı değil, metindir. Hesap yapmadan önce dönüştürmeniz gerekir:
puan = int(kayit["puan"])Türkiye’de Excel’den gelen CSV: ;, BOM ve ondalık virgül
Kendi hazırladığınız CSV dosyasında ayraç, ders örneklerindeki gibi , olmayabilir. Türkçe bölgesel ayarlarla çalışan Excel ve benzeri tablo programlarının (bilgisayarın bölge ayarı Türkiye olduğunda) kaydettiği dosyalar çoğu zaman şöyle görünür:
urun;adet;fiyat
Kalem;2;12,5
Defter;3;45,0
Bu dosyada üç ayrı sorun var.
1. Sütun ayıracı noktalı virgül olabilir
csv.DictReader(dosya) varsayılan olarak virgül bekler. Bu yüzden noktalı virgüllü bir satırı tek bir sütun gibi okuyabilir. Ayıracı açıkça verin:
okuyucu = csv.DictReader(dosya, delimiter=";")2. Dosyanın başında UTF-8 BOM olabilir
Bazı programlar UTF-8 kodlamalı bir CSV dosyasının en başına BOM (byte order mark) adlı görünmez bir işaret koyar. Dosyayı encoding="utf-8" ile açarsanız bu işaret ilk sütun adına yapışır: "ad" yerine "\ufeffad" gelir.
Böyle dosyaları utf-8-sig kodlamasıyla açın:
with open("veri.csv", "r", encoding="utf-8-sig", newline="") as dosya:
okuyucu = csv.DictReader(dosya, delimiter=";")utf-8-sig, dosyanın başında BOM varsa okurken onu atar. BOM olmayan bir UTF-8 dosyayı da okuyabilir. Yine de dosyanın hangi kodlamayla kaydedildiğini bilmek her zaman daha iyidir.
3. Ondalık ayıracı virgül olabilir
float("12,5")ValueError verir, çünkü float() ondalık ayracı olarak nokta bekler. Verinin biçimini biliyorsanız ve virgül yalnızca ondalık ayracı olarak kullanılıyorsa virgülü noktaya çevirip dönüştürebilirsiniz:
fiyat_metni = kayit["fiyat"]
fiyat = float(fiyat_metni.replace(",", "."))Üç çözüm bir arada:
import csv
with open("satislar.csv", "r", encoding="utf-8-sig", newline="") as dosya:
okuyucu = csv.DictReader(dosya, delimiter=";")
for kayit in okuyucu:
adet = int(kayit["adet"])
fiyat = float(kayit["fiyat"].replace(",", "."))
print(kayit["urun"], adet, fiyat)replace(",", ".") yalnızca sayıların ondalık virgülle ve binlik ayracı olmadan yazıldığını bildiğiniz basit örneklerde güvenlidir. 1.234,56, 1 234,56 ya da başka bölgesel sayı biçimlerinde bu yöntem işe yaramaz. Böyle veride önce sayıların nasıl yazılacağı açıkça kararlaştırılmalı ya da bölgesel biçimleri tanıyan bir araç kullanılmalıdır.
Bir CSV dosyasının biçimini uzantısına bakıp tahmin etmeyin. Ayracı, kodlamayı ve sayıların nasıl yazıldığını dosyayı açıp öğrenin. Excel’den aldığınız dosyada ;, BOM ya da 12,5 görmeniz Python’ın bozulduğunu göstermez; bunlar veriyle çalışırken sık karşılaşılan sorunlardır.
Tarayıcıda CSV okuma
Alıştırma: geçen öğrencileri sayın
Puanı 60 ve üzeri olan öğrencileri sayın. Beklenen sonuç 3’tür.
puan = int(kayit["puan"]) ile başlayın.
import csv
from io import StringIO
veri = """ad,puan
Deniz,78
Ayse,92
Mert,55
Ece,60
"""
dosya = StringIO(veri)
okuyucu = csv.DictReader(dosya)
gecen_sayisi = 0
for kayit in okuyucu:
puan = int(kayit["puan"])
if puan >= 60:
gecen_sayisi = gecen_sayisi + 1
print(gecen_sayisi)CSV’den sözlük listesi oluşturmak
Okunan her satırdan yeni bir sözlük oluşturup puan alanını sayıya çeviriyoruz:
import csv
ogrenciler = []
with open("ogrenciler.csv", "r", encoding="utf-8", newline="") as dosya:
okuyucu = csv.DictReader(dosya)
for kayit in okuyucu:
ogrenci = {
"ad": kayit["ad"],
"puan": int(kayit["puan"]),
"bolum": kayit["bolum"]
}
ogrenciler.append(ogrenci)Artık elimizde 11. haftadaki gibi bir sözlük listesi var; orada yazdığımız fonksiyonları bu listeyle de kullanabiliriz. Akış şöyle:
dosyadan okuma
↓
türleri dönüştürme
↓
program içi veri yapısı
↓
hesaplama / filtreleme / raporlama
Dosyaya yazmak ve eklemek
Yeni bir rapor dosyası yazmak:
toplam = 450
with open("rapor.txt", "w", encoding="utf-8") as dosya:
dosya.write(f"Toplam satış: {toplam:.2f} TL\n")Var olan bir günlüğün ya da raporun sonuna satır eklemek:
with open("rapor.txt", "a", encoding="utf-8") as dosya:
dosya.write("Yeni ölçüm eklendi\n")csv.DictWriter gibi CSV yazma araçları sınavda sorulmaz. Bu hafta okuma, dönüştürme ve rapor yazma akışını anlamanız yeterli.
Dosyayla çalışırken çıkan hatalar
Dosyayla çalışırken kodunuz söz dizimi açısından doğru olsa bile hata alabilirsiniz:
- Dosya bulunamıyor →
FileNotFoundError, - Beklenen sütun yok →
kayit["puan"]gibi erişimlerdeKeyError, "puan"alanı sayı değil →ValueError,- Dosya boş,
- Karakter kodlaması beklenenden farklı →
UnicodeDecodeErrorgibi hatalar, delimiteryanlış → beklenen sütunlar hiç oluşmayabilir.
13. haftada bu hatalara döneceğiz: FileNotFoundError, KeyError, ValueError gibi istisnaları (program çalışırken çıkan ve programı durduran hataları) try/except ile yakalamayı ve hata mesajını okumayı öğreneceğiz.
Üret: CSV satış özeti
Fonksiyon satırları DictReader ile okusun ve tutar alanını float ile sayıya çevirsin. İki değer döndürsün: toplam satış tutarı ve tutarı 100 TL ya da daha fazla olan satış sayısı. Dönüş biçimi (toplam, yuksek_satis_sayisi) olmalı. Beklenen sonuç (545.0, 2).
dosya = StringIO(veri), okuyucu = csv.DictReader(dosya) ve her kayıtta tutar = float(kayit["tutar"]) kullanabilirsiniz.
import csv
from io import StringIO
def csv_satis_ozeti(veri):
dosya = StringIO(veri)
okuyucu = csv.DictReader(dosya)
toplam = 0
yuksek_satis_sayisi = 0
for kayit in okuyucu:
tutar = float(kayit["tutar"])
toplam = toplam + tutar
if tutar >= 100:
yuksek_satis_sayisi = yuksek_satis_sayisi + 1
return toplam, yuksek_satis_sayisiKendi bilgisayarınızda deneyin
Bölümün başındaki puanlar.txt dosyasını oluşturun. Aynı klasöre şu programı kaydedip çalıştırın:
toplam = 0
adet = 0
with open("puanlar.txt", "r", encoding="utf-8") as dosya:
for satir in dosya:
puan = int(satir.strip())
toplam = toplam + puan
adet = adet + 1
print("Ortalama:", toplam / adet)Sonra Excel’de ya da başka bir tablo programında küçük bir tablo hazırlayıp CSV olarak kaydedin. Dosyayı Not Defteri gibi bir metin düzenleyicide açın ve şu üç soruyu cevaplayın:
- Sütun ayıracı
,mı;mi? - Ondalık sayılar
12.5mi12,5mi? - Python’da
utf-8ile okuyunca ilk sütun adı tuhaf görünüyorsautf-8-sigile düzeliyor mu?
Bu incelemede CSV’nin bir Excel dosyası değil, düz metin olduğunu göreceksiniz. Bir CSV’yi doğru okumak için ayracını, kodlamasını ve sayıların nasıl yazıldığını bilmeniz gerekir.
Tek sayfa özet
with open(...)dosyayı açar ve blok bitince kendiliğinden kapatır."r"okur,"w"eski içeriği silip baştan yazar,"a"dosyanın sonuna ekler.read()tek bir string,readlines()string listesi döndürür; doğrudan dosya üzerindeforsatır satır dolaşır.encoding="utf-8"metnin doğru çözülmesine yardım eder; başında BOM olan UTF-8 CSV içinutf-8-siggerekebilir.- CSV dosyasını
newline=""ile açmak kalıbın parçasıdır. Satır sonlarını böylececsvmodülü işler. - CSV sütun ayıracı her zaman virgül değildir;
delimiter=";"gibi açıkça belirtilebilir. - Türkçe bölgesel ayarlı tablo programlarından gelen CSV’lerde noktalı virgül ve ondalık virgül sık görülür.
12,5gibi basit bir değerireplace()ile12.5yapabilirsiniz; binlik ayracı olan ya da başka bölgesel biçimde yazılmış sayılarda bu yöntem işe yaramaz.csv.DictReadersütun adlarını anahtar yapar; bütün alanlar string olarak gelir.- Okuma, tür dönüştürme ve hesaplamayı ayrı adımlarda yaparsanız kodu okumak ve hatayı bulmak kolaylaşır.
Bir sonraki bölümde istisnaları, modülleri ve sistematik hata ayıklamayı göreceğiz. Bu bölümdeki dosya ve dönüşüm hatalarını orada yakalamayı öğreneceğiz.
Bu bölümün kazanımları
Bu bölümü bitiren öğrenci:
with open(...)ile dosya açar ve"r","w","a"kiplerini ayırt eder.read(),readlines()ve dosya üzerindefordavranışlarını ayırt eder.- Kodlama seçeneğinin metni nasıl etkilediğini bilir ve BOM sorununu tanır.
- CSV’de sütun ayıracını
delimiterile belirler. csv.DictReaderile okunan alanların metin olduğunu bilir ve onları dönüştürür.- Ondalık virgüllü sayıyı dönüştürür ve
replace()çözümünün sınırını bilir. - Okuma, dönüştürme ve hesaplama adımlarını birbirinden ayırır.