ترتیب حل متد در پایتون 2.3

توجه

این یک سند تاریخی است که به‌عنوان پیوستی برای مستندات رسمی ارائه شده است. ترتیب حل متدکه در اینجا مورد بحث قرار گرفته، در Python 2.3 معرفی شد، اما همچنان در نسخه‌های بعدی — از جمله Python 3 — استفاده می‌شود.

نوشته‌شده توسط Michele Simionato.

چکیده:

این سند برای برنامه‌نویسان پایتون در نظر گرفته شده است که می‌خواهند ترتیب حل متد C3 (C3 Method Resolution Order) را که در پایتون 2.3 استفاده می‌شود، درک کنند. اگرچه این سند برای تازه‌کاران در نظر گرفته نشده است، اما با مثال‌های حل‌شده‌ی متعدد، کاملاً آموزشی است. من از اسناد دیگری که به‌صورت عمومی در دسترس باشند و همان محدوده را داشته باشند، آگاه نیستم؛ بنابراین باید مفید باشد.

سلب مسئولیت:

من این سند را تحت پروانه‌ی Python 2.3 به بنیاد نرم‌افزار پایتون اهدا می‌کنم. همان‌طور که در این شرایط معمول است، به شما، خواننده، هشدار می‌دهم که آنچه در ادامه می‌آید باید صحیح باشد، اما هیچ ضمانتی نمی‌دهم. با مسئولیت و خطر خودتان از آن استفاده کنید!

سپاسگزاری‌ها:

همه‌ی افراد فهرست پستی پایتون که حمایت خود را برایم فرستادند. Paul Foley که بی‌دقتی‌های مختلفی را گوشزد کرد و مرا بر آن داشت تا بخش مربوط به ترتیب اولویت محلی را اضافه کنم. David Goodger برای کمک در قالب‌بندی در reStructuredText. David Mertz برای کمک در ویرایش. در نهایت، Guido van Rossum که با اشتیاق این سند را به صفحه‌ی اصلی رسمی پایتون 2.3 افزود.

آغاز

خوشبخت کسی است که توانسته است علل چیزها را بشناسد -- ویرژیل

همه‌چیز با پیامی از Samuele Pedroni در فهرست ایمیلی توسعه پایتون آغاز شد [1]. در پیام خود، Samuele نشان داد که ترتیب حل متد (method resolution order) در پایتون 2.2 یک‌نواخت نیست و پیشنهاد داد آن را با ترتیب حل متد C3 جایگزین کند. Guido با استدلال‌های او موافقت کرد و بنابراین اکنون پایتون 2.3 از C3 استفاده می‌کند. خود روش C3 هیچ ارتباطی به پایتون ندارد، زیرا توسط افرادی که بر روی Dylan کار می‌کردند ابداع شده است و در مقاله‌ای که برای برنامه‌نویسان Lisp در نظر گرفته شده است، توصیف شده است [2]. مقاله حاضر بحثی (امیدواریم) خواندنی از الگوریتم C3 را برای پایتون‌کاران (Pythonistas) ارائه می‌دهد که می‌خواهند دلایل این تغییر را درک کنند.

پیش از هر چیز، اجازه دهید اشاره کنم که آنچه می‌خواهم بگویم فقط در مورد کلاس‌های جدید معرفی‌شده در Python 2.2 صدق می‌کند: کلاس‌های کلاسیک ترتیب حل متد قدیمی خود، یعنی عمق‌اول و سپس چپ‌به‌راست، را حفظ می‌کنند. بنابراین، برای کلاس‌های کلاسیک هیچ شکستی در کد قدیمی رخ نمی‌دهد؛ و حتی اگر از نظر اصولی ممکن است شکستی در کد برای کلاس‌های جدید Python 2.2 وجود داشته باشد، در عمل مواردی که ترتیب حل C3 با ترتیب حل متد Python 2.2 تفاوت دارد، آن‌قدر نادر هستند که انتظار نمی‌رود هیچ شکست واقعی در کد رخ دهد. بنابراین:

