~/icsd.ir — bash
SYSTEM_ONLINE

Magic Methods و Operator Overloading

Magic Methods (یا Dunder Methods - Double Underscore Methods) متدهایی هستند که با دو زیرخط شروع و پایان می‌یابند و رفتار built-in پایتون را برای کلاس‌های ما تعریف می‌کنند. در این فصل، با مهم‌ترین magic methods و Operator Overloading آشنا می‌شویم.

Magic Methods (یا Dunder Methods – Double Underscore Methods) متدهایی هستند که با دو زیرخط شروع و پایان می‌یابند و رفتار built-in پایتون را برای کلاس‌های ما تعریف می‌کنند. در این فصل، با مهم‌ترین magic methods و Operator Overloading آشنا می‌شویم.

معرفی Magic Methods

Magic methods به ما اجازه می‌دهند کلاس‌هایمان مثل انواع built-in رفتار کنند:

class Book:
    def __init__(self, title, pages):
        self.title = title
        self.pages = pages

book = Book("شاهنامه", 1500)
print(book)         # <__main__.Book object at 0x...>  (نه‌چندان مفید)
print(len(book))    # TypeError - Book نمی‌داند len چیست

# با magic methods رفع می‌شود

__str__ و __repr__

دو متد برای نمایش متنی شیء:

class Book:
    def __init__(self, title, author, pages):
        self.title = title
        self.author = author
        self.pages = pages
    
    def __str__(self):
        """نمایش کاربرپسند - برای print()"""
        return f"{self.title} نوشته {self.author}"
    
    def __repr__(self):
        """نمایش برای توسعه‌دهنده - دقیق و قابل بازسازی"""
        return f"Book(title={self.title!r}, author={self.author!r}, pages={self.pages})"

book = Book("شاهنامه", "فردوسی", 1500)
print(book)         # شاهنامه نوشته فردوسی
print(repr(book))   # Book(title='شاهنامه', author='فردوسی', pages=1500)

# در REPL
book  # خروجی repr
قاعده طلایی: __repr__ باید unambiguous باشد، __str__ باید readable. اگر فقط یکی پیاده می‌کنید، __repr__ را پیاده کنید (پایتون از آن به‌عنوان fallback برای __str__ استفاده می‌کند).

عملگرهای مقایسه

class Money:
    def __init__(self, amount, currency="IRR"):
        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):  # less than
        return self.amount < other.amount
    
    def __le__(self, other):  # less or equal
        return self.amount <= other.amount
    
    def __gt__(self, other):  # greater than
        return self.amount > other.amount
    
    def __ge__(self, other):  # greater or equal
        return self.amount >= other.amount
    
    def __hash__(self):
        # برای استفاده در set و dict
        return hash((self.amount, self.currency))

m1 = Money(1000)
m2 = Money(2000)
print(m1 == m2)   # False
print(m1 < m2)    # True
print(m1 != m2)   # True (پایتون خودش از __eq__ استفاده می‌کند)

functools.total_ordering

برای کاهش boilerplate، فقط __eq__ و __lt__ را پیاده کنید:

from functools import total_ordering

@total_ordering
class Version:
    def __init__(self, major, minor):
        self.major = major
        self.minor = minor
    
    def __eq__(self, other):
        return (self.major, self.minor) == (other.major, other.minor)
    
    def __lt__(self, other):
        return (self.major, self.minor) < (other.major, other.minor)

# total_ordering خودکار __le__, __gt__, __ge__ را می‌سازد
v1 = Version(3, 10)
v2 = Version(3, 12)
print(v1 < v2)   # True
print(v1 >= v2)  # False (خودکار ساخته شد)

عملگرهای ریاضی

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __add__(self, other):     # +
        return Vector(self.x + other.x, self.y + other.y)
    
    def __sub__(self, other):     # -
        return Vector(self.x - other.x, self.y - other.y)
    
    def __mul__(self, scalar):    # *
        return Vector(self.x * scalar, self.y * scalar)
    
    def __rmul__(self, scalar):   # برای 2 * vector
        return self.__mul__(scalar)
    
    def __neg__(self):            # -vector
        return Vector(-self.x, -self.y)
    
    def __abs__(self):            # abs(vector) - طول بردار
        return (self.x ** 2 + self.y ** 2) ** 0.5
    
    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(2 * v1)     # Vector(2, 4) - از __rmul__
print(-v1)        # Vector(-1, -2)
print(abs(v2))    # 5.0

عملگرهای اختصاص (in-place)

class Counter:
    def __init__(self, value=0):
        self.value = value
    
    def __iadd__(self, other):    # +=
        self.value += other
        return self
    
    def __isub__(self, other):    # -=
        self.value -= other
        return self

c = Counter(10)
c += 5
print(c.value)    # 15

کلاس‌های کانتینری

برای ساخت کلاسی که مثل لیست/دیکشنری رفتار کند:

