FA-TOOLS — Header Component
متدهای جادویی (dunder) در پایتون — آموزش کامل

متدهای جادویی (dunder) در پایتون — آموزش کامل

رفیق برنامه‌نویس، اگه تا حالا با پایتون کار کردی، حتماً اسم متدهای جادویی یا دندر (Dunder Methods) به گوشت خورده. این متدها، با اون دوتا آندرلاین اول و آخرشون (__method__)، مثل عصای جادویی پایتون می‌مونن که بهت اجازه میدن رفتار آبجکت‌هات رو از پایه تغییر بدی و اون‌ها رو به معنای واقعی کلمه «پایتونیک» کنی. اینا همون رمز و رازهایی هستن که پشت خیلی از کارهایی که روزمره با پایتون انجام میدیم، مثل جمع کردن اعداد یا چاپ کردن یه آبجکت، پنهان شدن. آماده‌ای پا به دنیای جذاب و قدرتمند دندر متدها بذاری و کدهات رو یه لول بالا ببری؟

قبل از اینکه غرق دنیای کد و جادو بشیم، اگه دنبال ابزارهای حرفه‌ای و اسنیپت‌های پایتونی آماده می‌گردی، حتماً یه سر به فروشگاه ابزارهای FA-Tools بزن و از امکانات بی‌نظیرش استفاده کن! همینطور می‌تونی برای دیدن کلی اسنیپت پایتون کاربردی به صفحه اسنیپت‌های پایتون ما یه نگاه بندازی. برای هرگونه سوال یا مشاوره هم می‌تونی با شماره 09202232789 تماس بگیری.

🗺️ نقشه راه آموزش متدهای جادویی پایتون

متدهای جادویی (dunder) در پایتون — آموزش کامل — تصویر 1

تعریف و اهمیت
دندر متدها چیه و چرا باید بشناسیمشون؟
🏗️
ساخت و نمایش
__init__, __str__, __repr__

عملیات ریاضی
__add__, __sub__, __mul__
📏
مدیریت کالکشن‌ها
__len__, __getitem__, __setitem__

مدیریت کانتکست
__enter__, __exit__
🔑
دسترسی به ویژگی‌ها
__getattr__, __setattr__, __delattr__
🤝
مقایسه آبجکت‌ها
__eq__, __lt__, __gt__
⚙️
عیب‌یابی رایج
مشکلات و راه‌حل‌ها

فهرست مطالب

متدهای جادویی (dunder) در پایتون — آموزش کامل — تصویر 2
  1. متدهای جادویی (Dunder Methods) چی هستن؟
  2. متدهای جادویی اساسی برای شروع
  3. عملیات ریاضی با دندر متدها
  4. کنترل رفتار کالکشن‌ها
  5. دندر متدها برای مدیریت کانتکست (Context Managers)
  6. کنترل دسترسی به ویژگی‌های آبجکت
  7. دندر متدهای مقایسه‌ای
  8. تبدیل آبجکت به تابع و Hashable کردن آن
  9. جمع‌بندی
  10. عیب‌یابی سریع: مشکلات رایج با دندر متدها
  11. سوالات متداول (FAQ)

متدهای جادویی (Dunder Methods) چی هستن؟

متدهای جادویی (dunder) در پایتون — آموزش کامل — تصویر 3

متدهای جادویی یا دندر متدها (Dunder methods)، اسمشون رو از عبارت “Double Under” گرفتن، چون با دوتا آندرلاین در ابتدا و دوتا آندرلاین در انتها مشخص میشن (مثل __init__ یا __str__). این متدها در واقع قلاب‌هایی هستن که پایتون برای اعمال اپراتورها یا توابع داخلی روی آبجکت‌های کلاس‌های خودت فراهم کرده. وقتی مثلاً از عملگر + برای جمع کردن دو عدد استفاده می‌کنی، در پشت پرده متد جادویی __add__ فراخوانی میشه. اگه بخوای آبجکت‌های کلاسی که خودت نوشتی هم با + جمع بشن، باید این متد رو تو کلاس خودت پیاده‌سازی کنی.