نترسید!

علاوه بر این، مگر اینکه از وراثت چندگانه به‌طور گسترده استفاده کنید و سلسله‌مراتب‌های غیربدیهی داشته باشید، نیازی نیست الگوریتم C3 را درک کنید و می‌توانید به‌راحتی از این مقاله بگذرید. از سوی دیگر، اگر واقعاً می‌خواهید بدانید که وراثت چندگانه چگونه کار می‌کند، این مقاله برای شماست. خبر خوب این است که مسائل آن‌گونه که ممکن است انتظار داشته باشید، پیچیده نیستند.

اجازه دهید با چند تعریف پایه آغاز کنم.

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

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

  3. ترتیب حل متد مجموعه‌ای از قوانین است که خطی‌سازی را تشکیل می‌دهد. در ادبیات پایتون، اصطلاح «the MRO of C» نیز به‌عنوان مترادفی برای خطی‌سازی کلاس C به کار می‌رود.

  4. برای مثال، در مورد سلسله‌مراتب وراثت تکی، اگر C زیرکلاسی از C1 باشد و C1 زیرکلاسی از C2 باشد، آنگاه خطی‌سازی C صرفاً فهرست [C, C1 , C2] است. با این حال، در سلسله‌مراتب‌های وراثت چندگانه، ساختن خطی‌سازی دشوارتر است، زیرا ساختن خطی‌سازی‌ای که ترتیب تقدم محلی و یکنواختی را رعایت کند، دشوارتر است.

  5. من ترتیب تقدم محلی را بعداً بررسی خواهم کرد، اما می‌توانم تعریف یک‌نواختی را در اینجا ارائه دهم. یک MRO زمانی یک‌نواخت است که شرط زیر برقرار باشد: اگر C1 در خطی‌سازی C پیش از C2 بیاید، آنگاه C1 در خطی‌سازی هر زیرکلاسی از C پیش از C2 خواهد آمد. در غیر این صورت، عملیات بی‌ضرر اشتقاق یک کلاس جدید می‌تواند ترتیب حل متدها را تغییر دهد و به‌طور بالقوه باعث ایجاد باگ‌های بسیار ظریف شود. مثال‌هایی که در آن‌ها این اتفاق می‌افتد، بعداً نشان داده خواهند شد.

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

در اینجا مثالی از این موقعیت ارائه می‌شود. سلسله‌مراتب را در نظر بگیرید

>>> O = object
>>> class X(O): pass
>>> class Y(O): pass
>>> class A(X,Y): pass
>>> class B(Y,X): pass

که می‌توان آن را با نمودار وراثت زیر نمایش داد، که در آن کلاس object را با O نشان داده‌ام، کلاسی که آغاز هر سلسله‌مراتبی برای کلاس‌های به‌سبک جدید است:

 -----------
|           |
|    O      |
|  /   \    |
 - X    Y  /
   |  / | /
   | /  |/
   A    B
   \   /
     ?

در این حالت، نمی‌توان یک کلاس جدید C را از A و B مشتق کرد، زیرا X در A پیش از Y قرار دارد، اما Y در B پیش از X قرار دارد، بنابراین ترتیب حل متد (method resolution order) در C مبهم خواهد بود.

پایتون 2.3 در این شرایط یک استثنا پرتاب می‌کند (TypeError: MRO conflict among bases Y, X) و برنامه‌نویس ناآگاه را از ایجاد سلسله‌مراتب‌های مبهم بازمی‌دارد. پایتون 2.2 در عوض استثنا پرتاب نمی‌کند، بلکه یک ترتیب موردی را انتخاب می‌کند (در این مورد CABXYO).

ترتیب حل متد C3

اجازه دهید چند نمادگذاری ساده را معرفی کنم که برای بحث پیش رو مفید خواهند بود. من از نمادگذاری میان‌بر زیر استفاده خواهم کرد:

C1 C2 ... CN

برای نشان دادن فهرست کلاس‌ها [C1, C2, ... , CN].

