id(), is, == — Identity vs Equality: Python Bellek Modelinin Final Sınavı
Modül 2'nin capstone'u. `is` ve `==` arasındaki fark — yıllar içinde gördüğüm Python interview sorularının %50'si bunun üzerinde. Bu derste: id() fonksiyonu, identity vs equality kontratı, small int caching ve string interning'in derinlemesine implementasyon detayları, weakref kavramı, ve ne zaman is — ne zaman == kararı.
Şükrü Yusuf KAYA
20 dakikalık okuma
Orta🎓 Modül 2'nin capstone — kanıtla bilgini
15 derse geldik. Variables/PEP 8/sayısal tipler/operatörler/precedence/cast — hepsi bu son dersin küçük parçalarına dokunuyordu. Şimdi 'Python bellek modeli' kapağını açıp ile arasındaki ince çizgiyi anlatıyoruz. Bu bilgiyle Modül 2 mezunu olacaksın — Modül 3 (string'ler) bizi bekliyor.
is==Önce hatırlatma — Modül 2/Ders 1'den#
Python'da değişken bir etiket, kutu değil. yapınca:
a = 5- Bellekte int nesnesi var (zaten varsa kullanılıyor — small int cache).
5 - adlı etiket o nesneye bağlanıyor.
a
İki farklı kavram:
- Identity: Aynı nesneye mi bağlı? (id'ler aynı mı?) → .
is - Equality: Değer eşit mi? → .
==
a = [1, 2, 3] b = a # b aynı nesneye bağlandı c = [1, 2, 3] # ayrı nesne, aynı içerik print(a is b) # True (aynı nesne) print(a is c) # False (farklı nesne) print(a == b) # True (eşit içerik) print(a == c) # True (eşit içerik)
Modül 2/Ders 1 etiket modelini öğrettik. Şimdi bu kavramın arkasındaki bellek mekanizmasını açıyoruz.
id() fonksiyonu — bellek adresi#
id()Python'da her nesnenin benzersiz bir id'si var. CPython'da bu id bellek adresi (RAM içindeki konumu).
x = 42 print(id(x)) # örn 4368089096 # Hex olarak (adresleri böyle görmek alışkındır) print(hex(id(x))) # '0x10500ac88' # Aynı nesneye iki etiket y = x print(id(y)) # aynı sayı # Farklı nesneye atama x = 100 print(id(x)) # farklı sayı (yeni nesne)
id()is# x is y aslında: id(x) == id(y) a = [1, 2] b = a print(a is b) # True print(id(a) == id(b)) # True (denk)
🚨 Önemli notlar:
-
id'ler her runtime'da farklı.bir runtime'da, başka runtime'da farklı sayı. Test'lerinde id kullanma.
id(x) == 4368089096 -
id'ler bir ömür için garantili sadece o nesne yaşadığı sürece. Garbage collected nesnenin id'si yeniden kullanılabilir!
import gc x = [1, 2, 3] old_id = id(x) del x gc.collect() # Şimdi farklı bir nesne aynı id'yi alabilir y = [4, 5, 6] print(id(y) == old_id) # bazen True (memory reuse)!
Bu yüzden id'leri "kalıcı identifier" olarak kullanma. Sadece o anda geçerli.
- PyPy/Jython implementasyonları farklı id formatına sahip olabilir. Standardı: id'ler unique, integer, ömür boyu garantili. Implementation detayları farklı.
is vs == — derinlemesine#
is==# Aynı nesne a = [1, 2, 3] b = a print(a is b) # True print(a == b) # True # Farklı nesne, aynı içerik c = [1, 2, 3] d = [1, 2, 3] print(c is d) # False print(c == d) # True # Aynı içerik, immutable — implementation-defined x = "hello" y = "hello" print(x is y) # True (string interning — sonra) print(x == y) # True # Aynı içerik, immutable, dynamic import sys a = sys.intern("merhaba") b = sys.intern("merhaba") print(a is b) # True (zorlanmış intern)
is ne zaman kullanılır?#
isTek doğru kullanım: sentinel kontrolleri.
# ✅ if x is None: ... if x is True: ... # nadiren — 'if x:' daha iyi if x is False: ... # nadiren # 🚫 (implementation detail'a güveniyor) if x is 5: ... if x is "hello": ...
Kural: sentinel singleton'lar (None, True, False, NotImplemented, Ellipsis) için. Diğer her durumda .
is==== ne yapıyor?#
====__eq____eq__a == ba is bclass Point: def __init__(self, x, y): self.x = x self.y = y p1 = Point(1, 2) p2 = Point(1, 2) print(p1 == p2) # False (default __eq__ — identity) print(p1 is p2) # False # Override class Point2: def __init__(self, x, y): self.x = x self.y = y def __eq__(self, other): return isinstance(other, Point2) and self.x == other.x and self.y == other.y p3 = Point2(1, 2) p4 = Point2(1, 2) print(p3 == p4) # True (custom __eq__) print(p3 is p4) # False (hâlâ farklı nesne)
__eq____hash____hash__Small int caching — derinlemesine#
CPython startup'ta -5'ten 256'ya kadar her int için bir tane nesne yaratıp önbelleğe alıyor. Bu sayede her referansta aynı nesne.
5a = 5 b = 5 print(a is b) # True (small int cache) a = 100 b = 100 print(a is b) # True a = 256 b = 256 print(a is b) # True (sınır) a = 257 b = 257 print(a is b) # False (genelde) — cache dışı
🎯 Sınır 256'da. -5 ila 256. Bu sınır implementation detail; Python dokümantasyonu garanti vermiyor.
Görselleştirme#
import sys # Small int cache'in pre-allocated olduğunu kanıtla for i in range(-5, 257): print(f"{i}: refcount={sys.getrefcount(i)}") # Çıktı: -5: refcount=66, -4: 30, ..., 256: çok yüksek # Cache dışı: print(f"257: refcount={sys.getrefcount(257)}") # düşük
Cache içi int'lerin reference count'ı yüksek — interpreter, standart kütüphane, kendi kodun her yerde kullanıyor.
5Neden böyle?#
Performans. Yaygın int'leri tekrar tekrar yaratmak yerine bir kez yaratıp paylaşıyor. Memory tasarrufu + allocation maliyeti yok.
Pratik etkisi?#
Yok — eğer ile değer karşılaştırma yapmıyorsan.
is# 🚫 Kötü if count is 5: ... # 5 cache'te ama 257 olsa False! # ✅ if count == 5: ... # her zaman doğru
Bunu asla unutma: int için hep , asla .
==isString interning — daha karmaşık#
Python tarafından kullanılan tüm "identifier-like" string'ler (değişken adları, attribute adları, modül adları) otomatik intern ediliyor — bellekte tek bir kopya.
# Değişken adları otomatik intern a = "hello" b = "hello" print(a is b) # True (genelde) # Aynı şey def foo(): x = "world" return x s = foo() t = "world" print(s is t) # True # Boşluklu string — intern olmayabilir x = "hello world" y = "hello world" print(x is y) # CPython 3.x: True (genelde) # Ama implementation detail'a güvenme # Programatik string — intern garantili değil import time a = "abc" + str(time.time())[:0] # "abc" + "" b = "abc" print(a is b) # genelde False (runtime concat'tı)
Manuel intern: sys.intern#
sys.internimport sys a = sys.intern("special_key") b = sys.intern("special_key") print(a is b) # Garantili True # Kullanım: dict key olarak yoğun aynı string'ler keys = [sys.intern(s) for s in ["status", "id", "name"] * 1000000]
Use case: aynı string'i milyonlarca kez bellekte tutmamak. Compiler'lar, JSON parser'lar, log processing — interning ile bellek dramatik azalıyor.
CPython implementasyon detayı#
# Identifier-like string'ler kompile time intern "hello".isidentifier() # True → intern "a b c".isidentifier() # False → intern olmayabilir # Length sınırı yok ama kısa string'ler daha çok intern oluyor
Kuralın özeti: ile string karşılaştırma yapma. kullan. Interning bir performans optimizasyonu, semantik bir özellik değil.
is==Singleton'lar — is ile karşılaştırılması GEREKEN#
isBazı Python nesneleri gerçekten tek nesne olarak garantili:
# None print(None is None) # True (tek None) a = None b = None print(a is b) # True # True ve False print(True is True) # True print(False is False) # True # NotImplemented (sentinel) print(NotImplemented is NotImplemented) # True # Ellipsis (...) print(... is ...) # True # StopIteration print(StopIteration is StopIteration) # True (class itself is singleton-like)
Bu nesneler için doğru pratik. da çalışıyor ama:
is== None- daha hızlı (id karşılaştırma).
is - Custom class 'i override edebilir — yanıltıcı sonuç.
__eq__ - Linter (PEP 8) 'a uyarı atıyor.
== None
# Çok yaygın pattern def fetch(default=None): if default is None: default = [] # ...
Bonus: weakref — bellek modeline derinlik#
weakrefPython'un bellek modelini gerçekten anlamak istersen weakref kavramına bakman gerek.
Normal değişken atama "strong reference" (güçlü referans) yaratıyor. Yani nesne, en az bir referans olduğu sürece silinmiyor.
Weak reference ise: nesne yaşıyor mu kontrol edebilirsin ama silinmesini engelleyemiyorsun.
import weakref import gc class Cache: def __init__(self, name): self.name = name print(f"Cache({name}) yaratıldı") def __del__(self): print(f"Cache({self.name}) silindi") # Strong reference c1 = Cache("strong") print(f"c1: {c1.name}") del c1 gc.collect() # "Cache(strong) silindi" yazar # Weak reference c2 = Cache("weak") ref = weakref.ref(c2) # weakref ile erişim obj = ref() print(f"weakref'ten: {obj.name if obj else 'None'}") # weak del c2 gc.collect() # "Cache(weak) silindi" yazar # weakref artık None döner obj = ref() print(f"Silindikten sonra: {obj}") # None
Use case: cache, observer pattern#
import weakref class EventBus: def __init__(self): # subscribers'ı weakref olarak tut — referans varsa otomatik silinir self._subscribers = weakref.WeakSet() def subscribe(self, listener): self._subscribers.add(listener) def emit(self, event): for listener in self._subscribers: listener.handle(event) class Listener: def handle(self, event): print(f"Got event: {event}") bus = EventBus() listener = Listener() bus.subscribe(listener) bus.emit("test") # "Got event: test" # Listener silindiğinde otomatik unsubscribe del listener import gc; gc.collect() bus.emit("test2") # hiçbir çıktı (listener kayboldu)
WeakSet kullanmadan, listener manuel çağırmazsa bellek sızıntısı olurdu. bu sorunu çözüyor.
unsubscribe()weakrefBu konu Modül 13 (Karanlık Köşeler) için derindir — şimdilik var olduğunu bil yeter.
Karar matrisi: is mi == mı?#
is==| Durum | Operator |
|---|---|
| None ile karşılaştırma | is None |
| True/False ile | Genelde if x:is True |
| Sentinel kontrolü | is _SENTINEL |
| Sayısal değer | ==is |
| String içerik | ==is |
| List/dict/set içerik | == |
| Custom class değer eşitliği | == |
| "Aynı nesne mi" kontrolü | is |
Pratik debug ipucu#
Kafayı karıştıran bir bug varsa:
# Hangi nesneye bakıyorum print(f"x: id={id(x)}, val={x!r}") # == ve is birbirinden farklı sonuç veriyor mu? print(f"x == y: {x == y}, x is y: {x is y}") # Eğer == True ama is False ise: aynı değer, farklı nesne # Eğer == False ama is True ise: __eq__ override garip — incele
Bu basit kontrol ve davranışlarındaki sürprizleri açıklar.
==isYaygın tuzaklar — son liste#
1. Integer ile is#
is# 🚫 x = 1000 y = 1000 if x is y: ... # False! 1000 cache dışı # ✅ if x == y: ...
2. String ile is#
is# 🚫 greeting = input("Merhaba ne?") if greeting is "evet": ... # implementation-defined # ✅ if greeting == "evet": ...
3. __eq__ override + is#
__eq__isclass Confusing: def __eq__(self, other): return True # her şeye True c = Confusing() print(c == 5) # True print(c == "x") # True print(c is 5) # False (identity OK) # == güvenilir değil eğer __eq__ override edilmişse # is her zaman doğru identity
4. Garbage collection ve id#
def get_id(): obj = object() return id(obj) id1 = get_id() id2 = get_id() print(id1 == id2) # bazen True! (memory reuse)
5. Cache invalidation#
import functools @functools.lru_cache(maxsize=128) def expensive(x): return x * 2 a = expensive(5) b = expensive(5) print(a is b) # True (cache aynı nesneyi dönüyor)
LRU cache identity'i koruyor. Cache'lenmiş değerlerde çalışıyor olabilir ama bunu pattern olarak kullanma.
is6. Mutable default + identity#
def f(items=[]): items.append(1) return items a = f() b = f() print(a is b) # True! (Modül 2/Ders 1 hatırla — mutable default)
Mutable default trap'i id ile bile görüyorsun.
Modül 2 sonu — neler kazandın?#
Bu Modül 2'nin 15. ve son dersi. Hadi tüm modülü gözden geçirelim:
✓ Ders 1: Variables — etiket vs kutu, mutable default tuzağı.
✓ Ders 2: PEP 8 — snake_case, PascalCase, naming convention.
✓ Ders 3: int — sınırsız precision, RSA gücü.
✓ Ders 4: float — IEEE 754, 0.1+0.2 mistik, NaN/inf.
✓ Ders 5: complex — j syntax, Euler's formula, sinyal işleme.
✓ Ders 6: Decimal — finansal precision, TR KDV hesabı.
✓ Ders 7: Fraction — tam rasyonel, müzik teorisi.
✓ Ders 8: bool/None — truthiness, sentinel pattern.
✓ Ders 9: Aritmetik op — operator overloading, Vector/Money sınıfları.
✓ Ders 10: Karşılaştırma — total_ordering, hash kontratı.
✓ Ders 11: Mantıksal op — short-circuit, validators.
✓ Ders 12: Bit-level — IntFlag, RGB, IPv4.
✓ Ders 13: Operator precedence — parantez kullanımı.
✓ Ders 14: Type conversion — Pydantic, dataclass.
✓ Ders 15: id/is/== — bu ders, identity vs equality.
Self-assessment — Modül 2 mezunu olabilir misin?#
Şu sorulara cevap verebiliyorsan, hazırsın:
- sonucunda
a = [1, 2]; b = a; a += [3]ne?b - ile
Decimal(0.1)farkı?Decimal('0.1') - neden False, nasıl güvenli karşılaştırırsın?
0.1 + 0.2 == 0.3 - ile
x & 1 == 1farkı? Neden?(x & 1) == 1 - ne döner ve neden?
a = 1000; b = 1000; a is b - decorator'unu ne için kullanırsın?
@total_ordering - tanımladığında
__eq__ile ne yapman gerek?__hash__ - ile
if x:arasında ne zaman hangi?if x is not None: - Mutable default argument tuzağı nedir, çözüm pattern'i?
- neden hata atar, nasıl düzeltirsin?
int("3.14")
10/10 alıyorsan Modül 3 (String'ler ve Metin İşleme) seni bekliyor.
Lokal Durum#
Modül 2 tamamlandı! 15 ders, ~5 saat içerik, çok-detaylı seviyede.
Sıradaki: Modül 3 — string'ler. f-string mini-dili, regex tam tur, Türkçe karakter kabusu, 18 ders boyunca metin işlemenin her köşesine.
Sık Sorulan Sorular
Çok farklı. **C++**: stack vs heap, manual memory, pointer'lar açık. **Python**: tüm objeler heap'te, reference counting + cycle GC, pointer kavramı yok. Python'un 'reference' aslında C-level'da PyObject pointer ama dilden saklanıyor. Performans tradeoff: Python yavaş ama kolay; C++ hızlı ama disiplin gerektiriyor.
Yorumlar & Soru-Cevap
(0)Yorum yazmak için giriş yap.
Yorumlar yükleniyor...
İlgili İçerikler
Modül 1: Giriş ve Kurulum
Python Nedir, Neden Bu Kadar Popüler?
Öğrenmeye BaşlaModül 1: Giriş ve Kurulum
Python Sürümlerinin Tarihi: 2'den 3.14'e, AI Winter'lardan 'No-GIL' Devrimine
Öğrenmeye BaşlaModül 1: Giriş ve Kurulum