هدف اصلی استفاده از این متدها، اینه که کدهات خواناتر و پایتونیک‌تر بشن. یعنی رفتاری که انتظار داری آبجکت‌هات داشته باشن، رو به صورت طبیعی و مطابق با فلسفه پایتون براشون تعریف کنی.

متدهای جادویی اساسی برای شروع

__init__(): سازنده کلاس

خب، از همین اول بریم سرِ __init__، یکی از آشناترین دندر متدها که تو هر کلاسی که می‌نویسی احتمالاً دیدیش. این متد به “سازنده” (constructor) کلاس معروفه. وظیفه‌اش اینه که وقتی یه آبجکت جدید از کلاس می‌سازی، اون رو مقداردهی اولیه کنه.

پایتون


class Car:
    def __init__(self, brand, model, year):
        self.brand = brand
        self.model = model
        self.year = year
        print(f"A new car ({self.brand} {self.model}) has been created!")

my_car = Car("Toyota", "Camry", 2022)
# Output: A new car (Toyota Camry) has been created!
print(my_car.brand)
# Output: Toyota
        

اینجا وقتی my_car = Car("Toyota", "Camry", 2022) رو صدا می‌زنیم، پایتون به صورت خودکار متد __init__ رو فراخوانی می‌کنه و مقادیری که دادیم رو به آبجکت نسبت میده.

__str__() و __repr__(): نمایش آبجکت

یکی از چیزایی که خیلی تو دیباگ کردن و کار با آبجکت‌ها کمکت می‌کنه، نمایش درست اون‌هاست. پایتون دوتا دندر متد برای این کار داره:

  • __str__(self): این متد وقتی فراخوانی میشه که بخوای یه نمایش “کاربرپسند” (readable for end-users) از آبجکتت داشته باشی، مثلاً با تابع print().
  • __repr__(self): این متد برای نمایش “توسعه‌دهنده پسند” (unambiguous for developers) استفاده میشه. هدفش اینه که یه رشته برگردونه که اگه اونو تو مفسر پایتون وارد کنی، آبجکت رو همونطور که هست بازسازی کنه. معمولاً تو دیباگ کردن خیلی مفیده. اگه __str__ پیاده‌سازی نشده باشه، __repr__ برای print() هم استفاده میشه.
پایتون


class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return f"({self.x}, {self.y})" # User-friendly representation

    def __repr__(self):
        return f"Point(x={self.x}, y={self.y})" # Developer-friendly representation

p = Point(10, 20)
print(p)         # Calls __str__ -> (10, 20)
print(f"{p!r}")  # Calls __repr__ -> Point(x=10, y=20)
print(p.__repr__()) # Explicitly calls __repr__ -> Point(x=10, y=20)
        

عملیات ریاضی با دندر متدها

__add__(), __sub__(), __mul__(): جمع، تفریق و ضرب

تصور کن یه کلاس برای بردارها (Vectors) داری و دوست داری بتونی اونها رو با عملگر + جمع کنی، دقیقاً مثل اعداد عادی. اینجاست که دندر متدهای عملیات ریاضی به کارت میان.

  • __add__(self, other): برای عملگر +
  • __sub__(self, other): برای عملگر -
  • __mul__(self, other): برای عملگر *
  • و کلی متد دیگه برای تقسیم، توان و …
پایتون


class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return f"Vector({self.x}, {self.y})"

    def __add__(self, other):
        if isinstance(other, Vector):
            return Vector(self.x + other.x, self.y + other.y)
        raise TypeError("Can only add Vector to another Vector")

    def __mul__(self, scalar):
        if isinstance(scalar, (int, float)):
            return Vector(self.x * scalar, self.y * scalar)
        raise TypeError("Can only multiply Vector by a scalar (int/float)")

v1 = Vector(2, 3)
v2 = Vector(4, 5)
v3 = v1 + v2
print(v3) # Output: Vector(6, 8)

v4 = v1 * 3
print(v4) # Output: Vector(6, 9)
        

