Verileri kalıcı olarak saklamak, okumak ve yazmak
Önceki bölümlerde notları listelerde ve sözlüklerde tuttuk. Bu yapılar programın belleğinde yaşar. Program bittiğinde bellek boşaltılır ve notlar da onunla birlikte kaybolur. Ertesi gün aynı programı açtığımızda her şeye baştan girmemiz gerekir.
Verinin program bittikten sonra da durmasını istiyorsak onu dosyaya yazarız. Dosya diskte durur; program kapansa da bilgisayar yeniden açılsa da içeriği yerinde kalır. Bir sonraki çalıştırmada programımız aynı dosyayı açıp verileri geri okuyabilir.
Bu bölümde dosya açmayı, okumayı ve yazmayı öğrenecek; sonunda notları bir dosyadan okuyup hesapladığımız sonucu başka bir dosyaya yazan küçük bir program kuracağız.
w, a ve x kiplerinin sonuçlarını karşılaştıracağız.with bloğunun dosyayı neden kendiliğinden kapattığını göreceğiz.pathlib ile dosya yollarını kuracak ve klasör içindeki dosyaları listeleyeceğiz.Bir dosyayla çalışmak üç adımdır: dosyayı açarız, içinde okuma ya da yazma yaparız, işimiz bitince kapatırız. Açma işini open() yapar.
open() Bir Dosya Nesnesi Veriropen() dosyanın içeriğini değil, o dosyayla çalışmamızı sağlayan bir dosya nesnesi döndürür. Okuma ve yazma işlemlerini bu nesnenin metotlarıyla yaparız.
open("notlar.txt","r",encoding="utf-8")
Kip yazılmazsa dosya okumak için ("r") açılır. Kodlamayı encoding="utf-8" diye açıkça yazacağız; nedenini bu bölümde ilerleyince göreceğiz.
Okuyacağımız bir dosya gerekiyor. Aşağıdaki hücre notlar.txt adında bir dosya oluşturur ve içine her satırda bir öğrencinin adını ve puanını yazar. "w" kipi dosyayı yazmak için açar; kiplerin ayrıntısına birazdan döneceğiz.
Bu sayfadaki kod hücreleri tarayıcının içindeki sanal bir dosya sistemini kullanır. Hücrelerde oluşturduğumuz dosyalar bilgisayarınızın diskine yazılmaz.
read()read() dosyanın tamamını tek bir string olarak döndürür. Hücreyi çalıştırmadan önce çıktının nasıl görüneceğini düşünün.
repr() metnin gerçek biçimini yazar: 'Ayşe,80\nCan,65\n'. Her satırın sonunda görünmeyen bir satır sonu karakteri (\n) vardır.
Dosya nesnesi, nereye kadar okuduğumuzu bir imleçle hatırlar. Her okuma imleçten başlar ve imleci okuduğu yerin sonuna taşır. Mavi kutular o çağrıda okunan karakterlerdir, kırmızı çizgi imlecin yeridir.
tell() → 0tell() → 5tell() → 9tell() → 16read(4) sonrası tell() neden 5? Çünkü tell() karakteri değil bayt sayar ve ş iki bayttır. Kodlama başlığında ayrıntısına bakacağız.
readline()readline() imleçten başlayıp satır sonuna kadar olan bölümü döndürür. Her çağrı bir sonraki satıra geçer. Dosya bittikten sonra boş string döner. Hücrenin üç çıktısı ne olur?
'Ayşe,80\n', 'Can,65\n' ve ''. Üçüncü çağrıda satır kalmadığı için boş string döner; hata oluşmaz.
readlines()readlines() her satırı bir eleman yapan bir liste döndürür. Satır sonu karakterleri elemanların içinde kalır.
forDosya nesnesi doğrudan for ile dolaşılabilir. Döngü değişkeni her turda bir satır alır. Dosyanın tamamını belleğe bir liste olarak almaz; bu yüzden büyük dosyalarda da güvenle kullanılır.
strip() satırın başındaki ve sonundaki boşlukları, satır sonu karakteri dâhil, siler. print() zaten kendi satır sonunu eklediği için strip() kullanmazsak çıktıda her satırdan sonra bir boş satır görürüz.
| Yöntem | Döndürdüğü | Ne zaman? |
|---|---|---|
read() |
Tüm içerik, tek string | Küçük bir dosyanın tamamı tek parça lazımsa |
read(n) |
En fazla n karakter |
Dosyayı parça parça okumak gerekirse |
readline() |
Tek satır, string | Satırları elle, birer birer almak istiyorsak |
readlines() |
Tüm satırlar, liste | Satırlar üzerinde indeksle çalışacaksak |
for satir in dosya |
Her turda bir satır | Satır satır işlemlerin çoğunda; ilk tercihimiz |
Dosyadan gelen her şey metindir. "Ayşe,80" satırından ad ve puanı almak için satırı temizler, virgülden böler, puanı sayıya çeviririz.
split(",") iki parçalı bir liste verir ve ad, puan ile açılır. int(puan) olmadan toplam += puan satırı sayıya metin eklemeye çalışır ve TypeError verirdi.
Yazma, okumanın tersidir: dosyayı yazma kipinde açar ve dosya nesnesinin write() metodunu çağırırız.
write() Satır Sonunu Kendisi Eklemezwrite() verdiğimiz metni olduğu gibi yazar. Satırı bitirmek istiyorsak \n karakterini biz ekleriz. Aşağıdaki hücrede dosyaya ne yazılır?
Birinciİkinci aynı satırda görünür. İlk write() çağrısında \n yoktu; satırı biz bitirmedikçe yazı aynı satırdan devam eder.
Satırları bir listede tutuyorsak writelines() hepsini sırayla yazar. Bu metot da satır sonu eklemez; her string’in sonuna \n koymak bize kalır. Bir diğer yol, dosyaya yazdırmak için print() işlevinin file parametresini kullanmaktır: print() satır sonunu kendisi ekler.
write() yalnızca string kabul eder. Sayı yazmak istiyorsak f-string ya da str() ile önce metne çeviririz. Aşağıdaki hücre bilerek hata veriyor; mesajı okuyun.
Hata mesajı write() argümanının string olması gerektiğini, int verildiğini söyler. Düzeltmek için dosya.write(f"{puan}\n") yazarız.
open() çağrısındaki ikinci argüman dosyanın hangi amaçla açılacağını belirler. Aynı dosya üzerinde kipe göre çok farklı sonuçlar oluşur.
r, w, a ve x| Kip | Amaç | Dosya yoksa | Dosya varsa |
|---|---|---|---|
"r" |
Okumak (varsayılan) | FileNotFoundError | İçerik korunur, imleç başta |
"w" |
Yazmak | Oluşturur | İçeriği siler |
"a" |
Sona eklemek | Oluşturur | İçerik korunur, yazı sona eklenir |
"x" |
Yeni dosya oluşturmak | Oluşturur | FileExistsError |
w Eski İçeriği Siler"w" ile açılan dosya, içinde ne olduğuna bakılmadan hemen sıfırlanır. Yazma işlemi yapmasak bile açılış anında eski içerik gider. Bir dosyaya mevcut içeriği koruyarak eklemek istiyorsak "a" kullanırız.
Dikkat
Bir dosyayı "w" ile açmadan önce kendinize şunu sorun: eski içerik kalmalı mı? Cevap evetse "a" kullanın. Yanlış kip seçmenin bedeli dosyanın kaybolmasıdır.
Hücredeki dört with bloğu aynı dosyayı sırayla "w", "a", "w" ve "r" kipiyle açıyor. Son print dosyada hangi satırları gösterir?
Yalnızca Üçüncü satır kalır. Üçüncü blok dosyayı yeniden "w" ile açtığı için önceki iki satır silindi.
a ile Ekleme"a" kipi, her çalıştırmada kayıt eklemek istediğimiz günlük dosyaları için uygundur. Hücreyi birkaç kez çalıştırın; satırlar silinmez, birikir.
x: Var Olan Dosyayı Korumak"x" kipi yalnızca yeni bir dosya oluşturur. Dosya zaten varsa hiçbir şeyi bozmadan FileExistsError verir. Önemli bir dosyanın yanlışlıkla ezilmesini önlemek için kullanışlıdır.
+Kipe + eklersek dosya hem okunabilir hem yazılabilir olur. Üç + kipinin açılışta yaptığı iş temel kiplerle aynıdır.
| Kip | Amaç | Dosya yoksa | Dosya varsa |
|---|---|---|---|
"r+" |
Oku ve yaz | FileNotFoundError | İçerik korunur, imleç başta |
"w+" |
Yaz ve oku | Oluşturur | İçeriği siler |
"a+" |
Ekle ve oku | Oluşturur | İçerik korunur, yazı hep sona eklenir |
r+ Araya Eklemez, Üzerine Yazarr+ ile imleç başta başlar. write() imlecin bulunduğu yerden itibaren var olan karakterlerin üzerine yazar; araya ekleme yapmaz. Hücrede dosya Merhaba iken başına XY yazılırsa ne olur?
XYrhaba. XY, baştaki Me karakterlerinin üzerine yazıldı. seek(0) olmasaydı read() imlecin bulunduğu yerden okuyacak ve boş dönecekti.
Kipe "t" ya da "b" ekleyebiliriz. Metin kipi ("t", varsayılan) baytları karakterlere çevirir ve string döndürür. İkili kip ("b") baytlara dokunmaz; resim, ses ve PDF gibi metin olmayan dosyalar için kullanılır. İkili kipte kodlama belirtilmez ve okunan değer bytes olur.
withAçtığımız dosya işletim sisteminin sınırlı sayıda verdiği bir kaynağı tutar. İşimiz bitince dosyayı kapatmak gerekir.
write() çağrıları verinin hemen diske gitmesini sağlamaz. Yazılan metin önce bellekte küçük bir tamponda toplanır. close() çağrıldığında tampondakiler diske aktarılır ve dosya serbest kalır. Kapatmayı unutursak veri diske hiç ulaşmayabilir, dosya başka programlar tarafından kullanılamayabilir.
write("Birinci\n")
write("İkinci\n")
Birinci
İkinci
gunluk.txt
close()Dosya nesnesinin closed özelliği dosyanın kapalı olup olmadığını gösterir. Önce açıkken, sonra close() çağrısından sonra bakalım.
open() ile close() arasında hata çıkarsa program durur ve close() satırına ulaşılmaz. Hücre hatayı try/except ile yakalıyor; bu yapıyı bir sonraki bölümde işleyeceğiz. Burada yalnızca close() satırının atlandığına bakıyoruz.
Dosya açık kaldı. Küçük bir betikte fark edilmeyebilir; uzun süre çalışan bir uygulamada açık dosyalar birikir ve sınırlı kaynaklar tükenebilir.
with Bloğu: Hata Olsa da Kapanırwith bloğu dosyanın kapatılmasını Python’a bırakır. Blok normal biçimde de bitse, içinde bir hata da çıksa dosya kapatılır.
Önceki hücreyle aynı hatayı yaşadık, ama bu kez dosya.closed değeri True. Kapatmayı hatırlamak zorunda olmadığımız için with dosya işlemlerinde standart yoldur; bu bölümün geri kalanında dosyaları hep with ile açacağız.
Metin dosyası aslında bayt dizisidir. Hangi baytın hangi karaktere karşılık geldiğini bir karakter kodlaması belirler. encoding="utf-8" yazmamızın nedeni bu eşleşmeyi açıkça seçmektir.
UTF-8’de İngilizce harfler tek bayttır; Ç, ğ, ş, ı gibi Türkçe harfler ise iki bayt yer kaplar. Çağlar yazısı altı karakter olmasına rağmen dosyada sekiz bayttır. Önceki imleç şemasında tell() değerinin karakter sayısından büyük çıkmasının nedeni de buydu.
6 karakter · 8 bayt
Bir dosya UTF-8 ile yazılıp başka bir kodlamayla okunursa baytlar farklı karakterlere çevrilir. Aşağıdaki hücre aynı sekiz baytı önce doğru, sonra Türkçe Windows’ta kullanılan cp1254 kodlamasıyla çözüyor.
Kodlamayı yazmazsak Python işletim sisteminin varsayılanını kullanabilir. Aynı program başka bir bilgisayarda farklı çalışabilir. Bu yüzden metin dosyalarında her zaman encoding="utf-8" yazıyoruz.
tell() ve seek()Dosya imleci okuma ve yazmanın nereden devam edeceğini belirler. İki metotla bu konumu hem öğrenebilir hem değiştirebiliriz.
tell(): İmlecin Yeritell() imlecin başlangıçtan itibaren bayt cinsinden konumunu döndürür. Hücrede Ayşe yazısının dört karakter olmasına rağmen read(4) sonrası değer 5’tir, çünkü ş iki bayttır.
seek(): İmleci Başa Almakread() bir kez çağrıldığında imleç dosyanın sonuna gelir. İkinci read() okunacak bir şey bulamaz ve boş string döndürür. seek(0) imleci başa taşır; böylece dosyayı yeniden okuyabiliriz.
Metin kipinde bir bayt sayısı kadar ileri ya da geri gitmek güvenli değildir; çünkü iki baytlık bir harfin ortasına düşebilirsiniz. Bu yüzden seek() metin dosyalarında yalnızca iki biçimde kullanılır: seek(0) başa, seek(0, 2) sona gider. Sondan geriye gitmeyi deneyen hücre hata verir.
İkili kipte ("rb") bu sınır yoktur: bayt konumlarıyla serbestçe çalışabilirsiniz.
Şimdiye kadar yalnızca notlar.txt gibi kısa adlar yazdık. Bir dosya başka bir klasördeyse programın onu nasıl bulacağını yolla anlatırız.
Python göreli bir yol gördüğünde dosyayı çalışma dizininde arar: programın çalıştırıldığı klasör. Şemada program.py dosyası ders/ klasöründen çalıştırılıyor. Bu durumda notlar.txt doğrudan bulunur, ogrenciler.csv için veri/ klasörünü de yazmamız gerekir.
"notlar.txt"Göreli yol: çalışma dizininden başlar"veri/ogrenciler.csv"Göreli yol: alt klasörü de yazarız"/Users/ada/ders/notlar.txt"Mutlak yol: kök dizinden başlayan tam adresMutlak yol işe yarar ama başka bilgisayara taşıdığımızda geçersiz kalır; kullanıcı adı ve klasör düzeni her makinede farklıdır. Proje içindeki dosyalar için göreli yol daha taşınabilirdir.
pathlib ile Yol KurmakWindows klasörleri \ ile, macOS ve Linux / ile ayırır. Yolu elle string olarak birleştirirsek program yalnızca bir sistemde çalışabilir. pathlib modülündeki Path, ayırıcıyı çalışılan sisteme göre seçer. Yolu / işleciyle birleştiririz.
Eski yöntem: os.path.join()
Eski kodlarda aynı iş os.path.join("veri", "notlar.csv") ile yapılır. Sonucu aynıdır, ancak Path hem yolu kurar hem de dosya adı, uzantı ve varlık denetimi gibi işleri tek nesnede toplar. Yeni yazdığımız kodda pathlib kullanıyoruz.
mkdir(exist_ok=True) klasörü oluşturur, varsa hata vermez. write_text() ve read_text() küçük dosyaları tek satırda yazar ve okur. glob("*.txt") desene uyan dosyaları bulur.
bilgi.md desene uymadığı için listede görünmez. Klasördeki her şeyi görmek için klasor.iterdir() kullanılır.
Yol yanlışsa ya da dosya hiç oluşturulmamışsa open() FileNotFoundError verir. Hata mesajı aranan yolu da yazar; ilk bakılacak yer burasıdır.
Dosyanın olmaması beklenen bir durumsa hatayı yakalayıp anlamlı bir mesaj verebiliriz. try/except yapısını bir sonraki bölümde ayrıntılı işleyeceğiz.
Öğrendiklerimizi birleştirelim: notları bir dosyadan okuyacak, ortalamayı hesaplayacak ve sonucu başka bir dosyaya yazacağız.
Ayşe,80
Can,65
Çağlar,90
strip() yaparsplit(",") ile ad ve puan alırAyşe: 80
Can: 65
Çağlar: 90
Ortalama: 78.3
Girdi dosyasında bir de boş satır bırakıldı; gerçek dosyalarda bu sık görülür. Program boş satırı atlıyor ve her satırdan ad ile puanı alıp bir sözlüğe koyuyor.
Okuma işi bitince dosya kapandı; rapor için yeni bir with bloğu açıyoruz. Bu hücre, önceki hücrenin oluşturduğu notlar sözlüğünü kullanır; önce onu çalıştırın.
notlar.txt dosyasında kaç öğrenci kaydı olduğunu bulun. Dosyayı for ile satır satır dolaşın ve her satırda sayacı artırın.
Çözüm
for satir in dosya — dosya nesnesi doğrudan dolaşılır; her turda bir satır gelir ve sayaç bir artar.len(dosya.readlines()) de aynı sonucu verir, ancak bütün satırları önce belleğe liste olarak alır. Büyük dosyalarda for daha uygundur.Bir program her çalıştığında calisma.log dosyasına bir satır eklemeli, önceki satırları silmemelidir. Boşluğa uygun kipi yazın ve hücreyi üç kez çalıştırın. Her çalıştırmada satır sayısı artmalıdır.
Çözüm
"a" — dosya yoksa oluşturur, varsa eski satırları korur ve yeni satırı sona ekler. Hücreyi her çalıştırdığınızda satır sayısı bir artar."w" yazsaydık her çalıştırmada dosya sıfırlanır ve her zaman tek satır görürdük.adlar.txt dosyasındaki isimleri alfabetik sıraya dizip sirali.txt dosyasına yazın. Her ad ayrı satırda olmalıdır.
Çözüm
sorted(...) — satırları sıralı yeni bir liste yapar; her satır \n ile bittiği için satır sonları korunur.writelines(sirali) — listeyi sırayla yazar; satır sonlarını elemanlar zaten taşıdığı için ekleme gerekmez.not.txt dosyasında kelime sayısı en yüksek olan satırı bulun ve kelime sayısıyla birlikte yazın.
Çözüm
satir.split() — argümansız çağrılınca metni boşluklara göre böler; len() kelime listesinin uzunluğunu verir.en_cok = kelime_sayisi — yeni rekor bulunduğunda sayıyı ve satırı birlikte güncelleriz..txt Dosyalarını Listeleodevler klasöründeki .txt dosyalarının adlarını alfabetik sıraya dizip liste.txt dosyasına satır satır yazın. .md dosyası listede olmamalıdır.
Çözüm
yol.name — Path nesnesinden yalnızca dosya adını alır, klasör kısmı olmadan.glob("*.txt") — * herhangi bir adla eşleşir; .md dosyası desene uymadığı için listeye girmez.Kodları çalıştırmadan önce çıktıları tahmin edin. Ardından cevabınızı ve gerekçenizi karşılaştırın; yanlış şıkların hangi kavram karışıklığından kaynaklandığına dikkat edin.
with open("notlar.txt", "r", encoding="utf-8") as dosya: yazımının, open() ve close() ile elle çalışmaya göre avantajı nedir?
odev.txt dosyasında üç satır var. Aşağıdaki kod çalıştırıldıktan sonra dosyada ne bulunur?
YeniYeniYeni ve altında önceki üç satırFileExistsError oluşurwith open("deneme.txt", "w", encoding="utf-8") as dosya:
dosya.write("Ay")
dosya.write("şe")
with open("deneme.txt", "r", encoding="utf-8") as dosya:
print(repr(dosya.read()))'Ay\nşe\n''Ay şe'['Ay', 'şe']'Ayşe'Nonewith open("notlar.txt", "r", encoding="utf-8") as dosya:
ilk = dosya.read()
ikinci = dosya.read()
print(len(ikinci))notlar.txt dosyası boş değildir. Çıktı nedir?
ilk ile aynı uzunluk0FileNotFoundError oluşur1ValueError oluşurogrenciler.txt dosyasının ilk satırı Çağlar,90\n. Aşağıdaki kodun çıktısı nedir?
23456notlar .csvveri notlar.csvnotlar.csv .csvnotlar.csv veriveri .csvBoyutu çok büyük olabilecek bir günlük dosyasındaki her satırı sırayla işlemek istiyoruz. Hangi yaklaşım en uygundur?
dosya.read() ile tamamını alıp split("\n") yapmakdosya.readlines() ile liste almak ve indeksle dolaşmakfor satir in dosya: ile satırları birer birer almak"w" kipiyle açıp okumaknotlar.txt dosyasının her satırı ad,puan biçiminde. en_yuksek(dosya_yolu) fonksiyonunu yazın: en yüksek puanı alan öğrencinin (ad, puan) demetini döndürsün. Boş satırları atlasın; puan sayı olarak karşılaştırılmalıdır.
open(yol, kip, encoding="utf-8") bir dosya nesnesi döndürür. Kip yazılmazsa dosya okumak için açılır.read() tüm içeriği, readline() bir satırı, readlines() satırların listesini verir. Satır satır işlemlerde for satir in dosya ilk tercihtir.\n ile biter. strip() bu karakteri temizler; split() satırı parçalara ayırır. Dosyadan gelen sayılar metindir ve int() ile çevrilir.write() ve writelines() satır sonu eklemez; \n bize aittir. write() yalnızca string kabul eder."r" okur, "w" yazar ve eski içeriği siler, "a" sona ekler, "x" yalnızca yeni dosya oluşturur. + okuma ve yazmayı birlikte açar.with bloğu dosyayı hata olsa bile kapatır. Tamponda bekleyen veri kapanırken diske yazılır.encoding="utf-8" yazmak aynı dosyanın her makinede aynı okunmasını sağlar.tell() bayt cinsinden konumu verir, seek(0) başa döner. Metin kipinde seek() yalnızca başa ve sona gitmek için güvenlidir.Path("veri") / "notlar.txt" yolu her sistemde doğru kurar.mkdir(exist_ok=True) klasör oluşturur, glob("*.txt") desenle dosya bulur; name, stem ve suffix yolun parçalarını verir.FileNotFoundError, "x" ile açılan var olan dosya FileExistsError verir. Hata yönetimini sonraki bölümde işleyeceğiz.with ile açar, okur ve yazar; dosyanın blok bitiminde kapandığını bilir.read(), readline(), readlines() ve for ile okumanın farkını açıklar, duruma uygun olanı seçer."r", "w", "a" ve "x" kiplerinin dosya var olmadığında ve var olduğunda ne yaptığını söyler.write() ile print() farkını ve strip() ile temizlemeyi doğru kullanır.encoding="utf-8" yazmanın gerekçesini ve karakter ile bayt farkını açıklar.tell() ve seek(0) ile imleci izler; imleç sonda iken ikinci read() neden boş döndüğünü anlar.pathlib ile yol kurar, klasör oluşturur ve desene uyan dosyaları listeler.İleri Programlama – OMÜ | 🏠