Giriş
Python, geliştiricilere yüksek seviyeli ve okunabilir bir dil sunarken arka planda sofistike bir bellek yönetim modeli çalıştırır. Bir değişkene değer atadığınızda aslında bellekte bir nesne oluşturulur ve değişken o nesneye bir referans taşır. Bu modelin doğru anlaşılması performans sorunlarını önlemek, beklenmedik bug'ları yakalamak ve büyük ölçekli uygulamalarda kaynak kullanımını optimize etmek için kritik öneme sahiptir.
Bu yazıda Python'un temel ve bileşik veri tiplerini, mutable ile immutable ayrımını, referans semantiğini, kopyalama stratejilerini ve CPython'un garbage collector mekanizmasını detaylı olarak inceleyeceğiz. Özellikle list ve dict gibi sık kullanılan yapılarda yapılan küçük hatalar, üretim ortamında zor tespit edilen davranışlara yol açabilir.
Temel Veri Tipleri
Python'da her şey bir nesnedir. int, float, bool, str, list, tuple, dict ve set gibi yerleşik tipler, farklı bellek düzenleri ve davranışlarla gelir. int ve str immutable'dır; yani bir nesne oluşturulduktan sonra içeriği değiştirilemez. list ve dict ise mutable'dır ve yerinde değişiklik yapılabilir.
Sayısal tipler (int, float, complex) değer nesneleri olarak saklanırken, koleksiyon tipleri iç referanslar tutar. tuple immutable olsa bile içindeki mutable nesnelere referans verebilir; bu nedenle tuple'ın kendisi değişmez ama içindeki list değişebilir. Bu ayrım, veri modelini tasarlarken gözden kaçan bir tuzaktır.
- int, float, bool, complex: sayısal ve mantıksal tipler
- str, bytes, bytearray: metin ve ikili veri
- list, tuple: sıralı koleksiyonlar
- dict, set, frozenset: eşleme ve benzersiz koleksiyonlar
Mutability ve Immutable Davranış
Immutable bir nesneye 'değişiklik' yaptığınızda aslında yeni bir nesne oluşturulur. Örneğin x = 'merhaba' atamasından sonra x += ' dünya' ifadesi orijinal string'i değiştirmez; yeni bir string nesnesi üretir ve x referansı bu yeni nesneye yönlendirilir. Bu nedenle varsayılan argüman olarak mutable list kullanmak klasik bir Python anti-pattern'idir.
frozenset ve tuple gibi immutable koleksiyonlar, sözlük anahtarı veya set elemanı olarak kullanılabilir; list ve dict bu rolü üstlenemez. Tasarım aşamasında hangi verinin değişebilir olması gerektiğini netleştirmek, hem API sözleşmesini hem de bellek profilini doğrudan etkiler.
def append_item(item, bucket=[]):
bucket.append(item)
return bucket
# Her çağrıda aynı list paylaşılır!
print(append_item(1)) # [1]
print(append_item(2)) # [1, 2]Varsayılan argüman olarak mutable nesne kullanmayın. None ile başlayıp fonksiyon içinde oluşturun.
Referans Semantiği ve Kimlik
is operatörü iki değişkenin aynı nesneye işaret edip etmediğini kontrol eder; == operatörü ise değer eşitliğini test eder. Küçük tamsayılar (-5 ile 256 arası) CPython'da intern edilebilir; bu yüzden a is b True dönebilir. Büyük nesnelerde ise her zaman kimlik kontrolü yapmak gerekir.
Fonksiyonlara nesne geçirirken Python call-by-sharing modelini kullanır: referans kopyalanır, nesne kopyalanmaz. Bu yüzden mutable bir argümanı fonksiyon içinde değiştirmek, çağıran taraftaki değişkeni de etkiler. Yan etkileri kontrol altında tutmak için ya kopya döndürmek ya da immutability tercih etmek gerekir.
a = [1, 2, 3]
b = a
b.append(4)
print(a) # [1, 2, 3, 4] - a da değişti
c = a.copy()
c.append(5)
print(a) # [1, 2, 3, 4] - c bağımsızKopyalama Stratejileri
Yüzeysel kopya (shallow copy) yalnızca üst seviye konteyneri kopyalar; iç içe mutable nesneler paylaşılmaya devam eder. Derin kopya (deep copy) ise tüm alt nesneleri recursive olarak çoğaltır. copy modülü ve copy.deepcopy() bu iş için standart araçlardır.
Performans açısından gereksiz deepcopy maliyetlidir. Veri yapınızın ne kadar iç içe olduğunu biliyorsanız, yalnızca gerekli seviyede kopyalama yapın. Pydantic ve dataclasses gibi modern araçlar, model_copy(deep=True) veya replace() ile kontrollü güncelleme sunar.
- list.copy() veya slice [:] ile shallow copy
- copy.deepcopy() ile tam bağımsız kopya
- dataclasses.replace() ile immutable veri yapılarında güncelleme
- Pydantic modellerinde model_copy(deep=True)
Garbage Collector Mekanizması
CPython iki aşamalı bir bellek yönetimi kullanır: referans sayacı (reference counting) ve generational garbage collector. Bir nesnenin referans sayısı sıfıra düştüğünde bellek hemen geri kazanılır. Ancak döngüsel referanslar (A -> B -> A) yalnızca referans sayacı ile temizlenemez; bu durumda generational GC devreye girer.
GC üç nesil üzerinde çalışır: generation 0 (yeni nesneler), generation 1 ve generation 2 (uzun ömürlü nesneler). gc modülü ile manuel tetikleme, istatistik okuma ve debug işlemleri yapılabilir. Üretim kodunda genelde GC'yi kapatmak gerekmez; ancak düşük gecikme gerektiren iş yüklerinde gc.disable() geçici olarak kullanılabilir.
import gc
print(gc.get_count())
collected = gc.collect()
print(f'Toplanan nesne: {collected}')Bellek Profilleme ve Optimizasyon
sys.getsizeof() bir nesnenin yaklaşık bellek boyutunu verir ancak iç nesneleri tam olarak hesaba katmaz. tracemalloc modülü bellek tahsislerini izlemek için daha güvenilirdir. Büyük veri setlerinde __slots__ kullanarak sınıf başına bellek tüketimini önemli ölçüde azaltabilirsiniz.
Generator ve iterator kullanımı, tüm koleksiyonu belleğe yüklemek yerine lazy evaluation sağlar. Homojen sayısal veriler için array veya NumPy tercih etmek, Python list'ine kıyasla hem bellek hem CPU açısından kazanç sağlar. Ölçmeden optimize etmeyin: tracemalloc ve memory_profiler ile önce darboğazı bulun.
- tracemalloc ile bellek sızıntısı tespiti
- __slots__ ile instance bellek tasarrufu
- Generator kullanarak lazy evaluation
- array ve numpy ile homojen veri depolama
Sonuç
Python'un bellek modeli güçlü ve esnek olsa da, referans semantiği ve mutability kurallarını bilmek olmazsa olmazdır. Veri yapısını seçerken iş yükünüzün okuma/yazma profiline göre karar verin; gerektiğinde profiling araçlarıyla ölçün ve optimize edin.
Bu temeller, ileride asyncio, multiprocessing ve büyük veri işleme konularında sağlam bir zemin oluşturur. Mutable varsayılan argüman, shallow copy tuzakları ve kimlik ile eşitlik farkı gibi klasik hataları erken fark etmek, kod tabanınızın bakım maliyetini düşürür.
- Mutable varsayılan argüman kullanmaktan kaçının
- is ve == operatörlerini karıştırmayın
- İç içe yapılarda shallow copy yeterli sanmayın