سر فهرست، نخستین عنصر آن است:

head = C1

در حالی که دم باقی‌مانده‌ی فهرست است:

tail = C2 ... CN.

من همچنین از نمادگذاری زیر استفاده خواهم کرد:

C + (C1 C2 ... CN) = C C1 C2 ... CN

برای نشان دادن مجموع فهرست‌های [C] + [C1, C2, ... ,CN].

اکنون می‌توانم توضیح دهم که MRO در پایتون 2.3 چگونه کار می‌کند.

کلاس C را در یک سلسله‌مراتب ارث چندگانه در نظر بگیرید، که در آن C از کلاس‌های پایه B1، B2، ...، BN ارث می‌برد. می‌خواهیم خطی‌سازی کلاس C یعنی L[C] را محاسبه کنیم. قاعده به این صورت است:

خطی‌سازی C، حاصل‌جمع C به‌علاوه ادغام خطی‌سازی‌های والدین و فهرست والدین است.

در نمادگذاری نمادین:

L[C(B1 ... BN)] = C + merge(L[B1] ... L[BN], B1 ... BN)

به‌ویژه، اگر C کلاس object باشد که هیچ والدی ندارد، خطی‌سازی بدیهی است:

L[object] = object.

با این حال، به‌طور کلی باید ادغام را مطابق دستورالعمل زیر محاسبه کنید:

take the head of the first list, i.e L[B1][0]; if this head is not in the tail of any of the other lists, then add it to the linearization of C and remove it from the lists in the merge, otherwise look at the head of the next list and take it, if it is a good head. Then repeat the operation until all the classes are removed or it is impossible to find good heads. In this case, it is impossible to construct the merge, Python 2.3 will refuse to create the class C and will raise an exception.

این قاعده تضمین می‌کند که عملیات ادغام، ترتیب را حفظ می‌کند، اگر ترتیب قابل حفظ باشد. از سوی دیگر، اگر ترتیب قابل حفظ نباشد (مانند مثال اختلاف جدی در ترتیب که در بالا بحث شد)، آنگاه ادغام قابل محاسبه نیست.

اگر C تنها یک والد داشته باشد (وراثت تکی)، محاسبه‌ی ادغام بسیار ساده است؛ در این حالت:

L[C(B)] = C + merge(L[B],B) = C + L[B]

با این حال، در حالت وراثت چندگانه، موضوع پیچیده‌تر است و انتظار ندارم شما بتوانید قاعده را بدون چند مثال درک کنید ;-)

مثال‌ها

مثال اول. سلسله‌مراتب زیر را در نظر بگیرید:

>>> O = object
>>> class F(O): pass
>>> class E(O): pass
>>> class D(O): pass
>>> class C(D,F): pass
>>> class B(D,E): pass
>>> class A(B,C): pass

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

                          6
                         ---
Level 3                 | O |                  (کلی‌تر)
                      /  ---  \
                     /    |    \                      |
                    /     |     \                     |
                   /      |      \                    |
                  ---    ---    ---                   |
Level 2        3 | D | 4| E |  | F | 5                |
                  ---    ---    ---                   |
                   \  \ _ /       |                   |
                    \    / \ _    |                   |
                     \  /      \  |                   |
                      ---      ---                    |
Level 1            1 | B |    | C | 2                 |
                      ---      ---                    |
                        \      /                      |
                         \    /                      \ /
                           ---
Level 0                 0 | A |                (تخصصی‌تر)
                           ---

خطی‌سازی‌های O، D، E و F بدیهی هستند:

L[O] = O
L[D] = D O
L[E] = E O
L[F] = F O

خطی‌سازی B را می‌توان به‌صورت زیر محاسبه کرد:

L[B] = B + merge(DO, EO, DE)

