← Kursa Dön
📄 Text · 15 min

Değişkenler ve Bellek Modeli

Programlamanın en temel yapı taşlarından biri değişkenlerdir. Ama Python'daki değişkenler, belki daha önce duyduğun "kutu" analojisinden biraz farklı çalışıyor. Bu derste değişkenlerin gerçekte ne olduğunu, nasıl çalıştığını ve Python'un bu konudaki özgün yaklaşımını öğreneceksin.

Hazırsan başlayalım — çünkü bu ders, Python'u gerçekten anlamanın ilk adımı.


Değişken Nedir?

En basit tanımıyla değişken, bir değere verdiğin isimdir. Ama bu tanım tek başına yeterli değil — önemli olan Python'un bu "ismi" nasıl ele aldığı.

Kutu Analojisi Neden Yanlış?

C veya Java gibi dilleri öğrenirken değişkenleri genellikle "kutu" olarak anlatırlar. Kutu var, içine bir değer koyuyorsun. Kutu bellekte sabit bir yer kaplıyor ve içindeki değeri değiştirebiliyorsun.

Python'da durum böyle değil. Python'da değişkenler etiket gibi çalışır.

Etiket (Label) Analojisi

Bir müzedeki tabloları düşün. Tabloların yanında küçük etiketler var — "Yıldızlı Gece", "Mona Lisa" gibi. Bu etiketler tabloyu içermez, tabloya işaret eder.

Python değişkenleri de aynen böyle. Bir değişken oluşturduğunda:

  1. Python önce değeri (nesneyi) bellekte oluşturur

  2. Sonra değişken ismini bu nesneye "yapıştırır"

x = 42

Bu kodda olan şey:

  • Python bellekte 42 değerini taşıyan bir int nesnesi oluşturur

  • x ismini bu nesneye bağlar (etiketler)

Kutu analojisinde x bir kutudur ve 42 kutunun içindedir. Etiket analojisinde ise 42 zaten var olan bir nesnedir ve x ona yapıştırılmış bir etikettir.

x = 42
y = x

Bu durumda ne olur? Kutu analojisine göre 42 kopyalanıp yeni bir kutuya konur. Ama Python'da olan bu değil — y de aynı 42 nesnesine yapıştırılan ikinci bir etikettir. İki etiket, bir nesne.

x = 42
y = x
print(x is y)  # True — aynı nesne!

x = 99
print(y)  # 42 — y hâlâ eski nesneye bağlı

x = 99 dediğinde x etiketi eski nesneden koparılıp yeni 99 nesnesine yapıştırılır. y hâlâ 42'ye bağlı kalır. Etiketler bağımsızdır.

💡 İpucu: Bu etiket analojisini kafana iyi kazı. Python'un mutable/immutable davranışlarını, fonksiyon parametrelerini ve birçok "garip" davranışını bu temel anlayışla çözebilirsin.


Python'da Her Şey Nesne

Bu cümleyi çok duyacaksın: Python'da her şey bir nesnedir. Sayılar, stringler, fonksiyonlar, modüller, hatta None bile bir nesnedir.

Nesne Ne Demek?

Bir nesne, bellekte yer kaplayan ve üç temel özelliği olan bir varlıktır:

  1. Kimlik (Identity): Bellekteki benzersiz adresi. id() ile görürsün.

  2. Tip (Type): Nesnenin türü. type() ile görürsün.

  3. Değer (Value): Nesnenin taşıdığı veri.

x = "merhaba"
print(id(x))      # 140234567890 gibi bir sayı (bellek adresi)
print(type(x))    # <class 'str'>
print(x)          # merhaba (değer)

Değişkenler Birer Referans

Bir değişken oluşturduğunda aslında bir referans oluşturuyorsun. Değişken, nesnenin kendisi değil — nesneye olan referanstır.

a = [1, 2, 3]
b = a

b.append(4)

print(a)  # [1, 2, 3, 4] — a da değişti!
print(b)  # [1, 2, 3, 4]

Neden a da değişti? Çünkü a ve b aynı listeye referans veriyor. Listeyi b üzerinden değiştirdiğinde, a da aynı listeye baktığı için değişikliği görüyor.

Bu, sayılarda neden olmaz? Çünkü sayılar immutable (değiştirilemez). Ama bunun detayını birazdan göreceğiz.