تو این مثال، ما رفتار + و * رو برای آبجکت‌های Vector تعریف کردیم. دقت کن که همیشه باید یک آبجکت جدید برگردونی و آبجکت‌های اصلی رو تغییر ندی تا کدهات قابل پیش‌بینی‌تر باشن.

کنترل رفتار کالکشن‌ها

اگه کلاسی می‌سازی که شبیه به یه لیست، دیکشنری یا هر کالکشن دیگه‌ای رفتار می‌کنه، دندر متدهای کالکشن به دردت می‌خورن.

__len__(): گرفتن طول آبجکت

با پیاده‌سازی متد __len__، می‌تونی برای آبجکت کلاس خودت از تابع len() استفاده کنی. این متد باید یک عدد صحیح (integer) برگردونه که بیانگر طول آبجکت باشه.

پایتون


class MyList:
    def __init__(self, items):
        self.items = list(items)

    def __len__(self):
        return len(self.items)

my_list = MyList([1, 2, 3, 4, 5])
print(len(my_list)) # Output: 5
        

__getitem__(), __setitem__(), __delitem__(): دسترسی به آیتم‌ها

با این متدها می‌تونی آبجکت کلاست رو مثل یه لیست یا دیکشنری با براکت (Bracket Notation) مدیریت کنی:

  • __getitem__(self, key): برای دسترسی به آیتم با obj[key].
  • __setitem__(self, key, value): برای تنظیم یک آیتم با obj[key] = value.
  • __delitem__(self, key): برای حذف یک آیتم با del obj[key].
پایتون


class CustomDict:
    def __init__(self):
        self._data = {}

    def __getitem__(self, key):
        print(f"Getting item: {key}")
        return self._data[key]

    def __setitem__(self, key, value):
        print(f"Setting item: {key} = {value}")
        self._data[key] = value

    def __delitem__(self, key):
        print(f"Deleting item: {key}")
        del self._data[key]

    def __len__(self): # You might want __len__ too
        return len(self._data)

my_dict = CustomDict()
my_dict["name"] = "Alice" # Calls __setitem__
print(my_dict["name"])    # Calls __getitem__ -> Output: Getting item: name n Alice
del my_dict["name"]       # Calls __delitem__ -> Output: Deleting item: name
# print(my_dict["name"])  # This would raise KeyError now
        

این متدها بهت اجازه میدن تا منطق پشت عملیات براکت رو کنترل کنی. مثلاً می‌تونی یک دیکشنری بسازی که کلیدهاش فقط از یک نوع خاص باشن، یا موقع دسترسی به یک آیتم، لاگ بندازی.

دندر متدها برای مدیریت کانتکست (Context Managers)

احتمالاً با with open(...) as f: آشنایی. این یک نمونه از کانتکست منیجر در پایتونه که مدیریت منابع رو آسون می‌کنه. تو هم می‌تونی برای کلاس‌های خودت کانتکست منیجر بسازی.

__enter__() و __exit__(): برای بلاک‌های with

  • __enter__(self): وقتی بلاک with شروع میشه، این متد فراخوانی میشه. می‌تونی هر عملیات راه‌اندازی (setup) رو اینجا انجام بدی و آبجکتی رو برگردونی که به متغیر بعد از as نسبت داده میشه.
  • __exit__(self, exc_type, exc_val, exc_tb): وقتی بلاک with تموم میشه (چه با موفقیت و چه با خطا)، این متد فراخوانی میشه. اینجا می‌تونی عملیات پاکسازی (cleanup) رو انجام بدی. سه پارامتر exc_type, exc_val, exc_tb اطلاعات خطا رو در صورت بروز خطا بهت میدن.
پایتون


class ManagedFile:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
        self.file = None

    def __enter__(self):
        print("Entering context: Opening file...")
        self.file = open(self.filename, self.mode)
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.file:
            print("Exiting context: Closing file...")
            self.file.close()
        if exc_type:
            print(f"An exception occurred: {exc_type.__name__}: {exc_val}")
        return False # Propagate the exception if any