می‌بینیم که D یک سر مناسب است، بنابراین آن را برمی‌داریم و مسئله به محاسبه‌ی merge(O,EO,E) تقلیل می‌یابد. اکنون O یک سر مناسب نیست، زیرا در دم دنباله EO قرار دارد. در این حالت، قاعده می‌گوید که باید به دنباله بعدی برویم. سپس می‌بینیم که E یک سر مناسب است؛ آن را برمی‌داریم و مسئله به محاسبه‌ی merge(O,O) تقلیل می‌یابد که O را می‌دهد. بنابراین:

L[B] =  B D E O

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

L[C] = C + merge(DO,FO,DF)
     = C + D + merge(O,FO,F)
     = C + D + F + merge(O,O)
     = C D F O

اکنون می‌توانیم محاسبه کنیم:

L[A] = A + merge(BDEO,CDFO,BC)
     = A + B + merge(DEO,CDFO,C)
     = A + B + C + merge(DEO,DFO)
     = A + B + C + D + merge(EO,FO)
     = A + B + C + D + E + merge(O,FO)
     = A + B + C + D + E + F + merge(O,O)
     = A B C D E F O

در این مثال، خطی‌سازیبه‌شکلی بسیار مناسب بر اساس سطح ارث‌بری مرتب شده است، به این معنا که سطوح پایین‌تر (یعنی کلاس‌های تخصصی‌تر) اولویت بالاتری دارند (به نمودار ارث‌بری مراجعه کنید). با این حال، این حالت کلی نیست.

محاسبه‌ی خطی‌سازی دومین مثال خود را به‌عنوان تمرینی برای خواننده باقی می‌گذارم:

>>> O = object
>>> class F(O): pass
>>> class E(O): pass
>>> class D(O): pass
>>> class C(D,F): pass
>>> class B(E,D): pass
>>> class A(B,C): pass

تنها تفاوت با مثال پیشین، تغییر B(D,E) --> B(E,D) است؛ با این حال حتی چنین تغییر کوچکی نیز به‌طور کامل ترتیب سلسله‌مراتب را تغییر می‌دهد:

                           6
                          ---
Level 3                  | O |
                       /  ---  \
                      /    |    \
                     /     |     \
                    /      |      \
                  ---     ---    ---
Level 2        2 | E | 4 | D |  | F | 5
                  ---     ---    ---
                   \      / \     /
                    \    /   \   /
                     \  /     \ /
                      ---     ---
Level 1            1 | B |   | C | 3
                      ---     ---
                       \       /
                        \     /
                          ---
Level 0                0 | A |
                          ---

توجه داشته باشید که کلاس E، که در سطح دوم سلسله‌مراتب قرار دارد، بر کلاس C، که در سطح اول سلسله‌مراتب قرار دارد، مقدم است؛ یعنی E حتی اگر در سطح بالاتری باشد، تخصصی‌تر از C است.

یک برنامه‌نویس تنبل می‌تواند MRO را مستقیماً از Python 2.2 به دست آورد، زیرا در این حالت با خطی‌سازی Python 2.3 منطبق است. کافی است متد mro() کلاس A را فراخوانی کنید:

>>> A.mro()
[<class 'A'>, <class 'B'>, <class 'E'>,
<class 'C'>, <class 'D'>, <class 'F'>,
<class 'object'>]

در نهایت، اجازه دهید مثال مطرح‌شده در بخش نخست را که شامل یک اختلاف جدی در ترتیب است، در نظر بگیریم. در این حالت، محاسبه‌ی خطی‌سازی‌های O، X، Y، A و B ساده است:

L[O] = 0
L[X] = X O
L[Y] = Y O
L[A] = A X Y O
L[B] = B Y X O

با این حال، محاسبه‌ی خطی‌سازیبرای کلاس C که از A و B ارث‌بری می‌کند، غیرممکن است:

L[C] = C + merge(AXYO, BYXO, AB)
     = C + A + merge(XYO, BYXO, B)
     = C + A + B + merge(XYO, YXO)

در این نقطه نمی‌توانیم فهرست‌های XYO و YXO را ادغام کنیم، زیرا X در دم YXO قرار دارد در حالی که Y در دم XYO قرار دارد: بنابراین هیچ سر مناسبی وجود ندارد و الگوریتم C3 متوقف می‌شود. پایتون 2.3 خطایی پرتاب می‌کند و از ایجاد کلاس C سرباز می‌زند.