class Playlist:
    def __init__(self, name):
        self.name = name
        self.songs = []
    
    def __len__(self):
        """len(playlist)"""
        return len(self.songs)
    
    def __getitem__(self, index):
        """playlist[i]"""
        return self.songs[index]
    
    def __setitem__(self, index, value):
        """playlist[i] = value"""
        self.songs[index] = value
    
    def __delitem__(self, index):
        """del playlist[i]"""
        del self.songs[index]
    
    def __contains__(self, song):
        """song in playlist"""
        return song in self.songs
    
    def __iter__(self):
        """for song in playlist"""
        return iter(self.songs)
    
    def add(self, song):
        self.songs.append(song)

pl = Playlist("Favorites")
pl.add("سلطان قلبها")
pl.add("ایران ایران")
pl.add("بوی جوی مولیان")

print(len(pl))                # 3
print(pl[0])                  # سلطان قلبها
print("ایران ایران" in pl)    # True

for song in pl:               # __iter__
    print(song)

del pl[1]
print(len(pl))                # 2

__call__ – کلاس‌های قابل فراخوانی

با __call__، شیء را مثل تابع صدا می‌زنیم:

class Multiplier:
    def __init__(self, factor):
        self.factor = factor
    
    def __call__(self, x):
        return x * self.factor

double = Multiplier(2)
triple = Multiplier(3)

print(double(5))    # 10
print(triple(5))    # 15

# کاربرد در دکوراتورها و factory functions
print(callable(double))  # True

__enter__ و __exit__ (Context Manager)

class Timer:
    """Context manager که زمان اجرا را اندازه‌گیری می‌کند"""
    
    def __enter__(self):
        import time
        self.start = time.time()
        return self
    
    def __exit__(self, exc_type, exc_value, traceback):
        import time
        self.elapsed = time.time() - self.start
        print(f"زمان اجرا: {self.elapsed:.3f} ثانیه")
        return False  # exception را propagate کن

# استفاده با with
with Timer() as t:
    sum(range(10_000_000))
# زمان اجرا: 0.156 ثانیه

__hash__ و قاعده تساوی

قاعده مهم: اگر __eq__ را تعریف می‌کنید، باید __hash__ را هم تعریف کنید (یا __hash__ = None برای غیر hashable).
class Point:
    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))

# حالا قابل استفاده در set و dict
points = {Point(1, 2), Point(3, 4), Point(1, 2)}
print(len(points))  # 2 (تکراری حذف شد)

__format__ – قالب‌بندی سفارشی

class Currency:
    def __init__(self, amount):
        self.amount = amount
    
    def __format__(self, spec):
        if spec == "":
            return f"{self.amount} تومان"
        elif spec == "fa":
            # تبدیل به اعداد فارسی
            num = f"{self.amount:,}"
            fa_digits = "۰۱۲۳۴۵۶۷۸۹"
            return num.translate(str.maketrans("0123456789", fa_digits)) + " تومان"
        elif spec == "short":
            return f"{self.amount/1000:.0f}K تومان"
        return str(self.amount)

c = Currency(1500000)
print(f"{c}")          # 1500000 تومان
print(f"{c:fa}")       # ۱,۵۰۰,۰۰۰ تومان
print(f"{c:short}")    # 1500K تومان

مثال جامع: کلاس Money

from functools import total_ordering

@total_ordering
class Money:
    def __init__(self, amount, currency="IRR"):
        self.amount = amount
        self.currency = currency
    
    def _check_currency(self, other):
        if self.currency != other.currency:
            raise ValueError(f"واحد متفاوت: {self.currency} vs {other.currency}")
    
    def __add__(self, other):
        self._check_currency(other)
        return Money(self.amount + other.amount, self.currency)
    
    def __sub__(self, other):
        self._check_currency(other)
        return Money(self.amount - other.amount, self.currency)
    
    def __mul__(self, factor):
        return Money(self.amount * factor, self.currency)
    
    def __eq__(self, other):
        return self.amount == other.amount and self.currency == other.currency
    
    def __lt__(self, other):
        self._check_currency(other)
        return self.amount < other.amount
    
    def __hash__(self):
        return hash((self.amount, self.currency))
    
    def __str__(self):
        return f"{self.amount:,} {self.currency}"
    
    def __repr__(self):
        return f"Money({self.amount}, {self.currency!r})"

price = Money(1_000_000)
tax = Money(90_000)
total = price + tax
print(total)          # 1,090,000 IRR
print(total > price)  # True
discount = total * 0.9
print(discount)       # 981,000.0 IRR

جمع‌بندی

  • Magic methods رفتار built-in پایتون را برای کلاس‌های شما فعال می‌کنند
  • __str__ برای نمایش کاربری، __repr__ برای دیباگ
  • عملگرهای ریاضی و مقایسه را با magic methods overload کنید
  • برای کانتینرها: __len__، __getitem__، __iter__، __contains__
  • __call__ شیء را قابل فراخوانی می‌کند
  • اگر __eq__ دارید، __hash__ هم لازم است

نمایش سایت

رنگ سایت
حالت نمایش
اندازهٔ متن
خوانایی

این تنظیمات فقط روی مرورگر شما ذخیره می‌شود.