Skip to content

Karşılaştırma Operatörleri ve Sortable Class: __eq__, __lt__ ve total_ordering Sırrı

< > == != <= >= görünüşte basit ama Python'da chained comparison (`0 < x < 10`), her tip için custom karşılaştırma, ve `@total_ordering` decorator gibi süslü özellikler var. Bu derste: 6 karşılaştırma operatörü derinlemesine, custom sortable class yapımı, __hash__ ve __eq__ kontratı, list/tuple/string karşılaştırma kuralları, ve sıralama için key fonksiyon pattern'leri.

Şükrü Yusuf KAYA
22 min read
Intermediate
Karşılaştırma Operatörleri ve Sortable Class: __eq__, __lt__ ve total_ordering Sırrı
🎯 Karşılaştırma — sıralamanın temeli
Bir liste sortlamak istediğinde Python ne yapıyor? Elemanları karşılaştırıyor. "<" operatörünün bilmediği bir tip varsa hata atıyor. Bu derste, custom sınıfını sortable yapmak ve neden her eq değişikliğinde hash'ı düşünmek gerektiğini öğreniyoruz.

6 Karşılaştırma Operatörü#

OperatörAnlamMagic method
==
Eşit
__eq__
!=
Eşit değil
__ne__
<
Küçük
__lt__
>
Büyük
__gt__
<=
Küçük eşit
__le__
>=
Büyük eşit
__ge__
Tip-spesifik davranışlar:
# Sayılar (numeric) print(5 == 5.0) # True (int ve float, değer eşit) print(5 == "5") # False (farklı tip) print(Decimal("5") == 5) # True (Decimal int ile karşılaştırılabilir) # String — lexicographic print("apple" < "banana") # True (alfabetik) print("Apple" < "apple") # True (büyük harf önce — ASCII 65 < 97) print("ç" < "d") # False! Türkçe sıralama Unicode'la sürpriz # List — eleman eleman print([1, 2, 3] == [1, 2, 3]) # True print([1, 2, 3] < [1, 2, 4]) # True (3 < 4) print([1, 2] < [1, 2, 3]) # True (kısa olan küçük) print([1, 2, 3] < [1, 2]) # False print([1, "a"] < [1, "b"]) # True # Tuple — list ile aynı print((1, 2) == (1, 2)) # True print((1, "a") < (1, "b")) # True # Dict — sadece == ve !=, < > yok! print({1: "a"} == {1: "a"}) # True # print({1: "a"} < {1: "b"}) # TypeError! # Set — alt küme operatörü print({1, 2} < {1, 2, 3}) # True (proper subset) print({1, 2} == {1, 2}) # True
Önemli: dict'in
<
davranışı yok. Set'in
<
davranışı "subset" anlamı taşıyor — sayısal değil mantıksal.

Chained comparison — Python'un zarafeti#

Python'da matematiksel notasyon doğal yazılır:
x = 5 # Pythonic if 0 < x < 10: print("Aralıkta") # Java/C tarzı (uzun) if 0 < x and x < 10: print("Aralıkta")
İkisi de mantıksal olarak aynı ama chained versiyon
x
sadece bir kez
evaluate ediyor. Yan etkili (side effect) işlemlerde fark eder:
def get_value(): print("Çağrıldı!") return 5 # Klasik if 0 < get_value() and get_value() < 10: print("OK") # Çağrıldı! (1) # Çağrıldı! (2 — fonksiyon iki kez çalıştı) # OK # Chained if 0 < get_value() < 10: print("OK") # Çağrıldı! (sadece 1) # OK

Daha karmaşık chain'ler#

# Üç sınır age = 25 if 18 <= age < 65: print("Çalışma yaşında") # Eşitlik chain'i a, b, c = 5, 5, 5 if a == b == c: print("Üçü eşit") # Karışık operatör x, y, z = 1, 5, 10 if x < y < z: print("Sıralı") # OK # Garip ama geçerli if 1 < 2 > 0: print("True ama anlam karışık") # 1<2 (True) AND 2>0 (True) → True
🚨 Kafayı karıştıran chain'lerden kaçın:
# Anlam karışık — okumak zor if a < b > c < d: # 🚫 ... # Daha temiz if a < b and b > c and c < d: # ✅ ...
Chain'i sadece "x bir aralıkta mı" gibi doğal durumlarda kullan.