ترتیب‌های نامعتبر حل متد

یک MRO زمانی بد است که ویژگی‌های بنیادی مانند ترتیب تقدم محلی و یک‌نوایی را نقض کند. در این بخش، نشان خواهم داد که هم MRO برای کلاس‌های کلاسیک و هم MRO برای کلاس‌های به‌سبک جدید در Python 2.2 بد هستند.

شروع با ترتیب اولویت محلی آسان‌تر است. مثال زیر را در نظر بگیرید:

>>> F=type('Food',(),{'remember2buy':'spam'})
>>> E=type('Eggs',(F,),{'remember2buy':'eggs'})
>>> G=type('GoodFood',(F,E),{}) # under Python 2.3 this is an error!

با نمودار وراثت

             O
             |
(خرید اسپم)  F
             | \
             | E   (خرید تخم‌مرغ)
             | /
             G

      (خرید تخم‌مرغ یا اسپم؟)

می‌بینیم که کلاس G از F و E ارث می‌برد، به‌طوری که F قبل از E قرار دارد: بنابراین انتظار داریم ویژگی G.remember2buy از F.remember2buy به‌ارث برده شود و نه از E.remember2buy: با این حال Python 2.2 می‌دهد

>>> G.remember2buy
'eggs'

این نقض ترتیب تقدم محلی است، زیرا ترتیب در فهرست تقدم محلی، یعنی فهرست والدین G، در خطی‌سازی G در پایتون 2.2 حفظ نمی‌شود:

L[G,P22]= G E F object   # F *follows* E

می‌توان استدلال کرد که دلیل اینکه F در خطی‌سازیپایتون 2.2 پس از E قرار می‌گیرد، این است که F نسبت به E کمتر تخصصی است، زیرا F ابرکلاس E است؛ با این حال، نقض ترتیب تقدم محلی کاملاً غیرشهودی و مستعد خطا است. این موضوع به‌ویژه از آن رو درست است که با کلاس‌های قدیمی‌سبک متفاوت است:

>>> class F: remember2buy='spam'
>>> class E(F): remember2buy='eggs'
>>> class G(F,E): pass
>>> G.remember2buy
'spam'

در این حالت، MRO برابر GFEF است و ترتیب اولویت محلی حفظ می‌شود.

به‌عنوان یک قاعده‌ی کلی، باید از سلسله‌مراتب‌هایی مانند مورد قبلی اجتناب کرد، زیرا مشخص نیست که آیا F باید E را بازنویسی کند یا برعکس. پایتون 2.3 این ابهام را با پرتاب یک استثنا هنگام ایجاد کلاس G حل می‌کند و عملاً برنامه‌نویس را از تولید سلسله‌مراتب‌های مبهم باز می‌دارد. دلیل آن این است که الگوریتم C3 هنگام ادغام (merge) شکست می‌خورد:

merge(FO,EFO,FE)

نمی‌تواند محاسبه شود، زیرا F در دم EFO و E در دم FE قرار دارد.

راه‌حل واقعی این است که یک سلسله‌مراتب غیرمبهم طراحی کنید، یعنی G را از E و F (خاص‌تر در ابتدا) مشتق کنید، نه از F و E؛ در این حالت MRO بدون هیچ تردیدی GEF خواهد بود.

           O
           |
           F (spam)
         / |
(eggs)   E |
         \ |
           G
             (eggs, no doubt)

پایتون 2.3 برنامه‌نویس را وادار می‌کند که سلسله‌مراتب‌های خوبی بنویسد (یا دست‌کم، سلسله‌مراتب‌های کمتر مستعد خطا).

در نکته‌ای مرتبط، اجازه دهید اشاره کنم که الگوریتم پایتون 2.3 به اندازه کافی هوشمند است تا اشتباهات آشکار را، مانند تکرار کلاس‌ها در فهرست والدین، تشخیص دهد:

