Python'ın yerleşik dosya işleme özelliği, dilin gerçek güçlü yanlarından biridir — temel okuma/yazma işlemleri için
herhangi bir içe aktarma gerekmez ve API bir öğleden sonraya öğrenebilecek kadar temizdir. Ancak tutorial sürümü ile
gerçekte göndereceğiniz şey arasında gerçek bir boşluk vardır. Tutorial sürümü bir dosya açar, okur
ve kapatır. Üretim sürümü, verileri sessizce bozan kodlama uyumsuzluklarıyla, macOS'ta çalışan ama
Windows'ta çöken yollarla ve 2 GB'lık bir dosyada
read()
çağırırsanız tüm belleğinizi sessizce yiyen log dosyalarıyla başa çıkar. Bu makale dayanıklı kalıpları kapsar —
sadece mutlu yolu değil.
with İfadesi — Her Zaman Kullanın
Python'daki her dosya işleme örneği bir
bağlam yöneticisi
kullanmalıdır — okuma sırasında bir istisna oluşsa bile dosyanın kapatılmasını sağlayan with bloğu.
Bir bağlam yöneticisi, bir with bloğuna giriş ve çıkışta ne olacağını tanımlayan bir nesnedir;
dosyalar için, çıkış close()'un otomatik olarak çağrılması anlamına gelir. Pratikte neden önemli olduğu:
# ❌ Manual close — works until it doesn't
f = open('app.log')
data = f.read() # if this raises an exception...
f.close() # ...this line never runs. File handle leaks.
# ✅ Context manager — close() is guaranteed
with open('app.log') as f:
data = f.read()
# file is closed here, no matter what happened inside the blockUzun süreli çalışan sunucularda bu akademik değildir — dosya tanıtıcılarını sızdırmak sonunda
OSError: [Errno 24] Too many open files hatasına neden olur. with ifadesi hiçbir şeye mal olmaz
ve bu hata sınıfını tamamen önler. Her yerde kullanın.
Dosya Okuma — Dört Yol, Her Seferinde Bir Doğru Araç
Python size bir dosya nesnesi üzerinde çeşitli yöntemler sunar ve doğru olanı seçmek, çoğu tutorialın kabul ettiğinden daha fazla önem taşır:
f.read()— tüm dosyayı tek bir dizeye okur. Küçük yapılandırma dosyaları için uygundur, büyükler için tehlikelidir.f.readline()— iç işaretçiyi ilerletirken bir satırı bir seferde okur. İterasyon üzerinde manuel kontrol gerektiğinde kullanışlıdır.f.readlines()— tüm satırları bir listeye okur. Kullanışlıdır, ancak yine de tüm dosyayı belleğe yükler.for line in f:— yineleyici protokolü. Tam dosyayı yüklemeden bir seferde bir satır okur. Varsayılan olarak bu tercih edilmeli.
İşte gerçekçi bir örnek: .env tarzı bir yapılandırma dosyasını okuyup
bir sözlüğe dönüştürme. Bu gerçekten yazdığınız türden bir şeydir, uydurma bir "hello.txt oku" demosu değil:
from pathlib import Path
def load_config(path: str) -> dict:
"""Read a key=value config file, ignoring comments and blank lines."""
config = {}
with open(path, encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
if '=' not in line:
continue
key, _, value = line.partition('=')
config[key.strip()] = value.strip()
return config
# Usage
settings = load_config('config/app.conf')
db_host = settings.get('DB_HOST', 'localhost').strip() alışkanlığı: Satırları okurken, son satır hariç her satır
sondaki bir \n içerir (ve Windows'ta, \r\n). Her ikisini de kaldırmak için
line.strip() çağırın. Yalnızca yeni satırı kaldırmak ve baştaki boşluğu değil istiyorsanız,
bunun yerine line.rstrip('\n') kullanın.Yazma ve Ekleme — Hangi Modun Verileri Yok Ettiğini Bilin
open()'ın
ikinci argümanı moddur. İki mod insanları tekrar tekrar zorluyor:
'w'— yazma modu. Dosyayı yazmak için açar. Dosya zaten varsa, tek bir karakter yazmadan önce hemen sıfır bayta kısaltılır. Yanlış yolu açarsanız bu sessiz veri yok etmesidir.'a'— ekleme modu. Dosyayı açar ve yazma işaretçisini sona taşır. Mevcut içerik asla dokunulmaz. Yeni yazmalar zaten olanın arkasına gider.
Ekleme modu için iyi bir kullanım senaryosu, zaman damgalı yapılandırılmış bir log dosyası yazmaktır. İşte hem betiklerde hem de küçük servislerde kullanışlı olan bir kalıp:
import datetime
LOG_FILE = 'logs/pipeline.log'
def log_event(level: str, message: str) -> None:
timestamp = datetime.datetime.utcnow().isoformat() + 'Z'
line = f"[{timestamp}] {level.upper()}: {message}\n"
with open(LOG_FILE, 'a', encoding='utf-8') as f:
f.write(line)
log_event('info', 'Pipeline started')
log_event('warning', 'Retrying connection to database')
log_event('error', 'Failed to parse row 4821 — skipping')open(path, 'w') dosyayı yoksa oluşturur —
bu kullanışlıdır — ama varsa sessizce yok eder. Yanlış yazılan bir yol,
herhangi bir hata mesajı olmadan bir üretim dosyasını silebilir. Dosyanın üzerine yazılıp yazılmaması gerektiğinden emin değilseniz,
önce Path(path).exists() ile kontrol edin veya üzerine yazmak yerine FileExistsError fırlatan
'x' modunu kullanın.Kodlama — Herkesi Eninde Sonunda Isıran Hata
Bu, Python dosya işlemede sessiz veri bozulmasının en yaygın kaynağıdır.
Python 3'ün bir tane belirtmeden open() çağırdığınızda varsayılan kodlaması,
locale.getpreferredencoding() tarafından belirlenir — Windows'ta bu genellikle cp1252'dir
ve Linux/macOS'ta genellikle UTF-8'dir. Bu, Mac'inizde mükemmel çalışan kodun,
dosya ASCII dışında herhangi bir karakter içerdiğinde bir Windows sunucusunda sessizce bozulabileceği veya çökebileceği anlamına gelir.
Düzeltme tek bir ek argümandır:
# ❌ Platform-dependent — works on Linux, corrupts on Windows
with open('customers.csv') as f:
data = f.read()
# ✅ Explicit UTF-8 — same behavior on every platform
with open('customers.csv', encoding='utf-8') as f:
data = f.read()
# For files exported from Excel on Windows — may have a BOM (byte order mark)
# utf-8-sig strips the BOM automatically on read
with open('export.csv', encoding='utf-8-sig') as f:
data = f.read()BOM sorunu, Microsoft Excel'den dışa aktarılan CSV dosyalarında özellikle yaygındır — dosya
yanlış kodlamayla okunursa  olarak görünen gizli bir \ufeff karakteriyle başlar
veya ilk sütun başlığının name yerine name gibi görünmesine neden olur.
encoding='utf-8-sig' kullanmak bunu şeffaf bir şekilde ele alır. Kodlama adlarının tam listesi için
Python codecs belgelerine
bakın.
open() çağrısına her zaman encoding='utf-8' (veya
Excel dışa aktarmaları için 'utf-8-sig') geçirin. Alışkanlık haline getirin — hiçbir maliyeti yoktur ve
ortama özgü tüm hata kategorisini ortadan kaldırır.Yollarla Çalışma — pathlib Kullanın
Python'da dosya yolları oluşturmanın eski yolu, dize birleştirme veya
os.path.join()'di. Modern yol,
pathlib.Path'tir,
Python 3.4'ten beri kullanılabilir ve 3.6'dan itibaren tamamen olgunlaşmıştır. Düşünmenize gerek kalmadan
Windows ve Unix'te yol ayırıcılarını doğru şekilde yönetir ve bir avuç
os.path çağrısını okunabilir öznitelik erişimiyle değiştirir.
from pathlib import Path
# Build a path relative to the current script — works on Windows and Unix
base_dir = Path(__file__).parent
data_dir = base_dir / 'data'
input_file = data_dir / 'records.csv'
# Check existence before opening
if not input_file.exists():
raise FileNotFoundError(f"Input file not found: {input_file}")
# Create a directory (including parents) without error if it already exists
output_dir = base_dir / 'output' / 'reports'
output_dir.mkdir(parents=True, exist_ok=True)
# Iterate over all JSON files in a directory
for json_file in data_dir.glob('*.json'):
print(json_file.name) # just the filename: 'records.json'
print(json_file.stem) # filename without extension: 'records'
print(json_file.suffix) # extension: '.json'
print(json_file.parent) # parent directory as a Path
# The / operator builds paths — no os.path.join needed
report_path = output_dir / f"report_{input_file.stem}.txt"/ operatörü burada bölme değildir — Path bunu yol birleştirme anlamında geçersiz kılar.
Bu doğal okunur ve dizeye dayalı yol oluşturmayla gelen alıntılama ve ayırıcı sorunlarını ortadan kaldırır.
Bir kullanışlı yöntem daha: path.read_text(encoding='utf-8'), dosyanın içeriğini yalnızca
dize olarak istediğinizde açma/okuma/kapatma kalıbı için bir kısayoldur.
Büyük Dosyaları Belleği Patlatmadan Okuma
Bir dosya küçük olduğunda — diyelim ki birkaç megabaytın altında — f.read() veya
f.readlines() uygundur. 500 MB'lık bir sunucu günlüğü veya çok gigabaytlık bir veri dışa aktarımı
olduğunda, tüm şeyi belleğe yüklemek bir MemoryError'a veya işletim sisteminden bir işlem sonlandırmasına
giden hızlı bir yoldur. Düzeltme satır satır iterasyondur:
from pathlib import Path
from collections import Counter
def count_error_levels(log_path: str) -> dict:
"""
Process a large log file line by line.
Memory usage stays roughly constant regardless of file size.
"""
counts = Counter()
with open(log_path, encoding='utf-8') as f:
for line in f:
# Each line is fetched from disk as needed — not loaded all at once
if ' ERROR ' in line:
counts['error'] += 1
elif ' WARN ' in line:
counts['warning'] += 1
elif ' INFO ' in line:
counts['info'] += 1
return dict(counts)
results = count_error_levels('/var/log/app/server.log')
print(f"Errors: {results.get('error', 0)}, Warnings: {results.get('warning', 0)}")for line in f: kalıbı çalışır çünkü Python'ın dosya nesnesi yineleyici protokolünü
uygular — dahili bir arabellek kullanarak satırları diskten birer birer getirir, bu nedenle
bellek kullanımı dosya boyutundan bağımsız olarak özünde sabit kalır. Satır satır iterasyonun bile yeterince hızlı
olmadığı gerçekten büyük dosyalar için (onlarca gigabayt),
mmap
dosyayı okumadan belleğe eşlemenizi ve düzenli ifadelerle aramanızı sağlar —
ancak çoğu kullanım senaryosu için satır yineleyicisi ihtiyacınız olan tek şeydir.
JSON ve CSV Okuma ve Yazma
Gerçek Python çalışmasında iki format sürekli ortaya çıkar ve her ikisinin de alıntılama, kaçış ve yapıyı doğru şekilde işleyen özel stdlib modülleri vardır — bunları dize bölmeleriyle ayrıştırmayın.
import json
import csv
from pathlib import Path
# --- JSON ---
# Reading
with open('config/settings.json', encoding='utf-8') as f:
settings = json.load(f) # parsed directly from the file object
# Writing (indent=2 gives readable output)
with open('output/results.json', 'w', encoding='utf-8') as f:
json.dump(results, f, indent=2, ensure_ascii=False)
# --- CSV ---
# Reading
with open('data/customers.csv', encoding='utf-8-sig', newline='') as f:
reader = csv.DictReader(f) # each row is a dict keyed by header
for row in reader:
process_customer(row['email'], row['plan'])
# Writing
fieldnames = ['id', 'email', 'plan', 'created_at']
with open('output/export.csv', 'w', encoding='utf-8', newline='') as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
for record in records:
writer.writerow(record)Dikkat edilmesi gereken birkaç şey: CSV dosyalarını açarken newline='' geçirin —
csv modülü
kendi satır sonlarını yönetir ve Python'ın evrensel yeni satır modunun müdahale etmesine izin vermek,
Windows'ta yinelenen boş satırlara neden olur.
JSON için,
ensure_ascii=False, ASCII olmayan karakterlerin (aksanlı harfler, CJK karakterleri,
vb.) \uXXXX dizilerine kaçırılmak yerine olduğu gibi yazılmasını sağlar — çok daha
okunabilir çıktı. JSON veya CSV verileriyle çalışıyorsanız ve bunları görsel olarak incelemek veya dönüştürmek istiyorsanız,
bu sitedeki JSON Biçimlendirici ve
CSV Biçimlendirici, kod yaklaşımına iyi tamamlayıcılardır.
Hata İşleme — Göreceğiniz Üç İstisna
Dosya işlemleri öngörülebilir şekillerde başarısız olur. Her durumu açıkça işlemek, genel bir traceback yerine gerçekten kullanışlı hata mesajları sağlar:
import json
from pathlib import Path
def load_json_config(path: str) -> dict:
"""
Load a JSON config file with explicit error handling.
Returns the parsed config or raises with a clear message.
"""
config_path = Path(path)
try:
with open(config_path, encoding='utf-8') as f:
return json.load(f)
except FileNotFoundError:
raise FileNotFoundError(
f"Config file not found: {config_path.resolve()}\n"
f"Create it or set CONFIG_PATH to the correct location."
)
except PermissionError:
raise PermissionError(
f"No read permission on {config_path.resolve()}\n"
f"Check file ownership and mode (chmod 644 on Linux)."
)
except UnicodeDecodeError as e:
raise ValueError(
f"Encoding error reading {config_path}: {e}\n"
f"Try opening with encoding='utf-8-sig' if the file came from Windows."
)
except json.JSONDecodeError as e:
raise ValueError(
f"Invalid JSON in {config_path} at line {e.lineno}, col {e.colno}: {e.msg}"
)Dört istisna neredeyse her gerçek başarısızlık modunu kapsar: dosya mevcut değil,
izniniz yok, kodlama yanlış veya içerik hatalı biçimlendirilmiş. Her mesaj
bir sonraki geliştiriciye (veya saat 02:00'de size) tam olarak neyin yanlış gittiğini ve nereye bakacağını söyler. Çıplak bir
Exception yakalamak ve "bir şeyler ters gitti" yazdırmak kullanışlı hata işleme değildir —
sadece karışıklığı aşağı taşır.
FileNotFoundError'ı yakalamak
ve None veya varsayılan bir sözlük döndürmek uygundur. Fonksiyon başına bir davranış seçin
ve belgeleyin.Sonuç
Yukarıdakilerin kısa versiyonu: her zaman with bloklarını kullanın, her zaman
encoding='utf-8' geçirin, yol oluşturma için pathlib.Path kullanın ve
boyut bilinmediğinde tüm dosyaları okumak yerine satırlar üzerinde yineleme yapın. Bu dört alışkanlık,
dosya işleme hatalarının büyük çoğunluğunu üretime ulaşmadan önce ortadan kaldırır.
Daha derin okuma için: Dosya okuma ve yazma hakkındaki Python tutorial bölümü temelleri kapsamlı şekilde ele alır. pathlib belgeleri yer imi eklemeye değer — stdlib'in en kullanışlı parçalarından biridir ve Python geliştiricilerinin çoğu bunu yeterince kullanmaz. csv modülü belgeleri ve json modülü belgeleri her ikisi de bu formatlarla düzenli olarak çalışıyorsanız okumaya değer uç durumlar için iyi örneklere (özel sınırlayıcılar, akışlı JSON, vb.) sahiptir.