MRO در پایتون

1022

شیءگرایی یکی از مفاهیمی است که در اکثر زبان‌های سطح بالا وجود دارد. پایتون نیز همانند سایر زبان‌های سطح بالا،‌ از مفهوم شیءگرایی و مباحث مربوط به آن پشتیبانی می‌کند. یکی از مفاهیم مربوط به شیءگرایی، «ارث‌بری» و «ارث‌بری چندگانه» است و MRO یا Method Resolution Order نیز یکی از مباحثی است که در ارث‌بری چندگانه کاربردی است. در این مقاله به بررسی مبحث MRO در پایتون خواهیم پرداخت.

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

MRO در پایتون

در پایتون، شما می‌توانید به‌طور هم‌زمان از چندین کلاس ارث‌بری کنید که به این عمل ارث‌بری چندگانه (Multiple Inheritance) گفته می‌شود.

فرض کنید یک ویژگی دارید که در دو یا چند کلاس تعریف و استفاده شده است. به نظر شما هنگامی که این ویژگی را برای کلاس فرزند صدا بزنید، ترتیب جستجو در کلاس‌ها برای یافتن این ویژگی به چه صورت خواهد بود؟

بگذارید با ذکر مثالی این موضوع را شفاف‌تر کنیم. کلاس‌های زیر را در نظر بگیرید:

class A:
    def __init__(self):
        self.number = 1
        
class B(A):
    def __init__(self):
        self.number = 2

class C(A):
    def __init__(self):
        self.number = 3

class D(A):
    def __init__(self):
        self.number = 4

class E(B, C):
    def __init__(self):
        self.number = 5

class F(C, D):
    def __init__(self):
        self.number = 6

class G(E, F):
    def __init__(self):
        self.number = 7

در تصویر زیر، نحوهٔ ارث‌بری این کلاس‌ها نمایش داده شده است:

MRO در پایتون

همان‌طور که مشاهده می‌کنید، کلاس‌های C ،B و D از کلاس A، کلاس E از کلاس‌های B و C، کلاس F از کلاس‌های C و D و کلاس G از کلاس‌های E و F ارث‌بری کرده‌اند.

اکنون در انتهای کد مورد‌نظر، قطعه کد زیر را اضافه کرده و کد خود را اجرا کنید:

g = G()
print(g.number)

خروجیِ عبارت بالا برابر 7 خواهد بود. حالا محتویات کلاس G را حذف کرده و عبارت pass را به‌جای آن قرار دهید. یعنی کلاس G به‌صورت زیر تغییر پیدا کند:

class G(E, F):
    pass

حالا مجدداً کد خود را اجرا کنید. مشاهده خواهید کرد که خروجی کد فوق برابر 5 خواهد بود. به نظر شما علت چیست؟

طبق سناریوی ارث‌بری چندگانه، هر ویژگی ابتدا در کلاس کنونی (در مثال فوق، کلاس G) جستجو می‌شود. سپس اگر در کلاس کنونی یافت نشد، در کلاس(های) پدر در اولین عمق (در مثال فوق، کلاس‌های E و F) به دنبال این ویژگی خواهیم گشت. 

دقت کنید که اگر کلاسی از چند کلاس ارث‌بری کرده باشد، جستجو در کلاس‌های پدر از چپ به راست خواهد بود. یعنی اگر کلاسی به صورت زیر ارث‌بری شده باشد:

class Child(Parent1, Parent2, Parent3):
    pass

و ویژگی موردنظر در کلاس Child وجود نداشته باشد، جستجو از کلاس Parent1 آغاز شده و در صورت عدم یافتن ویژگی در کلاس Parent1، کلاس‌ Parent2 و مجدداً در صورت عدم یافتن ویژگی در کلاس Parent2،  کلاس Parent3 جستجو می‌شود.