>>> class A(object): pass
>>> class C(A,A): pass # error
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: duplicate base class A

پایتون 2.2 (هم برای کلاس‌های کلاسیک و هم برای کلاس‌های سبک جدید) در این موقعیت، هیچ استثنایی را پرتاب نمی‌کرد.

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

  1. با وجود این نام، MRO ترتیب حل ویژگی‌ها را تعیین می‌کند، نه فقط متدها؛

  2. غذای پیش‌فرض پایتونیست‌ها اسپم است! (اما شما از قبل این موضوع را می‌دانستید ;-)

پس از بحث درباره‌ی موضوع ترتیب تقدم محلی، اکنون موضوع یکنواختی را بررسی می‌کنم. هدف من این است که نشان دهم نه MRO کلاس‌های کلاسیک یکنواخت است و نه MRO کلاس‌های سبک جدید Python 2.2.

اثبات غیریک‌نواخت بودن ترتیب حل متد (MRO) برای کلاس‌های کلاسیک نسبتاً بدیهی است؛ کافی است به نمودار الماسی نگاه کنید:

   C
  / \
 /   \
A     B
 \   /
  \ /
   D

به‌راحتی می‌توان ناسازگاری را تشخیص داد:

L[B,P21] = B C        # B precedes C : B's methods win
L[D,P21] = D A C B C  # B follows C  : C's methods win!

از سوی دیگر، هیچ مشکلی با MROهای پایتون 2.2 و 2.3 وجود ندارد، آن‌ها هر دو را ارائه می‌دهند:

L[D] = D A B C

گیدو در مقاله‌ی خود [3] اشاره می‌کند که MRO کلاسیک در عمل آن‌قدرها هم بد نیست، زیرا معمولاً می‌توان در کلاس‌های کلاسیک از الماس‌ها اجتناب کرد. اما همه‌ی کلاس‌های جدید از object ارث می‌برند، بنابراین الماس‌ها اجتناب‌ناپذیرند و ناهماهنگی‌ها در هر گراف وراثت چندگانه آشکار می‌شوند.

MRO در پایتون 2.2 نقض یکنواختی را دشوار می‌سازد، اما آن را غیرممکن نمی‌کند. مثال زیر، که در اصل توسط Samuele Pedroni ارائه شده است، نشان می‌دهد که MRO در پایتون 2.2 یکنواخت نیست:

>>> class A(object): pass
>>> class B(object): pass
>>> class C(object): pass
>>> class D(object): pass
>>> class E(object): pass
>>> class K1(A,B,C): pass
>>> class K2(D,B,E): pass
>>> class K3(D,A):   pass
>>> class Z(K1,K2,K3): pass

در اینجا خطی‌سازی‌ها بر اساس C3 MRO آمده‌اند (شما باید این خطی‌سازی‌ها را به‌عنوان تمرین صحت‌سنجی کنید و نمودار وراثت را رسم کنید ;-)

L[A] = A O
L[B] = B O
L[C] = C O
L[D] = D O
L[E] = E O
L[K1]= K1 A B C O
L[K2]= K2 D B E O
L[K3]= K3 D A O
L[Z] = Z K1 K2 K3 D A B C E O

پایتون 2.2 دقیقاً همان خطی‌سازی‌ها را برای A، B، C، D، E، K1، K2 و K3 ارائه می‌دهد، اما خطی‌سازی متفاوتی برای Z:

L[Z,P22] = Z K1 K3 A K2 D B C E O

واضح است که این خطی‌سازی نادرست است، زیرا A پیش از D می‌آید، در حالی که در خطی‌سازی K3، A پس از D می‌آید. به بیان دیگر، در K3، متدهای مشتق‌شده از D، متدهای مشتق‌شده از A را بازتعریف می‌کنند، اما در Z، که همچنان زیرکلاسی از K3 است، متدهای مشتق‌شده از A، متدهای مشتق‌شده از D را بازتعریف می‌کنند! این نقض یکنواختی (monotonicity) است. علاوه بر این، خطی‌سازی Z در پایتون 2.2 نیز با ترتیب تقدم محلی (local precedence ordering) ناسازگار است، زیرا فهرست تقدم محلی کلاس Z [K1, K2, K3] است (K2 پیش از K3 می‌آید)، در حالی که در خطی‌سازی Z، K2 پس از K3 می‌آید. این مشکلات توضیح می‌دهند که چرا قانون 2.2 به نفع قانون C3 کنار گذاشته شده است.

