Magic Methods
Python'da sınıf yazarken __init__, __str__ gibi çift alt çizgiyle çevrili metodlar gördün. Bunlara magic methods veya dunder methods (double underscore) denir. "Sihirli" denmesinin sebebi, Python'un bu metodları otomatik olarak çağırmasıdır.
Mesela print(obj) yazdığında Python arka planda obj.__str__() çağırır. len(obj) yazdığında obj.__len__() çağırır. a + b yazdığında a.__add__(b) çağırır. Sen bu metodları tanımlayarak nesnelerinin Python ile nasıl etkileşime gireceğini belirliyorsun.
Bu derste en önemli magic method'ları öğrenip, kendi nesnelerimizi Python'un yerleşik operatörleri ve fonksiyonlarıyla uyumlu hale getireceğiz.
Magic Methods Nedir?
Magic method'lar, Python'un belirli işlemlerde (operatörler, built-in fonksiyonlar, döngüler, vb.) otomatik olarak çağırdığı özel metodlardır.
Analoji: Bir orkestra düşün. Şef (Python) belirli işaretler yapar ve müzisyenler (magic methods) otomatik olarak doğru partisyonu çalar. Şef "forte!" dediğinde davulcu gümbürdemeye başlar — şef tek tek "davul çal" demez, işaret yeterlidir. Magic method'lar da böyle: Python + operatörünü gördüğünde __add__ metodunu otomatik çalar.
# Python bu işaretleri magic method'lara çevirir
x = 42
y = 8
x + y # x.__add__(y)
x - y # x.__sub__(y)
x * y # x.__mul__(y)
len("abc") # "abc".__len__()
str(x) # x.__str__()
x == y # x.__eq__(y)
x < y # x.__lt__(y)Sen kendi sınıflarında bu metodları tanımlayarak, nesnelerinin Python'un doğal söz dizimiyle çalışmasını sağlarsın.
__str__ vs __repr__: Kullanıcı vs Geliştirici
En sık kullanılan iki magic method:
__str__: Kullanıcı İçin
print() ve str() çağrıldığında devreye girer. Amacı: okunabilir, güzel bir çıktı vermek.
__repr__: Geliştirici İçin
Interactive shell'de nesneyi yazdığında veya repr() çağrıldığında devreye girer. Amacı: nesneyi yeniden oluşturmaya yetecek kadar bilgi vermek.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
def __repr__(self):
return f"Point({self.x}, {self.y})"
p = Point(3, 5)
print(p) # (3, 5) — __str__ çağrılır
print(str(p)) # (3, 5) — __str__ çağrılır
print(repr(p)) # Point(3, 5) — __repr__ çağrılır
# Liste içindeki nesneler __repr__ kullanır!
points = [Point(1, 2), Point(3, 4)]
print(points) # [Point(1, 2), Point(3, 4)] — __repr__!Hangisini Yazmalısın?
Sadece birini yazacaksan `__repr__` yaz. Python,
__str__bulamazsa__repr__'a düşer.İkisini de yazacaksan:
__repr__teknik,__str__güzel olsun.
class User:
def __init__(self, username, email):
self.username = username
self.email = email
def __repr__(self):
return f"User(username='{self.username}', email='{self.email}')"
def __str__(self):
return f"@{self.username}"
u = User("ali", "ali@mail.com")
print(u) # @ali
print(repr(u)) # User(username='ali', email='ali@mail.com')💡 İpucu:
__repr__'ın çıktısı ideal olarakeval()ile nesneyi yeniden oluşturabilmeli:eval(repr(obj)) == obj. Her zaman mümkün olmaz ama hedefiniz bu olsun.
Karşılaştırma Metodları
Python'da ==, !=, <, >, <=, >= operatörlerinin her biri bir magic method'a karşılık gelir:
| Operatör | Method | Anlamı |
|---|---|---|
== | __eq__ | Eşit mi? |
!= | __ne__ | Eşit değil mi? |
< | __lt__ | Küçük mü? |
> | __gt__ | Büyük mü? |
<= | __le__ | Küçük veya eşit mi? |
>= | __ge__ | Büyük veya eşit mi? |
class Money:
def __init__(self, amount, currency="TL"):
self.amount = amount
self.currency = currency
def __eq__(self, other):
if not isinstance(other, Money):
return NotImplemented
return self.amount == other.amount and self.currency == other.currency
def __lt__(self, other):
if not isinstance(other, Money):
return NotImplemented
if self.currency != other.currency:
raise ValueError("Farklı para birimleri karşılaştırılamaz!")
return self.amount < other.amount
def __le__(self, other):
return self == other or self < other
def __repr__(self):
return f"Money({self.amount}, '{self.currency}')"
m1 = Money(100, "TL")
m2 = Money(200, "TL")
m3 = Money(100, "TL")
print(m1 == m3) # True
print(m1 < m2) # True
print(m1 <= m3) # True
print(m2 > m1) # True — Python __lt__'yi ters çevirerek kullanırNotImplemented Nedir?
NotImplemented döndürmek Python'a "ben bu karşılaştırmayı bilmiyorum, karşı tarafın metodunu dene" der:
class Meter:
def __init__(self, value):
self.value = value
def __eq__(self, other):
if isinstance(other, Meter):
return self.value == other.value
if isinstance(other, (int, float)):
return self.value == other
return NotImplemented # "Ben bilmiyorum, sen dene"
m = Meter(5)
print(m == Meter(5)) # True
print(m == 5) # True
print(m == "hello") # False — Python son çare olarak False döner⚠️ Dikkat:
NotImplementedileNotImplementedErrorkarıştırma!NotImplementedbir değer (return value),NotImplementedErrorbir exception (raise edilir). Karşılaştırma metodlarında her zamanNotImplementeddöndür, exception fırlatma.
__hash__: Hashable Nesneler
Python'da bir nesneyi set'e eklemek veya dict key'i olarak kullanmak istiyorsan, nesne hashable olmalı. Bunun için __hash__ metodu gerekir.
class Color:
def __init__(self, r, g, b):
self.r = r
self.g = g
self.b = b
def __eq__(self, other):
if not isinstance(other, Color):
return NotImplemented
return (self.r, self.g, self.b) == (other.r, other.g, other.b)
def __hash__(self):
return hash((self.r, self.g, self.b))
def __repr__(self):
return f"Color({self.r}, {self.g}, {self.b})"
# Set'e ekleyebiliriz
colors = {Color(255, 0, 0), Color(0, 255, 0), Color(255, 0, 0)}
print(colors) # {Color(255, 0, 0), Color(0, 255, 0)} — duplicate temizlendi!
# Dict key'i olarak kullanabiliriz
color_names = {
Color(255, 0, 0): "Kırmızı",
Color(0, 255, 0): "Yeşil",
Color(0, 0, 255): "Mavi",
}
print(color_names[Color(255, 0, 0)]) # KırmızıHash Kuralları
__eq__tanımlarsan, Python__hash__'i otomatikNoneyapar (unhashable).__eq__ve__hash__tutarlı olmalı:a == bisehash(a) == hash(b)olmalı.Mutable nesneler genellikle hashable olmamalı (değişirse hash da değişir, sorun çıkar).
# __eq__ tanımladın ama __hash__ tanımlamadın
class Broken:
def __init__(self, x):
self.x = x
def __eq__(self, other):
return self.x == other.x
b = Broken(5)
# {b} # TypeError: unhashable type: 'Broken'
# __hash__ tanımlanmadığı için set'e eklenemez!Container Protocol: __len__, __getitem__, __setitem__, __contains__
Bu metodları tanımlayarak kendi "koleksiyon" sınıflarını oluşturabilirsin:
class Playlist:
def __init__(self, name):
self.name = name
self._songs = []
def add(self, song):
self._songs.append(song)
def __len__(self):
"""len(playlist) çalışsın."""
return len(self._songs)
def __getitem__(self, index):
"""playlist[0], playlist[1:3] çalışsın."""
return self._songs[index]
def __setitem__(self, index, value):
"""playlist[0] = 'yeni şarkı' çalışsın."""
self._songs[index] = value
def __contains__(self, item):
"""'şarkı' in playlist çalışsın."""
return item in self._songs
def __repr__(self):
return f"Playlist('{self.name}', {len(self._songs)} songs)"
pl = Playlist("Favori")
pl.add("Bohemian Rhapsody")
pl.add("Stairway to Heaven")
pl.add("Hotel California")
print(len(pl)) # 3
print(pl[0]) # Bohemian Rhapsody
print(pl[1:3]) # ['Stairway to Heaven', 'Hotel California']
print("Hotel California" in pl) # True
pl[0] = "Imagine"
print(pl[0]) # Imagine
# Bonus: __getitem__ tanımladığın için for döngüsü de çalışır!
for song in pl:
print(f" 🎵 {song}")__iter__ ve __next__
__getitem__ ile döngü çalışır ama daha Pythonic olan __iter__ kullanmaktır:
class Countdown:
def __init__(self, start):
self.start = start
def __iter__(self):
current = self.start
while current > 0:
yield current
current -= 1
for n in Countdown(5):
print(n, end=" ") # 5 4 3 2 1Operatör Overloading: __add__, __mul__ ve Diğerleri
Aritmetik operatörleri kendi sınıflarında tanımlayabilirsin:
| Operatör | Method | Ters (Reverse) |
|---|---|---|
+ | __add__ | __radd__ |
- | __sub__ | __rsub__ |
* | __mul__ | __rmul__ |
/ | __truediv__ | __rtruediv__ |
// | __floordiv__ | __rfloordiv__ |
% | __mod__ | __rmod__ |
** | __pow__ | __rpow__ |
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
return NotImplemented
def __sub__(self, other):
if isinstance(other, Vector):
return Vector(self.x - other.x, self.y - other.y)
return NotImplemented
def __mul__(self, scalar):
if isinstance(scalar, (int, float)):
return Vector(self.x * scalar, self.y * scalar)
return NotImplemented
def __rmul__(self, scalar):
"""scalar * vector için (ters sıra)."""
return self.__mul__(scalar)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
print(v2 - v1) # Vector(2, 2)
print(v1 * 3) # Vector(3, 6)
print(3 * v1) # Vector(3, 6) — __rmul__ sayesinde__radd__, __rmul__ — Reverse Metodları
a + b yazıldığında Python önce a.__add__(b) dener. Eğer NotImplemented dönerse, b.__radd__(a) dener. Bu sayede farklı tipteki nesneler arasında operatör kullanabilirsin.
# v1 * 3 → v1.__mul__(3) ✅
# 3 * v1 → (3).__mul__(v1) → NotImplemented → v1.__rmul__(3) ✅__bool__: Truthy/Falsy Davranışı
Python'da her nesne bool() ile True veya False olarak değerlendirilir. __bool__ metodu ile bu davranışı kontrol edebilirsin:
class Inventory:
def __init__(self):
self.items = {}
def add(self, item, quantity=1):
self.items[item] = self.items.get(item, 0) + quantity
def __bool__(self):
"""Envanterde ürün varsa True."""
return bool(self.items)
def __len__(self):
return sum(self.items.values())
inv = Inventory()
if not inv:
print("Envanter boş!") # Bu çalışır
inv.add("Elma", 5)
inv.add("Armut", 3)
if inv:
print(f"Envanterde {len(inv)} ürün var!") # Envanterde 8 ürün var!Bool Çözümleme Sırası
Python bir nesnenin bool değerini şöyle belirler:
__bool__varsa → onu kullan.__bool__yoksa ama__len__varsa →len() != 0iseTrue.İkisi de yoksa → her zaman
True.
class A:
def __bool__(self):
return False
class B:
def __len__(self):
return 0
class C:
pass
print(bool(A())) # False — __bool__ var
print(bool(B())) # False — __len__ = 0
print(bool(C())) # True — ikisi de yok, default True__call__: Nesneyi Fonksiyon Gibi Çağırma
__call__ tanımlayarak nesneyi () ile çağrılabilir hale getirebilirsin:
class Multiplier:
def __init__(self, factor):
self.factor = factor
def __call__(self, value):
return value * self.factor
double = Multiplier(2)
triple = Multiplier(3)
print(double(5)) # 10 — double.__call__(5)
print(triple(5)) # 15
# callable() ile kontrol
print(callable(double)) # True
print(callable(42)) # FalsePratik Kullanım: Fonksiyon Fabrikası
class Validator:
def __init__(self, min_val, max_val):
self.min_val = min_val
self.max_val = max_val
def __call__(self, value):
if self.min_val <= value <= self.max_val:
return True
raise ValueError(f"{value}, {self.min_val}-{self.max_val} arasında olmalı!")
# Validator'lar oluştur
validate_age = Validator(0, 150)
validate_score = Validator(0, 100)
validate_temp = Validator(-50, 60)
print(validate_age(25)) # True
print(validate_score(85)) # True
try:
validate_temp(100)
except ValueError as e:
print(e) # 100, -50-60 arasında olmalı!Decorator Olarak Callable Sınıf
class Timer:
"""Fonksiyonun çalışma süresini ölçen decorator sınıfı."""
def __init__(self, func):
self.func = func
self.call_count = 0
def __call__(self, *args, **kwargs):
import time
self.call_count += 1
start = time.perf_counter()
result = self.func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{self.func.__name__}() — {elapsed:.4f}s (#{self.call_count})")
return result
@Timer
def slow_function(n):
return sum(range(n))
slow_function(1_000_000) # slow_function() — 0.0312s (#1)
slow_function(10_000_000) # slow_function() — 0.3124s (#2)@functools.total_ordering: Kısayol
6 karşılaştırma metodu yazmak yorucu. @functools.total_ordering ile sadece __eq__ ve bir sıralama metodu (__lt__, __gt__, __le__ veya __ge__) tanımlamanız yeterli — geri kalanı otomatik oluşturulur:
from functools import total_ordering
@total_ordering
class Student:
def __init__(self, name, grade):
self.name = name
self.grade = grade
def __eq__(self, other):
if not isinstance(other, Student):
return NotImplemented
return self.grade == other.grade
def __lt__(self, other):
if not isinstance(other, Student):
return NotImplemented
return self.grade < other.grade
def __repr__(self):
return f"Student('{self.name}', {self.grade})"
s1 = Student("Ali", 85)
s2 = Student("Veli", 92)
s3 = Student("Ayşe", 85)
print(s1 == s3) # True — __eq__
print(s1 < s2) # True — __lt__
print(s2 > s1) # True — otomatik oluşturuldu!
print(s1 >= s3) # True — otomatik oluşturuldu!
print(s1 <= s2) # True — otomatik oluşturuldu!
# Sıralama da çalışır!
students = [s2, s1, s3]
print(sorted(students))
# [Student('Ali', 85), Student('Ayşe', 85), Student('Veli', 92)]⚠️ Dikkat:
@total_orderingküçük bir performans maliyeti var çünkü otomatik oluşturulan metodlar her seferinde birden fazla karşılaştırma yapar. Performans kritik kodda tüm metodları elle yazmak daha iyi olabilir. Ama çoğu durumda bu maliyet ihmal edilebilir.
Pratik: Vector2D Sınıfı
Tüm magic method'ları kullanan kapsamlı bir örnek:
from functools import total_ordering
import math
@total_ordering
class Vector2D:
"""2 boyutlu vektör sınıfı."""
def __init__(self, x, y):
self.x = x
self.y = y
# ─── String Temsilleri ─────────────────
def __str__(self):
return f"({self.x}, {self.y})"
def __repr__(self):
return f"Vector2D({self.x}, {self.y})"
# ─── Aritmetik Operatörler ─────────────
def __add__(self, other):
if isinstance(other, Vector2D):
return Vector2D(self.x + other.x, self.y + other.y)
return NotImplemented
def __sub__(self, other):
if isinstance(other, Vector2D):
return Vector2D(self.x - other.x, self.y - other.y)
return NotImplemented
def __mul__(self, scalar):
if isinstance(scalar, (int, float)):
return Vector2D(self.x * scalar, self.y * scalar)
if isinstance(scalar, Vector2D):
# Dot product
return self.x * scalar.x + self.y * scalar.y
return NotImplemented
def __rmul__(self, scalar):
return self.__mul__(scalar)
def __neg__(self):
return Vector2D(-self.x, -self.y)
def __abs__(self):
"""Vektörün büyüklüğü (magnitude)."""
return math.sqrt(self.x**2 + self.y**2)
# ─── Karşılaştırma (büyüklüğe göre) ───
def __eq__(self, other):
if isinstance(other, Vector2D):
return self.x == other.x and self.y == other.y
return NotImplemented
def __lt__(self, other):
if isinstance(other, Vector2D):
return abs(self) < abs(other)
return NotImplemented
def __hash__(self):
return hash((self.x, self.y))
# ─── Container Protocol ────────────────
def __len__(self):
return 2 # 2D vektör
def __getitem__(self, index):
if index == 0:
return self.x
elif index == 1:
return self.y
raise IndexError("Vector2D index 0 veya 1 olmalı")
def __iter__(self):
yield self.x
yield self.y
def __contains__(self, value):
return value in (self.x, self.y)
# ─── Bool ──────────────────────────────
def __bool__(self):
"""Sıfır vektör değilse True."""
return self.x != 0 or self.y != 0
# ─── Callable ──────────────────────────
def __call__(self, scalar):
"""Kısayol: v(3) == v * 3"""
return self * scalar
# ─── Ek Metodlar ──────────────────────
@property
def magnitude(self):
return abs(self)
def normalize(self):
"""Birim vektör döner."""
mag = abs(self)
if mag == 0:
raise ValueError("Sıfır vektör normalize edilemez!")
return Vector2D(self.x / mag, self.y / mag)
def dot(self, other):
"""Dot product."""
return self.x * other.x + self.y * other.y
def angle_between(self, other):
"""İki vektör arasındaki açı (radyan)."""
cos_angle = self.dot(other) / (abs(self) * abs(other))
return math.acos(max(-1, min(1, cos_angle)))# Kullanım
v1 = Vector2D(3, 4)
v2 = Vector2D(1, 2)
# String temsilleri
print(v1) # (3, 4)
print(repr(v1)) # Vector2D(3, 4)
# Aritmetik
print(v1 + v2) # (4, 6)
print(v1 - v2) # (2, 2)
print(v1 * 2) # (6, 8)
print(2 * v1) # (6, 8)
print(-v1) # (-3, -4)
# Büyüklük
print(abs(v1)) # 5.0
print(v1.magnitude) # 5.0
# Karşılaştırma
print(v1 > v2) # True (5.0 > 2.236...)
print(v1 == Vector2D(3, 4)) # True
# Container
print(len(v1)) # 2
print(v1[0]) # 3
print(v1[1]) # 4
print(3 in v1) # True
# İterasyon
x, y = v1 # Unpacking!
print(x, y) # 3 4
# Bool
print(bool(v1)) # True
print(bool(Vector2D(0, 0))) # False
# Callable
print(v1(3)) # (9, 12) — v1 * 3
# Set/dict key
vectors = {v1, v2, Vector2D(3, 4)}
print(len(vectors)) # 2 — duplicate temizlendi
# Normalize
print(v1.normalize()) # (0.6, 0.8)
# Dot product
print(v1 * v2) # 11 (3*1 + 4*2)
print(v1.dot(v2)) # 11Tüm Önemli Magic Method'lar: Referans Tablosu
String / Temsil
| Method | Tetikleyen | Açıklama |
|---|---|---|
__str__ | str(), print() | Kullanıcı dostu çıktı |
__repr__ | repr(), interactive shell | Geliştirici dostu çıktı |
__format__ | format(), f-string | Özel formatlama |
Karşılaştırma
| Method | Tetikleyen |
|---|---|
__eq__ | == |
__ne__ | != |
__lt__ | < |
__gt__ | > |
__le__ | <= |
__ge__ | >= |
Aritmetik
| Method | Tetikleyen |
|---|---|
__add__ / __radd__ | + |
__sub__ / __rsub__ | - |
__mul__ / __rmul__ | * |
__truediv__ / __rtruediv__ | / |
__floordiv__ | // |
__mod__ | % |
__pow__ | ** |
__neg__ | -obj (unary) |
__abs__ | abs() |
Container
| Method | Tetikleyen |
|---|---|
__len__ | len() |
__getitem__ | obj[key] |
__setitem__ | obj[key] = val |
__delitem__ | del obj[key] |
__contains__ | item in obj |
__iter__ | for x in obj |
Diğer
| Method | Tetikleyen |
|---|---|
__bool__ | bool(), if obj |
__call__ | obj() |
__hash__ | hash(), set/dict key |
__init__ | Nesne oluşturma |
__del__ | Nesne silinme |
Yaygın Hatalar
1. __eq__ Yazıp __hash__ Unutmak
class BadPoint:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return self.x == other.x and self.y == other.y
# __hash__ yok! Python otomatik None yapar.
p = BadPoint(1, 2)
# {p} # TypeError: unhashable type: 'BadPoint'2. NotImplemented Yerine False Döndürmek
# ❌ Kötü
def __eq__(self, other):
if not isinstance(other, MyClass):
return False # Diğer tarafın şansını elinden aldın!
# ✅ İyi
def __eq__(self, other):
if not isinstance(other, MyClass):
return NotImplemented # "Ben bilmiyorum, karşı taraf denesin"3. Magic Method'u Doğrudan Çağırmak
# ❌ Yapma
obj.__len__()
obj.__str__()
obj.__eq__(other)
# ✅ Yap
len(obj)
str(obj)
obj == other💡 İpucu: Magic method'ları doğrudan çağırmak antipattern'dir. Her zaman Python'un operatörlerini veya built-in fonksiyonlarını kullan. Tek istisna:
super().__init__()gibi kalıtım senaryoları.
Özet
Magic methods (dunder methods), Python'un operatör ve fonksiyonlarda otomatik çağırdığı özel metodlardır.
`__str__` kullanıcı için okunabilir çıktı, `__repr__` geliştirici için teknik çıktı verir. Sadece birini yazacaksan
__repr__yaz.Karşılaştırma (
__eq__,__lt__vb.) ile nesneleri karşılaştırabilir, `@functools.total_ordering` ile sadece 2 metod yeterli.`__hash__` tanımlayarak nesneleri set/dict'te kullanabilirsin.
__eq__ile tutarlı olmalı.Container protocol (
__len__,__getitem__,__contains__) ile kendi koleksiyon sınıflarını yapabilirsin.Operatör overloading (
__add__,__mul__) ile nesnelerin+,*gibi operatörlerle çalışmasını sağlarsın.`__bool__` truthy/falsy davranışı, `__call__` nesneyi fonksiyon gibi çağırma yeteneği verir.
AI Asistan
Sorularını yanıtlamaya hazır