__eq__
ve
__hash__
kontratı#

Bu Python'un kritik kurallarından biri:
__eq__
değiştirirsen
__hash__
de düşün
.
class 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__ nesne kimliği) print(hash(p1)) # bir sayı (default — id'ye dayalı) # Eğer Point'i değer-bazlı eşit yapmak istersek class Point2: def __init__(self, x, y): self.x = x self.y = y def __eq__(self, other): if not isinstance(other, Point2): return NotImplemented return self.x == other.x and self.y == other.y p1 = Point2(1, 2) p2 = Point2(1, 2) print(p1 == p2) # True print(hash(p1)) # TypeError: unhashable type: 'Point2'
🚨
__eq__
tanımlayınca
__hash__
otomatik None oluyor! Yani sınıf set'te veya dict key olarak kullanılamıyor.
Çözüm:
__hash__
da tanımla.
class Point3: def __init__(self, x, y): self.x = x self.y = y def __eq__(self, other): if not isinstance(other, Point3): return NotImplemented return self.x == other.x and self.y == other.y def __hash__(self): return hash((self.x, self.y)) # tuple'ın hash'ini kullan p1 = Point3(1, 2) p2 = Point3(1, 2) print(p1 == p2) # True print(hash(p1) == hash(p2)) # True print({p1, p2}) # {Point3(...)} — tek eleman (eşit oldukları için)

Kontrat (Python language reference)#

İki nesne eşitse (
a == b
),
hash(a) == hash(b)
olmalı.
Tersi şart değil — iki nesnenin hash'i eşit olabilir ama eşit olmayabilirler (hash çakışması, normal). Ama eşit nesnelerin hash'i mutlaka eşit olmalı.
Bu kuralı bozmak: dict/set'in iç işlemleri bozulur. Aynı veri farklı yerlere konur, lookup başarısız olur. Production'da çok sinsi bug.

Mutable nesne hashable olmamalı#

class MutablePoint: def __init__(self, x, y): self.x = x self.y = y def __eq__(self, other): return (self.x, self.y) == (other.x, other.y) def __hash__(self): return hash((self.x, self.y)) p = MutablePoint(1, 2) s = {p} p.x = 100 # mutate print(p in s) # False! — hash değişti, set artık bulamıyor # Yani mutate edilebilen sınıf hashable yapma
Pratik kural: mutable sınıfı hashable yapma. Frozen dataclass veya immutable tuple-tabanlı sınıf — evet. Mutable list/dict-tabanlı — hayır.

@total_ordering
— kısa yol#

Bir sınıfı sortable yapmak için 6 method tanımlaman gerekiyor:
__eq__
,
__lt__
,
__le__
,
__gt__
,
__ge__
,
__ne__
. Sıkıcı.
functools.total_ordering
decorator bu 4'ünü (eq + lt yetiyor) implementasyonundan otomatik üretiyor.
from functools import total_ordering @total_ordering class Version: def __init__(self, major, minor, patch): self.major = major self.minor = minor self.patch = patch def __eq__(self, other): if not isinstance(other, Version): return NotImplemented return (self.major, self.minor, self.patch) == (other.major, other.minor, other.patch) def __lt__(self, other): if not isinstance(other, Version): return NotImplemented return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch) def __hash__(self): return hash((self.major, self.minor, self.patch)) def __repr__(self): return f"v{self.major}.{self.minor}.{self.patch}" v1 = Version(1, 2, 3) v2 = Version(1, 2, 4) v3 = Version(2, 0, 0) # total_ordering otomatik üretti — hepsi çalışıyor print(v1 == v2) # False print(v1 < v2) # True print(v1 <= v2) # True (otomatik üretildi!) print(v3 > v1) # True (otomatik!) print(v3 >= v1) # True (otomatik) print(v1 != v2) # True (otomatik) # Sorting versions = [v3, v1, v2] sorted_versions = sorted(versions) print(sorted_versions) # [v1.2.3, v1.2.4, v2.0.0] # min/max print(min(versions)) # v1.2.3 print(max(versions)) # v2.0.0
🎯 Tuple comparison hack:
(self.major, self.minor, self.patch) < (other...)
— tuple'ları karşılaştırarak çoklu-alan sıralama. Python tuple'ı eleman-eleman karşılaştırıyor, ilk farklı bulduğunda karar veriyor. Major eşitse minor'a bakıyor, vs.
Performance:
@total_ordering
runtime'da method üretiyor — biraz yavaş. Performance-critical kodda 6 method'u manuel yaz. Çoğu uygulama için fark yok.

