خانه / آموزش‌ها / آموزش کامل MRO (Method Resolution Order) در پایتون

آموزش کامل MRO (Method Resolution Order) در پایتون

🐍 HomeOfPython
|
📅 1404/10/18

سطح مقدماتی (Beginner Level)

ارث‌بری (Inheritance) یکی از ارکان اصلی برنامه‌نویسی شی‌گرا است. زمانی که یک کلاس از کلاس دیگری ارث‌بری می‌کند، پایتون باید بداند که وقتی متدی را صدا می‌زنید، آن را از کجا پیدا کند. به ترتیبی که پایتون برای جستجوی متدها و ویژگی‌ها (Attributes) در کلاس‌ها طی می‌کند، Method Resolution Order یا به اختصار MRO گفته می‌شود.

در سطح مقدماتی، ما با سناریوهای ساده‌ی ارث‌بری یگانه و چندگانه آشنا می‌شویم.

۱. ارث‌بری یگانه (Single Inheritance)

در ساده‌ترین حالت، وقتی کلاس Child از کلاس Parent ارث می‌برد، پایتون ابتدا در خودِ کلاس Child به دنبال متد می‌گردد. اگر پیدا نشد، به سراغ Parent می‌رود.

دیاگرام ارث‌بری ساده

مثال اول: اولویت کلاس فرزند

در این مثال، متد در کلاس فرزند تعریف شده است، پس همان اجرا می‌شود.

Python

مثال دوم: استفاده از متد والد

اگر متد در فرزند نباشد، پایتون به بالا (والد) نگاه می‌کند.

Python

۲. ارث‌بری چندگانه (Multiple Inheritance) و ترتیب اولیه

پایتون از ارث‌بری چندگانه پشتیبانی می‌کند، یعنی یک کلاس می‌تواند هم‌زمان از چند کلاس ارث ببرد: class Child(Father, Mother). قانون کلی و ساده در اینجا چپ به راست است. پایتون ابتدا در کلاس جاری، سپس در اولین والد (سمت چپ)، سپس در والد بعدی و الی آخر جستجو می‌کند.

مثال اول: اولویت چپ به راست

در اینجا Child ابتدا از LeftParent و سپس از RightParent ارث می‌برد. چون LeftParent اول آمده، اولویت با آن است.

Python

مثال دوم: تغییر ترتیب ارث‌بری

اگر جای والدها را عوض کنیم، اولویت تغییر می‌کند.

Python

۳. مشاهده‌ی MRO با __mro__

چگونه مطمئن شویم پایتون چه مسیری را طی می‌کند؟ هر کلاس در پایتون یک ویژگی جادویی به نام __mro__ یا متد mro() دارد که لیست ترتیب جستجو را برمی‌گرداند.

مثال اول: چاپ MRO لیست

این کد دقیقاً مسیری که پایتون طی می‌کند را نشان می‌دهد. توجه کنید که همیشه کلاس object (کلاس پایه تمام کلاس‌های پایتون) در انتهای لیست قرار دارد.

Python

مثال دوم: بررسی MRO در زنجیره طولانی‌تر

Python

سطح پیشرفته (Professional Level)

در سطح حرفه‌ای، MRO فراتر از یک قانون ساده "چپ به راست" است. پایتون از نسخه 2.3 به بعد از الگوریتمی به نام C3 Linearization استفاده می‌کند تا پیچیدگی‌های ارث‌بری‌های تودرتو و به‌ویژه مشکل الماس (Diamond Problem) را حل کند. درک دقیق این موضوع برای نوشتن فریم‌ورک‌ها و استفاده صحیح از super() حیاتی است.

۱. مشکل الماس (The Diamond Problem)

مشکل الماس زمانی رخ می‌دهد که دو کلاس B و C از کلاس A ارث می‌برند، و کلاس D از هر دو کلاس B و C ارث می‌برد. اگر از الگوریتم‌های قدیمی (مثل "اول عمق" ساده) استفاده می‌شد، ممکن بود کلاس A دو بار فراخوانی شود یا ترتیب متدها بهم بریزد.

دیاگرام مشکل الماس

مثال اول: ساختار الماس و ترتیب صحیح

پایتون با الگوریتم C3 تضمین می‌کند که کلاس A (ریشه مشترک) تنها پس از بررسی B و C فراخوانی شود.

Python

مثال دوم: فراخوانی همه‌ی متدها با super()