به این ترتیب به خطی‌سازی کلاس‌ها برای جستجو، MultiDerived و به مجموعه قواعدی که برای پیدا کردن آن مورد استفاده قرار می‌گیرد، (Method Resolution Order (MRO گفته می‌شود.

در زبان پایتون، MRO باید از مرتب‌سازی محلی اولویت‌ها (Local Precedence Ordering) جلوگیری و یک‌نوایی (Monotonicity) را فراهم کند. در این صورت اطمینان حاصل می‌شود که یک کلاس همیشه پیش از کلاس‌های پدر خود ظاهر می‌شود و در صورت وجود چندین کلاس پدر، ترتیب ذکرشده رعایت می‌شود.

برای اینکه بتوانید اولویت‌بندی کلاس‌ها در ارث‌بری را بفهمید، می‌توانید از یکی از دو روش زیر استفاده کنید:

۱. استفاده از متد ()mro

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

مثالی که در ابتدای مقاله بررسی کردیم را در نظر بگیرید و به انتهای آن، قطعه کد زیر را اضافه کنید:

print(G.mro())

خروجی عبارت فوق به‌صورت زیر خواهد بود:

[<class '__main__.G'>, <class '__main__.E'>, <class '__main__.B'>, <class '__main__.F'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.A'>, <class 'object'>]

همان‌طور که انتظار داشتیم، خروجی برابر لیستی از کلاس‌هایی بود که به‌ترتیب جستجو می‌شوند. ابتدا همان کلاس کنونی که G است بررسی می‌شود. کلاس G از کلاس‌های E و F ارث‌بری کرده است و چون کلاس E اولین (چپ‌ترین) کلاسی است که G از آن ارث‌بری کرده است، ابتدا کلاس E و سپس کلاس F بررسی می‌شود.

متد بالا را برای کلاس E نیز انجام می‌دهیم و چون ابتدا از کلاس B ارث‌بری کرده، ابتدا کلاس B و سپس کلاس C جستجو می‌شوند. سپس به سراغ کلاس F می‌رویم. این کلاس از کلاس‌های C و D ارث‌بری می‌کند. کلاس C قبلاً جستجو شده، پس به جستجوی کلاس D می‌پردازیم. اکنون به سراغ کلاس B می‌رویم و چون این کلاس از کلاس A ارث‌بری کرده است، در کلاس A جستجو می‌کنیم. چون کلاس‌های C و D نیز از کلاس A ارث‌بری کرده‌اند و قبلاً کلاس A را جستجو کرده‌ایم، مجدداً نیازی به جستجو نیست.

حال به سراغ کلاس A می‌رویم. نکته‌ای که باید به آن دقت داشته باشید این است که در پایتون تمامی کلاس‌ها به‌طور پیش‌فرض از کلاس object ارث‌بری می‌کنند. پس به سراغ جستجو در کلاس object می‌رویم.

نکته: در هر کدام از مراحل، اگر ویژگی مورد‌نظر را در کلاس کنونی پیدا کردیم، جستجو متوقف شده و دیگر کلاس‌های پدر را جستجو نمی‌کنیم.

۲. استفاده از متد جادویی __mro__

خروجی این متد برابر با خروجی تابع ()mro است، با این تفاوت که به‌جای لیست، یک تاپل از اولویت‌بندی کلاس‌ها را به ما می‌دهد.

به مثال ابتدای مقاله، تکه کد زیر را اضافه کنید:

print(G.__mro__)

خروجی کد فوق به صورت زیر خواهد بود:

(<class '__main__.G'>, <class '__main__.E'>, <class '__main__.B'>, <class '__main__.F'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.A'>, <class 'object'>)

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

آموزش برنامه نویسی با کوئرا کالج
سعید زمانی

ممکن است علاقه‌مند باشید
Coroutine در پایتون
پنجمین سری مسابقات Quera Connect: پایتون/جنگو (Python/Django)
اشتراک در
اطلاع از
guest

2 دیدگاه‌
قدیمی‌ترین
تازه‌ترین بیشترین واکنش
بازخورد (Feedback) های اینلاین
View all comments
mas
mas
11 ماه قبل

خیلی واضح عالی بود ممنون

رضا
رضا
8 ماه قبل

جالب بود اما ای کاش سورس کد متد mro رو هم بررسی میکردیدxd 🙂