Modern alternatif: dataclass(order=True)#

from dataclasses import dataclass @dataclass(order=True, frozen=True) class Version2: major: int minor: int patch: int v1 = Version2(1, 2, 3) v2 = Version2(1, 2, 4) print(v1 < v2) # True (otomatik) print(sorted([v2, v1])) # [Version2(1, 2, 3), Version2(1, 2, 4)]
Daha az kod. Field sırası önemli — Python attribute sırasına göre karşılaştırıyor.
frozen=True
ile hashable de oldu.
Pratik tavsiye: Yeni sınıf yazıyorsan
@dataclass(order=True, frozen=True)
ile başla; özel logic gerekirse
@total_ordering
veya manuel.

Sorting —
key
parametresi#

Sıralama için sınıfı sortable yapmak şart değil;
key
parametresi yeter.
people = [ {"name": "Ali", "age": 30, "salary": 50000}, {"name": "Ayşe", "age": 25, "salary": 60000}, {"name": "Mehmet", "age": 35, "salary": 45000}, ] # Yaşa göre sorted_by_age = sorted(people, key=lambda p: p["age"]) # Maaşa göre azalan sorted_by_salary_desc = sorted(people, key=lambda p: p["salary"], reverse=True) # Multi-key: önce yaşa, eşitlikte maaşa sorted_multi = sorted(people, key=lambda p: (p["age"], -p["salary"])) # operator.itemgetter — daha hızlı (lambda'dan) from operator import itemgetter sorted_fast = sorted(people, key=itemgetter("age")) # Multi-key itemgetter sorted_multi_fast = sorted(people, key=itemgetter("age", "salary")) # Sınıflar için attrgetter from operator import attrgetter class Person: def __init__(self, name, age, salary): self.name = name self.age = age self.salary = salary people_obj = [Person("Ali", 30, 50000), Person("Ayşe", 25, 60000)] # Yaşa göre sorted(people_obj, key=attrgetter("age")) sorted(people_obj, key=lambda p: p.age) # eşdeğer
itemgetter
ve
attrgetter
lambda'dan daha hızlı (C-implementasyon). Performans-kritik kodda tercih et.

reverse
parametresi#

# Yaşa göre azalan sorted(people, key=itemgetter("age"), reverse=True) # Multi-key + reverse # Önce yaşa azalan, sonra maaşa artan? # Doğrudan reverse=True her iki sıralamayı da etkiler — istemediğin şey # Çözüm: negative trick sorted(people, key=lambda p: (-p["age"], p["salary"]))

Stable sort#

Python'un
sorted()
ve
list.sort()
Timsort algoritması — stable (eşit elemanların orijinal sırası korunur).
# Önce maaşa, sonra yaşa sırala data = [ {"name": "A", "age": 30, "salary": 5000}, {"name": "B", "age": 25, "salary": 5000}, {"name": "C", "age": 30, "salary": 6000}, ] # Multi-key tek seferde result = sorted(data, key=itemgetter("salary", "age")) # Veya stable sort kullanarak (önce ikinci sırada olanı sırala, sonra birincil) result = sorted(data, key=itemgetter("age")) result = sorted(result, key=itemgetter("salary")) # Aynı sonuç — Timsort stable olduğu için
Timsort 2002'de Tim Peters (Zen of Python yazarı!) tarafından geliştirildi. Java, Android, V8 (Node.js) — hepsi Timsort kullanıyor.

Yaygın tuzaklar#

1.
==
ile float#

(Önceki ders Modül 2/4 hatırla)
# 🚫 if 0.1 + 0.2 == 0.3: ... # ✅ import math if math.isclose(0.1 + 0.2, 0.3): ...

2.
is
ile değer karşılaştırma#