برای اینکه مطمئن شویم تمام متدهای والدین در ساختار الماس اجرا می‌شوند (مثلاً برای مقداردهی اولیه)، باید از super() استفاده کنیم. super() متد را در کلاس والد صدا نمی‌زند، بلکه کلاس بعدی در لیست MRO را صدا می‌زند.

Python

۲. الگوریتم خطی‌سازی C3 (C3 Linearization)

الگوریتم C3 با ادغام کردن لیست والدین و حفظ ترتیب نسبی آن‌ها کار می‌کند. قوانین اصلی آن عبارتند از:

  1. فرزند همیشه قبل از والد بررسی می‌شود.
  2. اگر یک کلاس چند والد داشته باشد، ترتیب تعریف آن‌ها (چپ به راست) حفظ می‌شود.
  3. اگر کلاسی در چند مسیر مختلف والد باشد، تا زمانی که تمام فرزندانش بررسی نشده‌اند، بررسی نمی‌شود (این قانون مشکل الماس را حل می‌کند).

مثال اول: لاجیک (Pseudo-code)

این یک کد استاتیک است که منطق ریاضی پشت C3 را نشان می‌دهد. فرض کنید L[C] لیست خطی شده کلاس C باشد.

python
# Static Representation of C3 Logic
# L[C(B1 ... BN)] = C + merge(L[B1] ... L[BN], B1 ... BN)

# قانون Merge:
# اولین کلاسی (Head) از لیست‌ها را انتخاب کن که در "Tail" (باقی‌مانده) هیچ‌کدام از لیست‌های دیگر نباشد.
# اگر چنین کلاسی پیدا شد، آن را به لیست نهایی اضافه کن و از تمام لیست‌ها حذف کن.
# تکرار کن تا لیست‌ها خالی شوند.

مثال دوم: محاسبه دستی یک ساختار پیچیده

بیایید یک ساختار پیچیده را بررسی کنیم و ببینیم آیا پایتون طبق منطق ما عمل می‌کند یا خیر. O کلاس پایه است. A از O، بی (B) از O. C از A و B.

Python

۳. MRO ناسازگار (Inconsistent MRO)

گاهی اوقات نمی‌توان یک ترتیب خطی (Linear) ایجاد کرد که تمام قوانین (تقدم فرزند بر والد و تقدم چپ به راست) را رعایت کند. در این صورت پایتون TypeError می‌دهد.

مثال اول: خطای ناسازگاری

اگر کلاس A از B ارث ببرد، ولی در کلاس C بگوییم اول A بررسی شود بعد B (که طبیعی است) اما در جای دیگر تناقض ایجاد کنیم، خطا رخ می‌دهد. اما خطای رایج‌تر این است: X از Y ارث می‌برد. class Z(Y, X) اینجا Z می‌گوید اول Y را بگرد (چپ)، بعد X را. اما X خودش فرزند Y است و باید قبل از آن باشد. این یک تناقض است.

python
# Static Code - This causes a TypeError at definition time
class X: pass
class Y(X): pass

# خطا! 
# Y می‌خواهد قبل از X باشد (چون فرزند است)
# اما در تعریف Z، کلاس X قبل از Y آمده (آرگومان اول)
class Z(X, Y): 
    pass
# TypeError: Cannot create a consistent method resolution order (MRO)

مثال دوم: حل ناسازگاری

برای حل مشکل بالا، باید ترتیب والدین در تعریف کلاس Z را جابجا کنیم تا با سلسله مراتب ارث‌بری Y و X همخوانی داشته باشد.

Python

۴. متد super() با آرگومان‌ها

بسیاری از توسعه‌دهندگان نمی‌دانند که super() می‌تواند آرگومان بگیرد تا نقطه شروع جستجو در MRO را تغییر دهد. فرمت آن super(Class, instance) است که می‌گوید: "در MRO مربوط به instance، جستجو را از بعد از Class شروع کن".

مثال اول: پرش از روی یک کلاس

فرض کنید می‌خواهیم متد پدربزرگ را صدا بزنیم، نه پدر را.

Python

مثال دوم: استفاده در کلاس‌های Mixin

این تکنیک در پترن‌های Mixin بسیار کاربردی است، جایی که می‌خواهید متدهای خاصی را در زنجیره تزریق کنید اما کنترل دقیقی بر ترتیب اجرا داشته باشید.

Python