with ManagedFile("hello.txt", "w") as f:
    f.write("Hello, Dunder Methods!n")
    # raise ValueError("Something went wrong inside with block!")

print("File operations complete.")
# Expected output (without exception):
# Entering context: Opening file...
# Exiting context: Closing file...
# File operations complete.
        

کنترل دسترسی به ویژگی‌های آبجکت

پایتون بهت این امکان رو میده که حتی نحوه دسترسی، تنظیم و حذف ویژگی‌های آبجکت‌ها رو هم کنترل کنی.

__getattr__(), __setattr__(), __delattr__(): مدیریت ویژگی‌ها

  • __getattr__(self, name): وقتی به یک ویژگی دسترسی پیدا می‌کنی که وجود نداره، این متد فراخوانی میشه. می‌تونی ازش برای ایجاد ویژگی‌های داینامیک یا مدیریت خطای دسترسی استفاده کنی.
  • __setattr__(self, name, value): این متد هر بار که یه ویژگی روی آبجکت تنظیم میشه فراخوانی میشه. مراقب باش تو این متد حلقه بی‌نهایت ایجاد نکنی! برای دسترسی به ویژگی‌های واقعی آبجکت باید از object.__setattr__(self, name, value) استفاده کنی.
  • __delattr__(self, name): وقتی یه ویژگی با del obj.attribute حذف میشه، این متد فعال میشه.
پایتون


class AttributeLogger:
    def __setattr__(self, name, value):
        print(f"Setting attribute '{name}' to '{value}'")
        object.__setattr__(self, name, value) # Avoid infinite recursion

    def __getattr__(self, name):
        print(f"Attempting to get non-existent attribute '{name}'")
        return f"Attribute '{name}' not found!"

    def __delattr__(self, name):
        print(f"Deleting attribute '{name}'")
        object.__delattr__(self, name) # Avoid infinite recursion

log_obj = AttributeLogger()
log_obj.my_attr = 10         # Calls __setattr__
print(log_obj.my_attr)       # Normal access, doesn't call __getattr__
print(log_obj.non_existent)  # Calls __getattr__
del log_obj.my_attr          # Calls __delattr__
        

دندر متدهای مقایسه‌ای

چطور دوتا آبجکت از کلاس خودت رو با هم مقایسه می‌کنی؟ مثلاً کدوم ماشین قدیمی‌تره؟ یا کدوم دانش‌آموز نمره‌اش بالاتره؟ اینجاست که دندر متدهای مقایسه‌ای به کارت میان.

__eq__(), __lt__(), __gt__(): برابری و مقایسه

  • __eq__(self, other): برای عملگر == (برابری).
  • __ne__(self, other): برای عملگر != (نابرابری).
  • __lt__(self, other): برای عملگر < (کوچکتر).
  • __le__(self, other): برای عملگر <= (کوچکتر یا مساوی).
  • __gt__(self, other): برای عملگر > (بزرگتر).
  • __ge__(self, other): برای عملگر >= (بزرگتر یا مساوی).

با پیاده‌سازی فقط چندتا از این‌ها، می‌تونی بقیه رو هم به صورت خودکار با استفاده از دکوراتور @functools.total_ordering داشته باشی.

پایتون


from functools import total_ordering

@total_ordering
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __eq__(self, other):
        if not isinstance(other, Person):
            return NotImplemented
        return self.age == other.age and self.name == other.name

    def __lt__(self, other):
        if not isinstance(other, Person):
            return NotImplemented
        return self.age < other.age # Or any other logic

p1 = Person("Alice", 30)
p2 = Person("Bob", 25)
p3 = Person("Alice", 30)

print(p1 == p3) # True (calls __eq__)
print(p1 == p2) # False
print(p1  p2)  # True (inferred from __lt__ by @total_ordering)
        

تبدیل آبجکت به تابع و Hashable کردن آن

__call__(): آبجکت‌های قابل فراخوانی