a = 10
b = a

b = 20

print(a)  # 10 — a değişmedi
print(b)  # 20

Burada b = 20 demek "b etiketini 10'dan kopar, 20'ye yapıştır" demektir. a hâlâ 10'a yapışık.


id() ve is Operatörü

id() Fonksiyonu

id() fonksiyonu, bir nesnenin bellekteki benzersiz kimliğini döndürür. CPython'da bu, nesnenin bellek adresidir.

x = 42
print(id(x))  # 94567812345600 gibi bir sayı

y = 42
print(id(y))  # Aynı sayı olabilir!

z = "merhaba"
print(id(z))  # Farklı bir sayı

İlginç bir şey fark ettin mi? x ve y aynı id'ye sahip olabilir! Bunun sebebi Python'un integer caching (tam sayı önbellekleme) mekanizması.

Integer Caching (Küçük Tam Sayı Havuzu)

Python, -5 ile 256 arasındaki tam sayıları önceden oluşturup bellekte tutar. Bu sayılara her referans verdiğinde, Python yeni bir nesne oluşturmak yerine var olanı kullanır.

a = 256
b = 256
print(a is b)  # True — aynı nesne

a = 257
b = 257
print(a is b)  # False (veya True — duruma göre değişir)

⚠️ Dikkat: 257 için sonuç, Python'un çalışma ortamına göre değişebilir. İnteraktif yorumlayıcıda False alabilirsin ama bir .py dosyasında True alabilirsin — çünkü derleyici optimizasyonları devreye girer. Bu yüzden tam sayı karşılaştırmalarında asla is kullanma, == kullan.

is Operatörü

is operatörü, iki değişkenin aynı nesneye referans verip vermediğini kontrol eder. == ise değerlerin eşit olup olmadığını kontrol eder.

a = [1, 2, 3]
b = [1, 2, 3]
c = a

print(a == b)   # True — değerler aynı
print(a is b)   # False — farklı nesneler
print(a is c)   # True — aynı nesne

Bu ayrım çok önemli. == sorusu: "İçerikleri aynı mı?" is sorusu: "Bellekte aynı nesne mi?"

# Pratik bir kullanım:
x = None
print(x is None)  # True — None kontrolünde is kullan
print(x == None)   # True ama önerilmez!

None kontrolünde her zaman is kullanılır. Bunun teknik bir sebebi var — == operatörü override edilebilir ama is edilemez. Bu yüzden is None her zaman güvenilirdir.


Değişken İsimlendirme Kuralları

Python'da değişken ismi verirken uyman gereken katı kurallar ve uyulması önerilen en iyi pratikler var.

Zorunlu Kurallar

  1. Harf veya alt çizgi ile başlamalı: _, a, Z olabilir ama 1 veya @ olamaz.

  2. Harf, rakam ve alt çizgi içerebilir: sayi_1, isim, _ozel geçerli.

  3. Büyük-küçük harf duyarlı: isim, Isim ve ISIM farklı değişkenlerdir.

  4. Anahtar kelimeler kullanılamaz: if, for, class, return gibi isimler yasak.

# Geçerli isimler
isim = "Ali"
_ozel = 42
sayi_1 = 100
camelCase = "bu da geçerli"
SABIT_DEGER = 3.14

# Geçersiz isimler
# 1sayi = 10      # Rakamla başlayamaz
# benim-degisken = 5  # Tire kullanılamaz
# class = "test"  # Anahtar kelime kullanılamaz

Python Anahtar Kelimeleri

Python'un ayrılmış kelimelerini görmek için:

import keyword
print(keyword.kwlist)
# ['False', 'None', 'True', 'and', 'as', 'assert', 'async', 
#  'await', 'break', 'class', 'continue', 'def', 'del', 'elif',
#  'else', 'except', 'finally', 'for', 'from', 'global', 'if',
#  'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or',
#  'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']

Best Practices (En İyi Uygulamalar)

Python topluluğunun ortak kuralları PEP 8 adlı stil rehberinde belirtilmiştir:

# ✅ snake_case kullan (Python standardı)
kullanici_adi = "ahmet"
toplam_fiyat = 99.90
ogrenci_sayisi = 42