# 🚫 — implementation detail'a güvenme x = 5 if x is 5: ... # ✅ if x == 5: ... # Sadece sentinel için is if x is None: ... if x is True: ... # nadiren — 'if x:' daha iyi

3. Mixed type comparison#

# Python 3'te eşitsizlik karşılaştırma farklı tipler arasında — TypeError 1 < "1" # TypeError [1, 2] < (1, 2) # TypeError # Sadece eşitlik check'i çalışır (False döner) 1 == "1" # False (uyarı yok, OK)
Python 2'de garip kurallar vardı (str ve int sıralanabilirdi); Python 3'te düzeldi — explicit hata.

4. NaN comparison (önceki ders)#

nan = float('nan') print(nan == nan) # False! print(nan != nan) # True print(nan < 1) # False print(nan > 1) # False
NaN list'i sıralarsan random sıraya girebilir. Önce
math.isnan
ile filtrele.

5.
__eq__
tanımlayıp
__hash__
unutma#

class MyClass: def __eq__(self, other): return ... # __hash__ otomatik None oluyor! # Set/dict key olarak kullanılamaz
__eq__
değiştirdiğinde mutlaka
__hash__
durumunu düşün.

6. Mutable ile karşılaştırma#

class Cart: def __init__(self, items): self.items = items def __eq__(self, other): return self.items == other.items # liste karşılaştırma c1 = Cart([1, 2, 3]) c2 = Cart([1, 2, 3]) print(c1 == c2) # True c1.items.append(4) print(c1 == c2) # False (liste değişti) # Eğer c1'i set'e koymuştuysak (hashable yapmıştık varsayalım) # hash değişti — set artık bulamıyor — bug
Mutable sınıfı eşit yapmak OK ama hashable yapma.

7. Inheritance ile karşılaştırma#

class Animal: def __eq__(self, other): return type(self) == type(other) and self.name == other.name class Dog(Animal): pass class Cat(Animal): pass d = Dog("Karabaş") d2 = Dog("Karabaş") c = Cat("Karabaş") print(d == d2) # True print(d == c) # False (type kontrolü)
Inheritance ile karışırsa:
type(self) == type(other)
mu yoksa
isinstance
mı? Karar bağlama: aynı subclass mı eşit (strict)? Yoksa Animal hierarchy'sindeki herhangi bir sınıf eşit mi (gevşek)?
Liskov substitution prensibi: subclass instance'ı, parent class instance'ı yerine geçebilmeli. Strict tip kontrolü bunu bozar. Genelde
isinstance
tercih edilir.

Bu derste neler kazandın?#

6 karşılaştırma operatörü + tip-spesifik davranışlar (str lexicographic, list element-by-element, set subset).
Chained comparison '0 < x < 10' — Pythonic, side-effect güvenli.
'eq' ve 'hash' kontratı — eşit nesneler aynı hash dönmeli.
'@total_ordering' decorator — 4 method'u eq + lt'den otomatik.
'@dataclass(order=True, frozen=True)' — modern minimal kod.
Tuple comparison ile multi-field sorting.
'key' parametresi, 'itemgetter', 'attrgetter' — performance optimal.
Stable sort (Timsort) — eşit elemanların sırası korunuyor.
Multi-key sorting trick'leri — negative for desc, sequential sorts.
7 yaygın tuzak — float ==, is değer, mixed type, NaN, hash unutma, mutable + hash, inheritance.
Sıradaki ders: Mantıksal operatörler (
and
,
or
,
not
). De Morgan kanunları,
any()
ve
all()
built-in'leri, conditional expression (ternary), validator pattern'leri.

Frequently Asked Questions

Bağlama bağlı. **Dataclass**: Field sırasına göre karşılaştırma, hızlı, az kod. Dezavantaj: tüm field'ları karşılaştırıyor; bazı field'ları dahil etmek istemiyorsan field'da `compare=False`. **total_ordering**: Custom logic mümkün — sadece bazı alanları karşılaştır, transformasyon uygula, type checks. Kural: basit dataclass-style ise `@dataclass(order=True)`; özel logic ise `@total_ordering` + manuel `__eq__/__lt__`.

Yorumlar & Soru-Cevap

(0)
Yorum yazmak için giriş yap.
Yorumlar yükleniyor...

Related Content