متد __call__ به آبجکت کلاست این قابلیت رو میده که مثل یه تابع رفتار کنه و بتونی اونو مستقیماً فراخوانی کنی. خیلی جاها مثل ساخت دکوراتورها یا شبیه‌سازی توابع با حالت (state) داخلی کاربرد داره.

پایتون


class Multiplier:
    def __init__(self, factor):
        self.factor = factor

    def __call__(self, number):
        return number * self.factor

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

print(double(5)) # Output: 10 (calls __call__)
print(triple(5)) # Output: 15
        

__hash__(): ساخت آبجکت‌های Hashable

فقط آبجکت‌های “hashable” (اونایی که قابلیت هش شدن دارن) می‌تونن به عنوان کلید در دیکشنری‌ها یا عضو در مجموعه‌ها (sets) استفاده بشن. انواع داده‌ای تغییرناپذیر مثل اعداد، رشته‌ها و تاپل‌ها hashable هستن. اگه کلاسی می‌سازی که آبجکت‌هاش قراره تو این ساختارها استفاده بشن، باید متد __hash__ رو پیاده‌سازی کنی. این متد باید یک عدد صحیح برگردونه.

نکته مهم: اگه __eq__ رو پیاده‌سازی کنی اما __hash__ رو نه، آبجکتت دیگه به صورت پیش‌فرض hashable نیست. همچنین اگه دو آبجکت == باشن، باید مقدار هش یکسانی داشته باشن.

پایتون


class ImmutablePoint:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        if not isinstance(other, ImmutablePoint):
            return NotImplemented
        return self.x == other.x and self.y == other.y

    def __hash__(self):
        # A simple way to combine hashes of immutable components
        return hash((self.x, self.y))

p1 = ImmutablePoint(1, 2)
p2 = ImmutablePoint(1, 2)
p3 = ImmutablePoint(3, 4)

my_set = {p1, p3}
print(p2 in my_set) # Output: True (because p1 == p2 and hash(p1) == hash(p2))

my_dict = {p1: "first point"}
print(my_dict[p2]) # Output: first point
        

جمع‌بندی

همونطور که دیدی، دندر متدها ابزارهای فوق‌العاده قدرتمندی تو پایتون هستن که بهت اجازه میدن رفتار آبجکت‌های کلاست رو به شکل دقیق و پایتونیک کنترل کنی. با شناخت و استفاده درست از این متدها، می‌تونی کدهایی بنویسی که نه تنها عملکرد بهتری دارن، بلکه خوانایی و نگهداریشون هم آسون‌تر میشه. این متدها کلید اصلی برای درک عمیق‌تر پایتون و نوشتن کتابخونه‌های قدرتمند و فریم‌ورک‌های انعطاف‌پذیر هستن. پس ازشون نترس و به عنوان یه ابزار کاربردی تو جعبه ابزار برنامه‌نویسیت ازشون استفاده کن!

عیب‌یابی سریع: مشکلات رایج با دندر متدها

اشکالاتی که ممکنه بخوری و چطور رفعشون کنی

مشکل ۱: حلقه بی‌نهایت در __setattr__

سناریو: داخل متد __setattr__ دوباره از self.attribute = value استفاده می‌کنی.

چرا اتفاق میفته؟ هر بار که self.attribute = value رو صدا می‌زنی، پایتون دوباره __setattr__ رو فراخوانی می‌کنه و این باعث یک حلقه بی‌نهایت (recursion depth exceeded) میشه.

راه‌حل: برای تنظیم ویژگی‌های واقعی آبجکت در __setattr__، همیشه از object.__setattr__(self, name, value) استفاده کن.

پایتون (راه حل)


def __setattr__(self, name, value):
    # logging.info(f"Setting {name} to {value}")
    object.__setattr__(self, name, value) # صحیح
                

مشکل ۲: آبجکت‌های غیر Hashable به عنوان کلید دیکشنری یا عضو مجموعه

سناریو: آبجکتی از کلاس خودت رو بدون پیاده‌سازی __hash__ تو یه set یا به عنوان کلید dict استفاده می‌کنی.