# ❌ camelCase kullanma (Java/JavaScript tarzı)
kullaniciAdi = "ahmet"     # Çalışır ama Pythonic değil

# ✅ Sabitler BÜYÜK HARF
MAX_BOYUT = 100
PI = 3.14159
VERİTABANI_URL = "localhost:5432"

# ✅ Anlamlı isimler ver
toplam = hesapla_toplam(fiyatlar)
aktif_kullanici = kullanici_getir(id=5)

# ❌ Anlamsız isimler
x = hesapla_toplam(f)
a = kullanici_getir(id=5)

# ✅ Boolean değişkenler is/has ile başlasın
is_active = True
has_permission = False
is_valid = True

# ✅ Özel (private) değişkenler _ ile başlasın
_dahili_sayac = 0
__cok_ozel = "dışarıdan erişilmemeli"

Kaçınılması Gereken İsimler

# ❌ Tek harfli isimler (döngü sayaçları hariç)
x = "Ali"        # Ne olduğu belli değil
n = 42            # Anlamsız

# ✅ Döngü sayaçlarında tek harf OK
for i in range(10):
    print(i)

# ❌ Built-in fonksiyonları ezme
list = [1, 2, 3]    # list() fonksiyonunu ezdik!
type = "admin"       # type() fonksiyonunu ezdik!
id = 42              # id() fonksiyonunu ezdik!

# ✅ Farklı isim seç
my_list = [1, 2, 3]
user_type = "admin"
user_id = 42

⚠️ Dikkat: Python'un built-in fonksiyonlarını (list, type, id, str, int gibi) değişken ismi olarak kullanmak en yaygın hata kaynaklarından biridir. Hata almazsın ama o fonksiyonu kullanmak istediğinde sorun yaşarsın!


Çoklu Atama

Python, birden fazla değişkene tek satırda değer atamana izin verir. Bu sadece kısa yazım değil — bazı durumlarda gerçekten kullanışlı.

Temel Çoklu Atama

# Tek satırda birden fazla değişken
a, b, c = 1, 2, 3
print(a)  # 1
print(b)  # 2
print(c)  # 3

# Bu aslında tuple unpacking
# Sağ taraf bir tuple: (1, 2, 3)
# Sol taraf da bir tuple: (a, b, c)

Aynı Değeri Birden Fazla Değişkene Atama

x = y = z = 0
print(x, y, z)  # 0 0 0

# Dikkat: Mutable nesnelerde tehlikeli!
a = b = []
a.append(1)
print(b)  # [1] — aynı listeye referans!

Aynı değeri birden fazla değişkene atarken, eğer değer mutable (değiştirilebilir) bir nesneyse dikkatli ol. Hepsi aynı nesneye referans verir.

Unpacking (Açma)

# Bir listeden değişkenlere atama
koordinatlar = [41.01, 28.97]
enlem, boylam = koordinatlar
print(f"Enlem: {enlem}, Boylam: {boylam}")

# Fonksiyon dönüş değerleriyle
def bolme_islemi(a, b):
    bolum = a // b
    kalan = a % b
    return bolum, kalan

sonuc, kalan = bolme_islemi(17, 5)
print(f"17 / 5 = {sonuc}, kalan {kalan}")
# 17 / 5 = 3, kalan 2

Yıldız (*) ile Unpacking

# İlk ve son elemanı ayır, gerisini topla
ilk, *orta, son = [1, 2, 3, 4, 5]
print(ilk)   # 1
print(orta)  # [2, 3, 4]
print(son)   # 5

# Sadece ilk elemanı al, gerisini yoksay
bas, *_ = "Python".split()
# _ yaygın olarak "umursamıyorum" anlamında kullanılır

Swap Trick: Değişken Değerlerini Takas Etme

Diğer dillerde iki değişkenin değerini takas etmek için üçüncü bir geçici değişkene ihtiyaç duyarsın:

# C tarzı swap (geçici değişkenli)
a = 10
b = 20

temp = a
a = b
b = temp

print(a, b)  # 20 10

Python'da buna gerek yok. Tek satırda halledebilirsin:

a = 10
b = 20

a, b = b, a

print(a, b)  # 20 10

