İçeriğe geç

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 dakikalık okuma
Orta
Aritmetik Operatörler ve Operator Overloading: Vector(1,2) + Vector(3,4) Mucizesi
✨ Python'un en sevimli özelliği — operator overloading
Java'da
BigInteger.add(BigInteger.ONE)
yazarsın. Python'da
5 + 1
. Java'da
Vector.add(other)
method çağrısı. Python'da
v1 + v2
. 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.

7 aritmetik operatör — referans#

OperatörİsimMagic methodAugmented
+
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İsimMagic 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
-10 / 3 == -3
(toward zero). Python'da
-10 // 3 == -4
(toward minus infinity). Kafa karıştırıcı ama matematiksel olarak tutarlı.
# 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
aynı anda
//
ve
%
döner — performans için tek operasyon.

%
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:
a % b
'nin işareti her zaman
b
'nin işaretine eşit (sıfırsa 0).
Pratik 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)
üç-argümanlı versiyon RSA gibi kriptografik algoritmaların temeli. Big int aritmetik ile birlikte (Modül 2/Ders 3) Python kriptografi için doğal.

Operator vs function:
**
vs
math.pow
#

import 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
her zaman float; integer hassasiyeti kaybeder. Integer için
**
veya
pow
kullan.

Augmented 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
+=
mutable nesnede yerinde değiştirme (
__iadd__
çağrılır),
x = x + ...
ise yeni nesne yaratma. Numpy array'leri için bu çok önemli — büyük array'lerde
arr += 1
bellekte kopyalama yapmaz.
import 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#

Önceki örnekteki
return NotImplemented
ne demek?
Python'da bir operasyon iki tarafa da soruyor: önce sol operand'ın
__add__
'ını dener; o
NotImplemented
dönerse, sağ operand'ın
__radd__
('reflected add') method'unu dener.
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"
NotImplemented
Python'a "ben bu kombinasyonu bilmiyorum, sen başkasına sor" diyor. Eğer iki taraf da
NotImplemented
dönerse — Python son raise eder:
TypeError
.
🚨
NotImplemented
ile
NotImplementedError
farklı!
  • NotImplemented
    : Bir sentinel değer, operator dispatch için.
  • NotImplementedError
    : Bir exception sınıfı, "subclass implement etmedi" demek.
class AbstractBase: def calculate(self): raise NotImplementedError("Subclass must implement")
İlk return, ikincisi raise.

__radd__
— sıralı tip dönüşümü#

__radd__
"reflected add" — sol operand'ın add'ı işe yaramazsa sağ operand'a soruluyor.
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.
5 + np.array([1,2,3])
çalışıyor çünkü numpy
__radd__
tanımlı.

__iadd__
— yerinde değiştirme#

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__
performans için (büyük list/array'de yerinde modifikasyon yapma). Genelde class'lar sadece
__add__
tanımlar;
__iadd__
numpy/pandas gibi performans-kritik kütüphanelerde önemli.

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.
__slots__
ile bellek tasarrufu (her instance'da
__dict__
yok),
__hash__
ile set/dict key olarak kullanılabilir,
math.isclose
ile float karşılaştırma sağlam.

Pythonic operator overloading — protocol kararları#

Operator overload yaparken bilmen gereken protocol kuralları:

1.
NotImplemented
döndür, raise etme#

def __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):
__radd__ = __add__
kısayolu. Eğer commutative değilse (çıkarma, bölme): manuel ters çevir.
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#

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
'mizde
__iadd__
tanımlamadık — Python otomatik
__add__
'a düşüyor, yeni nesne dönüyor. Immutable semantic. Doğru.
python
# Üretim kalitesi Money sınıfı — TR KDV hesabı için optimize
from decimal import Decimal, ROUND_HALF_UP
from 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 KDV
price = Money("19.99")
vat_rate = Decimal("0.20")
 
vat = price * vat_rate # Money çarpıldı, sonuç Money
total = price + vat # Money toplandı
 
print(f"Fiyat: {price}") # 19.99 TRY
print(f"KDV: {vat}") # 4.00 TRY
print(f"Toplam: {total}") # 23.99 TRY
 
# Sepet
items = [Money("19.99"), Money("4.99"), Money("99.50")]
total = sum(items, Money("0")) # 124.48 TRY
 
# 2'şer adet
total_double = total * 2
print(f"2 ADET TOPLAM: {total_double}") # 248.96 TRY
 
# Karşılaştırma
print(price < total) # True
 
# Currency mismatch
usd = Money("100", "USD")
# price + usd # ValueError: Cannot add TRY + USD
Production-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
pow(base, exp, mod)
modüler üs alma (RSA gibi).
Augmented assignment
+=
mutable nesnede yerinde, immutable'da yeni.
Operator overloading — Money/Vector sınıfları için
__add__
,
__sub__
,
__mul__
, vs.
NotImplemented
sentinel
— Python operator dispatch protokolü.
NotImplemented
vs
NotImplementedError
— sentinel return vs exception raise.
__radd__
— sıralı tip dönüşümü (
5 + Vector
çalışsın).
__iadd__
— yerinde değiştirme (immutable vs mutable semantic).
Production Vector2D
__slots__
,
__hash__
, full operator suite.
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
__eq__
,
__lt__
,
functools.total_ordering
. Custom sınıfında nasıl sortable hale getireceğini, hashable yapmak için
__eq__
+
__hash__
kombinasyonu, ve "neden Python'un comparison protokolü C++'tan iyi" tartışmasını yapacağız.

Sık Sorulan Sorular

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...

İlgili İçerikler