چرا اتفاق میفته؟ پایتون فقط به آبجکت‌های immutable و اونایی که __hash__ دارن اجازه میده به عنوان کلید دیکشنری یا عضو مجموعه باشن. اگه __eq__ رو پیاده‌سازی کنی اما __hash__ رو نه، آبجکتت خود به خود غیر-hashable میشه.

راه‌حل: اگه آبجکتت تغییرناپذیره و قراره تو set یا dict استفاده بشه، حتماً __hash__ رو هم پیاده‌سازی کن. مطمئن شو که برای دو آبجکت که == هستن، hash() هم مقدار یکسانی برگردونه. می‌تونی از hash(tuple(self.attr1, self.attr2)) استفاده کنی.

مشکل ۳: نمایش نامفهوم آبجکت‌ها

سناریو: وقتی آبجکتت رو print می‌کنی یا تو مفسر پایتون می‌بینیش، یه چیزی شبیه <__main__.MyClass object at 0x...> بهت نشون میده.

چرا اتفاق میفته؟ متدهای __str__ و __repr__ رو پیاده‌سازی نکردی.

راه‌حل: همیشه __repr__ رو پیاده‌سازی کن تا یه نمایش واضح و بدون ابهام از آبجکتت داشته باشی. اگه نمایش کاربرپسند هم لازمت شد، __str__ رو هم اضافه کن.

مشکل ۴: فراموشی self در متدها

سناریو: خطایی شبیه TypeError: __init__() takes 0 positional arguments but 1 was given میگیری.

چرا اتفاق میفته؟ تو پایتون، اولین آرگومان هر متد نمونه‌ای (instance method) باید self باشه، که اشاره به خود آبجکت داره. این یه قاعده اساسی OOP در پایتونه.

راه‌حل: همیشه مطمئن شو که self رو به عنوان اولین پارامتر به همه دندر متدهات (و کلاً همه متدهای نمونه) اضافه کردی.

سوالات متداول (FAQ)

❓ دندر متدها چی هستن؟

متدهای جادویی یا دندر متدها (Dunder methods) متدهایی با دو آندرلاین در ابتدا و انتها (مثل __init__) هستند که به شما اجازه می‌دهند رفتار پیش‌فرض عملگرها و توابع داخلی پایتون را برای آبجکت‌های کلاس خودتان شخصی‌سازی کنید.

❓ چرا باید از دندر متدها استفاده کنیم؟

استفاده از دندر متدها باعث می‌شود کدهای شما پایتونیک‌تر و خواناتر شوند. به جای فراخوانی متدهای طولانی، می‌توانید از عملگرهای آشنا و توابع داخلی پایتون (مثل + یا len()) روی آبجکت‌های خودتان استفاده کنید.

❓ تفاوت __str__ و __repr__ چیست؟

__str__ برای تولید یک نمایش کاربرپسند از آبجکت است (مثل خروجی print()). __repr__ برای تولید یک نمایش توسعه‌دهنده-پسند و بدون ابهام است که معمولاً می‌تواند برای بازسازی آبجکت استفاده شود.

❓ چطور می‌توانم آبجکتم را قابل Hash کردن کنم؟

اگر آبجکت شما تغییرناپذیر (immutable) است و نیاز دارید که از آن به عنوان کلید در دیکشنری یا عضو در مجموعه استفاده کنید، باید متد __hash__ را پیاده‌سازی کنید. همچنین، باید مطمئن شوید که اگر دو آبجکت با __eq__ برابرند، __hash__ آن‌ها نیز یکسان باشد.

❓ آیا استفاده از دندر متدها سرعت کد را کم می‌کند؟

در بیشتر موارد، سربار (overhead) استفاده از دندر متدها ناچیز است و تأثیر محسوسی بر عملکرد برنامه شما نخواهد داشت. فواید آن‌ها در خوانایی و پایتونیک بودن کد معمولاً بسیار بیشتر از هرگونه افت عملکرد جزئی است

Table of Contents

آخرین نوشته‌ها