Bu nasıl çalışıyor? Python önce sağ tarafı hesaplar (b, a20, 10 tuple'ı oluşur), sonra bu tuple'ı sol tarafa atar. Geçici değişkene gerek kalmaz.

# Üç değişkenle de çalışır
a, b, c = 1, 2, 3
a, b, c = c, a, b
print(a, b, c)  # 3 1 2

Bu, özellikle algoritma sorularında çok işine yarar. Sıralama algoritmalarında, linked list çevirmede, Fibonacci hesabında — her yerde.

# Fibonacci dizisi swap trick ile
a, b = 0, 1
for _ in range(10):
    print(a, end=" ")
    a, b = b, a + b
# 0 1 1 2 3 5 8 13 21 34

type() Fonksiyonu

type() fonksiyonu, bir nesnenin tipini (sınıfını) döndürür. Debugging (hata ayıklama) ve tip kontrolü için çok kullanışlı.

print(type(42))         # <class 'int'>
print(type(3.14))       # <class 'float'>
print(type("merhaba"))  # <class 'str'>
print(type(True))       # <class 'bool'>
print(type(None))       # <class 'NoneType'>
print(type([1, 2, 3]))  # <class 'list'>
print(type((1, 2)))     # <class 'tuple'>
print(type({"a": 1}))   # <class 'dict'>

type() ile Karşılaştırma

x = 42
if type(x) == int:
    print("Bu bir tam sayı")

# Ama isinstance() daha iyidir:
if isinstance(x, int):
    print("Bu bir tam sayı (veya int'in alt sınıfı)")

isinstance() neden daha iyi? Çünkü kalıtım (inheritance) ilişkisini de dikkate alır. bool, int'in alt sınıfıdır:

print(type(True) == int)        # False
print(isinstance(True, int))    # True — bool, int'in alt sınıfı

type() ile Dinamik Kontrol

def akilli_toplama(a, b):
    if isinstance(a, str) and isinstance(b, str):
        return a + " " + b
    elif isinstance(a, (int, float)) and isinstance(b, (int, float)):
        return a + b
    else:
        return str(a) + str(b)

print(akilli_toplama(3, 5))           # 8
print(akilli_toplama("Merhaba", "Dünya"))  # Merhaba Dünya
print(akilli_toplama("Skor:", 100))   # Skor:100

Mutable vs Immutable: İlk Bakış

Bu konu Python'un en önemli kavramlarından biri. Şimdilik temeline bakalım, ileride daha derinlemesine göreceğiz.

Immutable (Değiştirilemez) Nesneler

Bir nesne oluşturulduktan sonra değeri değiştirilemiyorsa, o nesne immutable'dır.

Immutable tipler:

  • int (tam sayılar)

  • float (ondalıklı sayılar)

  • str (stringler)

  • tuple (demetler)

  • bool (True/False)

  • frozenset

x = "merhaba"
# x[0] = "M"  # TypeError! String değiştirilemez.

# Peki bu ne?
x = "Merhaba"  # Bu yeni bir string nesnesi oluşturur
               # x etiketi yeni nesneye yapıştırılır
               # Eski "merhaba" nesnesi artık sahipsiz

Bir string'i "değiştirdiğini" düşündüğünde, aslında yeni bir string oluşturuyorsun ve etiketi yeni nesneye yapıştırıyorsun.

a = "hello"
print(id(a))  # 140234567890

a = a + " world"
print(id(a))  # 140234567999 — farklı id, farklı nesne!

Mutable (Değiştirilebilir) Nesneler

Bir nesne oluşturulduktan sonra değeri değiştirilebiliyorsa, o nesne mutable'dır.

Mutable tipler:

  • list (listeler)

  • dict (sözlükler)

  • set (kümeler)

liste = [1, 2, 3]
print(id(liste))  # 140234567890

liste.append(4)
print(id(liste))  # 140234567890 — aynı id, aynı nesne!
print(liste)      # [1, 2, 3, 4]

Liste değişti ama id aynı kaldı. Çünkü listenin içeriği yerinde (in-place) değiştirildi — yeni bir nesne oluşturulmadı.

Neden Önemli?

Bu ayrım, özellikle fonksiyonlara parametre geçerken ve değişken kopyalamada çok önemli hale gelir:

# Immutable — güvenli
def arttir(sayi):
    sayi += 1
    print(f"Fonksiyon içinde: {sayi}")

x = 10
arttir(x)
print(f"Fonksiyon dışında: {x}")
# Fonksiyon içinde: 11
# Fonksiyon dışında: 10 — x değişmedi!

# Mutable — dikkatli ol!
def eleman_ekle(liste):
    liste.append(99)
    print(f"Fonksiyon içinde: {liste}")

my_list = [1, 2, 3]
eleman_ekle(my_list)
print(f"Fonksiyon dışında: {my_list}")
# Fonksiyon içinde: [1, 2, 3, 99]
# Fonksiyon dışında: [1, 2, 3, 99] — my_list de değişti!

Fonksiyona bir liste geçtiğinde, fonksiyon orijinal listeyi değiştirebilir. Çünkü fonksiyona listenin kendisi değil, listeye olan referans geçilir.


Garbage Collection (Çöp Toplama)

Python, artık kullanılmayan nesneleri bellekten otomatik olarak temizler. Buna garbage collection (çöp toplama) denir.

Referans Sayacı (Reference Counting)

Python'un birincil bellek yönetim mekanizması referans sayacıdır. Her nesnenin kaç değişkenin ona referans verdiğini tutan bir sayacı vardır.

a = "merhaba"  # Referans sayacı: 1
b = a           # Referans sayacı: 2
c = a           # Referans sayacı: 3

del b           # Referans sayacı: 2
c = "başka"     # Referans sayacı: 1
a = None        # Referans sayacı: 0 → nesne silinir!

Referans sayacı 0'a düştüğünde, nesne bellekten otomatik olarak temizlenir.

del Anahtar Kelimesi

del bir değişkeni siler — daha doğrusu, değişkenin nesneye olan referansını kaldırır:

x = [1, 2, 3]
del x
# print(x)  # NameError: name 'x' is not defined

del nesneyi silmez, referansı siler. Eğer nesneye başka referanslar varsa, nesne bellekte kalmaya devam eder.

a = [1, 2, 3]
b = a
del a
print(b)  # [1, 2, 3] — nesne hâlâ var, b referans veriyor

Döngüsel Referanslar

Referans sayacının bir zayıf noktası var — döngüsel referanslar:

a = []
b = []
a.append(b)  # a, b'ye referans veriyor
b.append(a)  # b, a'ya referans veriyor
del a
del b
# Her iki nesnenin referans sayacı hâlâ 1
# Referans sayacı bunları temizleyemez!

Bu durumlar için Python'un ikincil bir garbage collector'ı var. Bu toplayıcı periyodik olarak çalışır ve döngüsel referansları tespit edip temizler.

import gc

# Garbage collector'ı elle çalıştır
gc.collect()

# İstatistikleri gör
print(gc.get_stats())

Günlük programlamada garbage collection ile uğraşman gerekmez. Python bunu otomatik halleder. Ama büyük uygulamalarda bellek yönetimini anlamak önemli olabilir.

💡 İpucu: Normal Python programlarında gc modülüyle oynamana gerek yok. Python'un otomatik bellek yönetimi çoğu durumda gayet iyi çalışır. Ama "bellekten neden yer yiyor?" diye sorduğunda bu bilgi işine yarar.


Değişken Kapsamı (Scope) — Kısa Bir Bakış

Bir değişkenin nerede tanımlandığı, nerede erişilebilir olduğunu belirler. Bu konuyu fonksiyonlar dersinde derinlemesine göreceğiz ama temel fikri şimdiden bilelim.

# Global değişken
mesaj = "Merhaba"

def selamla():
    # Fonksiyon içinden global değişkene erişilebilir
    print(mesaj)

selamla()  # Merhaba

def degistir():
    # Ama değiştiremezsin (yeni bir lokal değişken oluşur)
    mesaj = "Güle güle"
    print(mesaj)

degistir()     # Güle güle
print(mesaj)   # Merhaba — global değişmedi!

Fonksiyon içinde aynı isimle bir değişken oluşturduğunda, bu yeni bir lokal değişkendir. Global olan etkilenmez. Global değişkeni fonksiyon içinden değiştirmek istersen global anahtar kelimesini kullanman gerekir — ama bu genellikle iyi bir pratik değildir.


Değişken Türlerini Kontrol Etme

Python dinamik tipli bir dildir. Bir değişkenin tipi çalışma zamanında belirlenir ve istediğin zaman değişebilir.

x = 42          # int
x = "merhaba"   # str — aynı değişken, farklı tip
x = [1, 2, 3]   # list — yine değişti

Bu esneklik güçlü ama tehlikeli de olabilir. Python 3.5+ ile type hints (tip ipuçları) geldi:

# Tip ipuçları — zorunlu değil ama okunabilirliği artırır
isim: str = "Ali"
yas: int = 25
fiyat: float = 99.90
aktif: bool = True

Tip ipuçları Python tarafından zorlanmaz — sadece dokümantasyon ve araç desteği içindir. Ama büyük projelerde okunabilirliği dramatik şekilde artırır.

def selamla(isim: str, resmi: bool = False) -> str:
    if resmi:
        return f"Sayın {isim}, hoş geldiniz."
    return f"Selam {isim}!"

sonuc = selamla("Ali")
print(sonuc)  # Selam Ali!

Yaygın Hatalar ve Tuzaklar

1. Built-in İsimleri Ezme

# ❌ Yanlış
list = [1, 2, 3]
# Artık list() fonksiyonunu kullanamazsın!
# yeni_liste = list(range(5))  # TypeError!

# ✅ Doğru
my_list = [1, 2, 3]

2. Mutable Default Argümanlar

# ❌ Tehlikeli
def eleman_ekle(deger, liste=[]):
    liste.append(deger)
    return liste

print(eleman_ekle(1))  # [1]
print(eleman_ekle(2))  # [1, 2] — Beklenmedik!

# ✅ Güvenli
def eleman_ekle(deger, liste=None):
    if liste is None:
        liste = []
    liste.append(deger)
    return liste

3. Kopyalama Tuzağı

# ❌ Bu kopya değil, referans
orijinal = [1, 2, 3]
kopya = orijinal  # Aynı nesneye referans!

# ✅ Gerçek kopya
kopya = orijinal.copy()
# veya
kopya = orijinal[:]
# veya
kopya = list(orijinal)

4. Chained Assignment ile Mutable

# ❌ Tehlikeli
a = b = c = []  # Hepsi aynı listeye referans!
a.append(1)
print(b)  # [1] — b de değişti!

# ✅ Güvenli
a, b, c = [], [], []  # Her biri farklı liste
a.append(1)
print(b)  # [] — b etkilenmedi

Pratik Örnekler

Değişken Bilgilerini Görüntüleme

def degisken_bilgi(isim, deger):
    print(f"İsim:  {isim}")
    print(f"Değer: {deger}")
    print(f"Tip:   {type(deger).__name__}")
    print(f"ID:    {id(deger)}")
    print(f"Boyut: {deger.__sizeof__()} byte")
    print("-" * 30)

degisken_bilgi("sayi", 42)
degisken_bilgi("metin", "Python")
degisken_bilgi("liste", [1, 2, 3])
degisken_bilgi("bos", None)

Referans Takibi

import sys

x = "merhaba"
print(f"Referans sayısı: {sys.getrefcount(x)}")
# Not: getrefcount kendisi de bir referans ekler,
# bu yüzden beklediğinden 1 fazla gösterir

y = x
print(f"Referans sayısı: {sys.getrefcount(x)}")

del y
print(f"Referans sayısı: {sys.getrefcount(x)}")

Özet

  • Değişkenler etiket gibidir, kutu gibi değil. Bir değişken, bellekteki bir nesneye yapıştırılmış bir isimdir.

  • Python'da her şey nesnedir. Her nesnenin kimliği (id), tipi (type) ve değeri vardır.

  • `is` operatörü iki değişkenin aynı nesneye referans verip vermediğini, `==` ise değerlerin eşit olup olmadığını kontrol eder.

  • PEP 8 kurallarına uy: snake_case kullan, anlamlı isimler ver, built-in isimleri ezme.

  • Mutable ve immutable ayrımı Python'un en temel kavramlarından biridir — fonksiyon parametreleri, kopyalama ve atama davranışlarını doğrudan etkiler.

  • Garbage collection otomatik çalışır — referans sayacı 0'a düşen nesneler bellekten temizlenir.