Aritmetik Operatörler ve Operator Overloading: Vector(1,2) + Vector(3,4) Mucizesi
+ ve - tek satırda Vector toplayabiliyor mu? Money * 1.18 ile KDV hesaplayabiliyor mu? Python'un magic method'ları (__add__, __sub__, __mul__, __radd__) sayesinde evet. Bu derste: 7 aritmetik operatör derinlemesine, augmented assignment, NotImplemented sentinel'ı, sıralı tip dönüşümü, ve gerçek Vector + Money sınıfları.
Şükrü Yusuf KAYA
22 min read
Intermediate✨ Python'un en sevimli özelliği — operator overloading
Java'da yazarsın. Python'da . Java'da method çağrısı. Python'da . Operator overloading (operatörlerin sınıflar tarafından özelleştirilmesi) Python'un dilinin doğal okunmasını sağlayan ana özellik. Bu derste sadece operatörleri kullanmıyoruz — kendi sınıflarımız için onları tanımlıyoruz.
BigInteger.add(BigInteger.ONE)5 + 1Vector.add(other)v1 + v27 aritmetik operatör — referans#
| Operatör | İsim | Magic method | Augmented |
|---|---|---|---|
+ | Toplama | __add____radd__ | __iadd__ |
- | Çıkarma | __sub____rsub__ | __isub__ |
* | Çarpma | __mul____rmul__ | __imul__ |
/ | Bölme (true) | __truediv____rtruediv__ | __itruediv__ |
// | Floor div | __floordiv____rfloordiv__ | __ifloordiv__ |
% | Modulus | __mod____rmod__ | __imod__ |
** | Üs | __pow____rpow__ | __ipow__ |
@ | Matrix mul | __matmul__ | __imatmul__ |
Ek unary operatörler:
| Operatör | İsim | Magic method |
|---|---|---|
-x | Negatif | __neg__ |
+x | Pozitif | __pos__ |
abs(x) | Mutlak değer | __abs__ |
Bu listeyi ezberleme; ihtiyaç oldukça referans olarak kullan.
/ (true division) vs // (floor division)#
///Python 3'te her zaman float döner, integer floor division yapar.
///# True division print(10 / 3) # 3.3333333333333335 (float) print(10 / 5) # 2.0 (float! Tam bölünse de float) print(-10 / 3) # -3.3333333333333335 # Floor division print(10 // 3) # 3 (int floor) print(-10 // 3) # -4 (NOT -3 — toward negative infinity) print(10 // 5) # 2 (int) print(7.5 // 2) # 3.0 (float input → float output)
🎯 Floor division'un negatif sayılardaki davranışı:
-10 // 3 == -4 # NOT -3
Bunun sebebi: floor "her zaman aşağı yuvarlama" — negatif sayıda da. Yani -3.33 → -4 (daha küçük olan).
C/Java'da (toward zero). Python'da (toward minus infinity). Kafa karıştırıcı ama matematiksel olarak tutarlı.
-10 / 3 == -3-10 // 3 == -4# C-tarzı truncation istiyorsan import math print(math.trunc(-10 / 3)) # -3 (toward zero) print(int(-10 / 3)) # -3 (int() truncates) # Veya divmod print(divmod(10, 3)) # (3, 1) — quotient + remainder print(divmod(-10, 3)) # (-4, 2) — Python tarzı
divmod//%% modulus — Python'un farkı#
%C/Java'da divisor'un işaretine sahip değil — operand'ın işaretine sahip:
%// C -10 % 3 == -1 // negatif
Python'da modulus matematiksel:
-10 % 3 == 2 # divisor (3) işaretine sahip 10 % -3 == -1
Genel kural: 'nin işareti her zaman 'nin işaretine eşit (sıfırsa 0).
a % bbPratik etkisi: cyclic indeksleme.
# Hafta günleri (0 = Pazartesi) days = ["Pzt", "Sal", "Çar", "Per", "Cum", "Cmt", "Pzr"] today = 4 # Cuma days_offset = -3 # 3 gün önce day_index = (today + days_offset) % 7 # 1 print(days[day_index]) # Sal # C tarzı: (today + days_offset) % 7 → 1 (aynı sonuç burada) # Ama days_offset = -10 olsa: day_index_c = (today + (-10)) % 7 # Python: 1, C: -6 (negatif index!)
Python'un modulus'u cyclic array için doğru. C'de manuel düzeltme gerekiyor.
% string formatting (legacy)#
%# Python 2 tarzı (legacy) "Hello, %s!" % "World" # "Hello, World!" "%d + %d = %d" % (2, 3, 5) # "2 + 3 = 5"
Bu C-tarzı format. Modern Python'da kullanma — f-string tercih et. Ama eski kodda göreceksin, bilmek iyi.
** üs — incelikleri#
**print(2 ** 10) # 1024 (int) print(2 ** 0.5) # 1.4142135623730951 (float — kare kök) print(2 ** -1) # 0.5 (negative üs → float) print((-1) ** 0.5) # 6.123233995736766e-17+1.0j (complex!) print(0 ** 0) # 1 (matematik tartışmalı, Python: 1) print(0 ** -1) # ZeroDivisionError # Modüler üs (kriptografide hayati) print(pow(2, 100, 7)) # 2 — (2^100) mod 7 # pow ile aynı: print((2 ** 100) % 7) # 2 (ama önce büyük sayı yarat — yavaş) # pow(base, -1, mod) — modüler ters (3.8+) print(pow(3, -1, 7)) # 5 — çünkü (3 * 5) mod 7 = 1
pow(base, exp, mod)Operator vs function: ** vs math.pow#
**math.powimport math print(2 ** 10) # 1024 (int eğer ikisi de int) print(math.pow(2, 10)) # 1024.0 (her zaman float) print(2 ** 0.5) # float print(math.sqrt(2)) # float — daha hızlı (özel implementasyon)
math.pow**powAugmented assignment — +=, -=, vs#
+=-=Kısaltma syntax'ı:
x = 10 x += 5 # x = x + 5 → 15 x -= 3 # x = x - 3 → 12 x *= 2 # x = x * 2 → 24 x /= 4 # x = x / 4 → 6.0 (float!) x //= 2 # x = x // 2 → 3.0 (float — bir önceki adımda float oldu) x %= 2 # x = x % 2 → 1.0 x **= 3 # x = x ** 3 → 1.0
🎯 Mutable nesnelerle kritik fark:
# List a = [1, 2, 3] b = a a += [4] # a'yı yerinde değiştirir (__iadd__ var, list extend gibi) print(a, b) # [1, 2, 3, 4] [1, 2, 3, 4] — b de değişti! # Karşılaştır: a = [1, 2, 3] b = a a = a + [4] # YENİ liste yarattı, a yeni liste'ye bağlı print(a, b) # [1, 2, 3, 4] [1, 2, 3] — b değişmedi
+=__iadd__x = x + ...arr += 1import numpy as np # Yerinde arr = np.zeros(1_000_000) arr += 1 # bellekte aynı array, +1 her yere # Yeni array arr = np.zeros(1_000_000) arr = arr + 1 # yeni array yaratıldı, eski silindi
İlk yöntem ~2x hızlı (allocation yok).
Operator overloading — temelleri#
Kendi sınıfında , , tanımlamak için magic method:
+-*class Money: def __init__(self, amount, currency="TRY"): self.amount = amount self.currency = currency def __add__(self, other): if not isinstance(other, Money): return NotImplemented if self.currency != other.currency: raise ValueError(f"Currency mismatch: {self.currency} vs {other.currency}") return Money(self.amount + other.amount, self.currency) def __sub__(self, other): if not isinstance(other, Money): return NotImplemented if self.currency != other.currency: raise ValueError(f"Currency mismatch") return Money(self.amount - other.amount, self.currency) def __mul__(self, scalar): """Money * int veya Money * Decimal""" if isinstance(scalar, (int, float, Decimal)): return Money(self.amount * scalar, self.currency) return NotImplemented def __repr__(self): return f"Money({self.amount}, '{self.currency}')" def __eq__(self, other): return (isinstance(other, Money) and self.amount == other.amount and self.currency == other.currency) from decimal import Decimal m1 = Money(Decimal("100"), "TRY") m2 = Money(Decimal("50"), "TRY") # Toplama (__add__) total = m1 + m2 print(total) # Money(150, 'TRY') # Çıkarma (__sub__) diff = m1 - m2 print(diff) # Money(50, 'TRY') # Çarpma (__mul__) with_vat = m1 * Decimal("1.18") print(with_vat) # Money(118, 'TRY') # Hata m3 = Money(Decimal("100"), "USD") m1 + m3 # ValueError: Currency mismatch
İşte bu — , , artık Money sınıfı için doğal kullanılıyor. Code okuyucular için çok daha okunabilir.
+-*NotImplemented — gizli sentinel#
NotImplementedÖnceki örnekteki ne demek?
return NotImplementedPython'da bir operasyon iki tarafa da soruyor: önce sol operand'ın 'ını dener; o dönerse, sağ operand'ın ('reflected add') method'unu dener.
__add__NotImplemented__radd__class A: def __add__(self, other): print("A.__add__ çağrıldı") return NotImplemented class B: def __radd__(self, other): print("B.__radd__ çağrıldı") return "B handled it" a = A() b = B() result = a + b # A.__add__ çağrıldı # B.__radd__ çağrıldı print(result) # "B handled it"
NotImplementedNotImplementedTypeError🚨 ile farklı!
NotImplementedNotImplementedError- : Bir sentinel değer, operator dispatch için.
NotImplemented - : Bir exception sınıfı, "subclass implement etmedi" demek.
NotImplementedError
class AbstractBase: def calculate(self): raise NotImplementedError("Subclass must implement")
İlk return, ikincisi raise.
__radd__ — sıralı tip dönüşümü#
__radd____radd__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) if isinstance(other, (int, float)): return Vector(self.x + other, self.y + other) return NotImplemented def __radd__(self, other): # 5 + Vector(1,2) için return self.__add__(other) # commutative, aynı def __repr__(self): return f"Vector({self.x}, {self.y})" v = Vector(1, 2) print(v + 5) # Vector(6, 7) — Vector.__add__ print(5 + v) # Vector(6, 7) — int.__add__ NotImplemented dönünce Vector.__radd__
Bu pattern özellikle NumPy ve pandas ile etkileşim için kritik. çalışıyor çünkü numpy tanımlı.
5 + np.array([1,2,3])__radd____iadd__ — yerinde değiştirme#
__iadd__class Counter: def __init__(self, count=0): self.count = count def __iadd__(self, other): self.count += other return self # yerinde değişti def __add__(self, other): return Counter(self.count + other) # yeni nesne def __repr__(self): return f"Counter({self.count})" c = Counter(5) print(id(c)) # örnek 12345 c += 3 # __iadd__ çağrıldı, yerinde değişti print(c) # Counter(8) print(id(c)) # 12345 (aynı id — aynı nesne) c2 = c + 1 # __add__ — yeni Counter print(id(c2)) # farklı id
__iadd____add____iadd__Tam Vector sınıfı — production-quality#
import math from typing import Union Number = Union[int, float] class Vector2D: """2D vektör — production-grade operator overloading.""" __slots__ = ("x", "y") # Memory optimization def __init__(self, x: Number, y: Number): self.x = float(x) self.y = float(y) # ─── Arithmetic ────────────────────────────────── def __add__(self, other): if isinstance(other, Vector2D): return Vector2D(self.x + other.x, self.y + other.y) return NotImplemented __radd__ = __add__ # commutative def __sub__(self, other): if isinstance(other, Vector2D): return Vector2D(self.x - other.x, self.y - other.y) return NotImplemented def __rsub__(self, other): if isinstance(other, Vector2D): return other - self return NotImplemented def __mul__(self, scalar): """Skaler çarpma.""" if isinstance(scalar, (int, float)): return Vector2D(self.x * scalar, self.y * scalar) return NotImplemented __rmul__ = __mul__ def __truediv__(self, scalar): if isinstance(scalar, (int, float)): if scalar == 0: raise ZeroDivisionError("Vector division by zero") return Vector2D(self.x / scalar, self.y / scalar) return NotImplemented def __neg__(self): return Vector2D(-self.x, -self.y) def __pos__(self): return Vector2D(self.x, self.y) def __abs__(self): """Magnitude (length).""" return math.hypot(self.x, self.y) # ─── Comparison ────────────────────────────────── def __eq__(self, other): if not isinstance(other, Vector2D): return NotImplemented return math.isclose(self.x, other.x) and math.isclose(self.y, other.y) def __hash__(self): return hash((round(self.x, 9), round(self.y, 9))) # ─── Display ───────────────────────────────────── def __repr__(self): return f"Vector2D({self.x:g}, {self.y:g})" def __str__(self): return f"({self.x:.2f}, {self.y:.2f})" # ─── Domain methods ────────────────────────────── def dot(self, other): return self.x * other.x + self.y * other.y def magnitude(self): return abs(self) def normalized(self): m = self.magnitude() if m == 0: return Vector2D(0, 0) return self / m def angle(self): """Radyan cinsinden açı.""" return math.atan2(self.y, self.x) # Kullanım v1 = Vector2D(3, 4) v2 = Vector2D(1, 2) # Doğal aritmetik print(v1 + v2) # Vector2D(4, 6) print(v1 - v2) # Vector2D(2, 2) print(v1 * 2) # Vector2D(6, 8) print(2 * v1) # Vector2D(6, 8) — __rmul__ print(v1 / 2) # Vector2D(1.5, 2) print(-v1) # Vector2D(-3, -4) print(abs(v1)) # 5.0 (magnitude) # Domain print(v1.dot(v2)) # 11 print(v1.normalized()) # Vector2D(0.6, 0.8) # Set'te kullanılabilir (hash) unique_vectors = {Vector2D(1, 1), Vector2D(2, 2), Vector2D(1, 1)} print(len(unique_vectors)) # 2
Bu sınıf gerçek game-dev kodunda kullanılabilir kalitede. ile bellek tasarrufu (her instance'da yok), ile set/dict key olarak kullanılabilir, ile float karşılaştırma sağlam.
__slots____dict____hash__math.isclosePythonic operator overloading — protocol kararları#
Operator overload yaparken bilmen gereken protocol kuralları:
1. NotImplemented döndür, raise etme#
NotImplementeddef __add__(self, other): if not isinstance(other, MyClass): return NotImplemented # ✅ Python karşı tarafa sorsun # 🚫 raise TypeError("Cannot add ...") # Python kendisi TypeError raise eder eğer iki taraf da NotImplemented dönerse return MyClass(self.val + other.val)
2. Reflected method'ları doğru implement et#
Eğer commutative ise (toplama, çarpma): kısayolu.
Eğer commutative değilse (çıkarma, bölme): manuel ters çevir.
__radd__ = __add__def __sub__(self, other): return MyClass(self.val - other.val) def __rsub__(self, other): # other - self (other'ı reverse'le) return MyClass(other - self.val)
3. __iadd__ opsiyonel — eğer yoksa Python __add__'a düşer#
__iadd____add__class Foo: def __add__(self, other): return Foo(self.val + other.val) # __iadd__ yok ama: f = Foo(5) f += 3 # f = f + 3 olarak çalışıyor — yeni Foo nesne
4. Tip kontrolü — duck typing vs strict#
# Duck typing (esnek) def __add__(self, other): try: return MyClass(self.val + other.val) except (TypeError, AttributeError): return NotImplemented # Strict (sadece aynı tip) def __add__(self, other): if not isinstance(other, MyClass): return NotImplemented return MyClass(self.val + other.val) # Hibrit (aynı tip + sayılar) def __add__(self, other): if isinstance(other, MyClass): return MyClass(self.val + other.val) if isinstance(other, (int, float)): return MyClass(self.val + other) return NotImplemented
Tercih bağlama. Genelde "explicit better than implicit" — strict tip kontrolü.
5. In-place vs new — semantic karar#
# Bu sınıf immutable mi mutable mı? # Immutable (Vector2D gibi): __iadd__ yeni nesne döner # Mutable (numpy array): __iadd__ yerinde değiştirir class ImmutableVec: def __iadd__(self, other): return self.__add__(other) # yeni nesne (immutable semantic) class MutableVec: def __iadd__(self, other): self.x += other.x self.y += other.y return self # yerinde
Vector2D__iadd____add__python
# Üretim kalitesi Money sınıfı — TR KDV hesabı için optimizefrom decimal import Decimal, ROUND_HALF_UPfrom dataclasses import dataclass @dataclass(frozen=True)class Money: """Immutable para — operator overloading ile.""" amount: Decimal currency: str = "TRY" def __post_init__(self): # frozen=True yüzünden direct attribute set yapamayız object.__setattr__(self, 'amount', Decimal(str(self.amount)).quantize( Decimal('0.01'), rounding=ROUND_HALF_UP )) def __add__(self, other): if not isinstance(other, Money): return NotImplemented if self.currency != other.currency: raise ValueError(f"Cannot add {self.currency} + {other.currency}") return Money(self.amount + other.amount, self.currency) __radd__ = __add__ def __sub__(self, other): if not isinstance(other, Money): return NotImplemented if self.currency != other.currency: raise ValueError(f"Cannot subtract") return Money(self.amount - other.amount, self.currency) def __mul__(self, factor): if isinstance(factor, (int, float, Decimal)): return Money(self.amount * Decimal(str(factor)), self.currency) return NotImplemented __rmul__ = __mul__ def __truediv__(self, divisor): if isinstance(divisor, (int, float, Decimal)): return Money(self.amount / Decimal(str(divisor)), self.currency) return NotImplemented def __neg__(self): return Money(-self.amount, self.currency) def __abs__(self): return Money(abs(self.amount), self.currency) def __lt__(self, other): if not isinstance(other, Money) or self.currency != other.currency: return NotImplemented return self.amount < other.amount def __str__(self): return f"{self.amount:,.2f} {self.currency}" # Kullanım — TR KDVprice = Money("19.99")vat_rate = Decimal("0.20") vat = price * vat_rate # Money çarpıldı, sonuç Moneytotal = price + vat # Money toplandı print(f"Fiyat: {price}") # 19.99 TRYprint(f"KDV: {vat}") # 4.00 TRYprint(f"Toplam: {total}") # 23.99 TRY # Sepetitems = [Money("19.99"), Money("4.99"), Money("99.50")]total = sum(items, Money("0")) # 124.48 TRY # 2'şer adettotal_double = total * 2print(f"2 ADET TOPLAM: {total_double}") # 248.96 TRY # Karşılaştırmaprint(price < total) # True # Currency mismatchusd = Money("100", "USD")# price + usd # ValueError: Cannot add TRY + USDProduction-grade Money sınıfı. Decimal precision + immutable + operator overloading. E-ticaret backend'inde direkt kullanılabilir.
Bu derste neler kazandın?#
✓ 7 aritmetik operatör + 3 unary — ve .
+ - * / // % ** @-x +x abs(x)✓ (true) vs (floor) — float vs int, negatif sayı davranışları.
///✓ modulus — Python'un divisor-signed davranışı, cyclic indexing için ideal.
%✓ üs — modüler üs alma (RSA gibi).
**pow(base, exp, mod)✓ Augmented assignment — mutable nesnede yerinde, immutable'da yeni.
+=✓ Operator overloading — Money/Vector sınıfları için , , , vs.
__add____sub____mul__✓ sentinel — Python operator dispatch protokolü.
NotImplemented✓ vs — sentinel return vs exception raise.
NotImplementedNotImplementedError✓ — sıralı tip dönüşümü ( çalışsın).
__radd__5 + Vector✓ — yerinde değiştirme (immutable vs mutable semantic).
__iadd__✓ Production Vector2D — , , full operator suite.
__slots____hash__✓ Production Money — Decimal precision, immutable dataclass, currency safety.
✓ 5 protocol kuralı — NotImplemented, reflected methods, in-place semantic.
Sıradaki ders: Karşılaştırma operatörleri () ve , , . Custom sınıfında nasıl sortable hale getireceğini, hashable yapmak için + kombinasyonu, ve "neden Python'un comparison protokolü C++'tan iyi" tartışmasını yapacağız.
< > == != <= >=__eq____lt__functools.total_ordering__eq____hash__Frequently Asked Questions
Python 3.5+ ile gelen `@` operator matrix multiplication için ayrılmış. NumPy'de:
```python
A @ B # matrix multiplication
A * B # element-wise multiplication
```
Önceki Python'larda `np.matmul(A, B)` veya `A.dot(B)` yazılıyordu. `@` daha okunabilir. PyTorch, JAX, TensorFlow da destekliyor. Linear algebra ve neural network kodu çok daha temiz.
Yorumlar & Soru-Cevap
(0)Yorum yazmak için giriş yap.
Yorumlar yükleniyor...
Related Content
Modül 1: Giriş ve Kurulum
Python Nedir, Neden Bu Kadar Popüler?
Start LearningModül 1: Giriş ve Kurulum
Python Sürümlerinin Tarihi: 2'den 3.14'e, AI Winter'lardan 'No-GIL' Devrimine
Start LearningModül 1: Giriş ve Kurulum