پایان

این بخش برای خواننده‌ی عجولی است که همه‌ی بخش‌های پیشین را رد کرده و بلافاصله به انتها پریده است. این بخش برای برنامه‌نویس تنبل نیز هست که نمی‌خواست مغز خود را ورزش دهد. سرانجام، این بخش برای برنامه‌نویسی است که کمی غرور دارد، وگرنه او مقاله‌ای درباره‌ی ترتیب حل متد C3 در سلسله‌مراتب‌های ارث‌بری چندگانه نمی‌خواند ;-) این سه فضیلت در کنار هم (و نه به‌طور جداگانه) شایسته‌ی یک جایزه هستند: جایزه یک اسکریپت کوتاه پایتون 2.2 است که به شما امکان می‌دهد MRO 2.3 را بدون خطر برای مغزتان محاسبه کنید. به‌سادگی خط آخر را تغییر دهید تا با مثال‌های مختلفی که در این مقاله به آن‌ها پرداخته‌ام بازی کنید.:

#<mro.py>

"""C3 algorithm by Samuele Pedroni (with readability enhanced by me)."""

class __metaclass__(type):
    "All classes are metamagically modified to be nicely printed"
    __repr__ = lambda cls: cls.__name__

class ex_2:
    "Serious order disagreement" #From Guido
    class O: pass
    class X(O): pass
    class Y(O): pass
    class A(X,Y): pass
    class B(Y,X): pass
    try:
        class Z(A,B): pass #creates Z(A,B) in Python 2.2
    except TypeError:
        pass # Z(A,B) cannot be created in Python 2.3

class ex_5:
    "My first example"
    class O: pass
    class F(O): pass
    class E(O): pass
    class D(O): pass
    class C(D,F): pass
    class B(D,E): pass
    class A(B,C): pass

class ex_6:
    "My second example"
    class O: pass
    class F(O): pass
    class E(O): pass
    class D(O): pass
    class C(D,F): pass
    class B(E,D): pass
    class A(B,C): pass

class ex_9:
    "Difference between Python 2.2 MRO and C3" #From Samuele
    class O: pass
    class A(O): pass
    class B(O): pass
    class C(O): pass
    class D(O): pass
    class E(O): pass
    class K1(A,B,C): pass
    class K2(D,B,E): pass
    class K3(D,A): pass
    class Z(K1,K2,K3): pass

def merge(seqs):
    print '\n\nCPL[%s]=%s' % (seqs[0][0],seqs),
    res = []; i=0
    while 1:
      nonemptyseqs=[seq for seq in seqs if seq]
      if not nonemptyseqs: return res
      i+=1; print '\n',i,'round: candidates...',
      for seq in nonemptyseqs: # find merge candidates among seq heads
          cand = seq[0]; print ' ',cand,
          nothead=[s for s in nonemptyseqs if cand in s[1:]]
          if nothead: cand=None #reject candidate
          else: break
      if not cand: raise "Inconsistent hierarchy"
      res.append(cand)
      for seq in nonemptyseqs: # remove cand
          if seq[0] == cand: del seq[0]

def mro(C):
    "Compute the class precedence list (mro) according to C3"
    return merge([[C]]+map(mro,C.__bases__)+[list(C.__bases__)])

def print_mro(C):
    print '\nMRO[%s]=%s' % (C,mro(C))
    print '\nP22 MRO[%s]=%s' % (C,C.mro())

print_mro(ex_9.Z)

#</mro.py>

همین و بس، دوستان،

لذت ببرید!

منابع