GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing python.github.io — ترتیب حل متد در پایتون 2.3 ترتیب حل متد در پایتون 2.3¶نکته این یک سند تاریخی است که بهعنوان پیوستی برای مستندات رسمی ارائه شده است. ترتیب حل متدکه در اینجا مورد بحث قرار گرفته، در Python 2.3 معرفی شد، اما همچنان در نسخههای بعدی — از جمله Python 3 — استفاده میشود. نوشتهشده توسط Michele Simionato.
سلب مسئولیت:
سپاسگزاریها:
آغاز¶
همهچیز با پیامی از 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 را درک کنید و میتوانید بهراحتی از این مقاله بگذرید. از سوی دیگر، اگر واقعاً میخواهید بدانید که وراثت چندگانه چگونه کار میکند، این مقاله برای شماست. خبر خوب این است که مسائل آنگونه که ممکن است انتظار داشته باشید، پیچیده نیستند. اجازه دهید با چند تعریف پایه آغاز کنم.
در اینجا مثالی از این موقعیت ارائه میشود. سلسلهمراتب را در نظر بگیرید >>> O = object >>> class X(O): pass >>> class Y(O): pass >>> class A(X,Y): pass >>> class B(Y,X): pass که میتوان آن را با نمودار وراثت زیر نمایش داد، که در آن کلاس
در این حالت، نمیتوان یک کلاس جدید 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] را محاسبه کنیم. قاعده به این صورت است:
در نمادگذاری نمادین: L[C(B1 ... BN)] = C + merge(L[B1] ... L[BN], B1 ... BN) بهویژه، اگر C کلاس L[object] = object. با این حال، بهطور کلی باید ادغام را مطابق دستورالعمل زیر محاسبه کنید:
این قاعده تضمین میکند که عملیات ادغام، ترتیب را حفظ میکند، اگر ترتیب قابل حفظ باشد. از سوی دیگر، اگر ترتیب قابل حفظ نباشد (مانند مثال اختلاف جدی در ترتیب که در بالا بحث شد)، آنگاه ادغام قابل محاسبه نیست. اگر 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 در این حالت میتوان نمودار وراثت را به این شکل رسم کرد:
خطیسازیهای 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 یک سر مناسب است، بنابراین آن را برمیداریم و مسئله به محاسبهی 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) است؛ با این حال حتی چنین تغییر کوچکی نیز بهطور کامل ترتیب سلسلهمراتب را تغییر میدهد:
توجه داشته باشید که کلاس E، که در سطح دوم سلسلهمراتب قرار دارد، بر کلاس C، که در سطح اول سلسلهمراتب قرار دارد، مقدم است؛ یعنی E حتی اگر در سطح بالاتری باشد، تخصصیتر از C است. یک برنامهنویس تنبل میتواند MRO را مستقیماً از Python 2.2 به دست آورد، زیرا در این حالت با خطیسازی Python 2.3 منطبق است. کافی است متد >>> A.mro() [<class 'A'>, <class 'B'>, <class 'E'>, <class 'C'>, <class 'D'>, <class 'F'>, <class 'object'>] در نهایت، اجازه دهید مثال مطرحشده در بخش نخست را که شامل یک اختلاف جدی در ترتیب است، در نظر بگیریم. در این حالت، محاسبهی خطیسازیهای O، X، Y، A و B ساده است:
با این حال، محاسبهی خطیسازیبرای کلاس 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! با نمودار وراثت
میبینیم که کلاس 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 خواهد بود.
پایتون 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 (هم برای کلاسهای کلاسیک و هم برای کلاسهای سبک جدید) در این موقعیت، هیچ استثنایی را پرتاب نمیکرد. در نهایت، مایلم به دو درسی که از این مثال آموختهایم اشاره کنم:
پس از بحث دربارهی موضوع ترتیب تقدم محلی، اکنون موضوع یکنواختی را بررسی میکنم. هدف من این است که نشان دهم نه MRO کلاسهای کلاسیک یکنواخت است و نه MRO کلاسهای سبک جدید Python 2.2. اثبات غیریکنواخت بودن ترتیب حل متد (MRO) برای کلاسهای کلاسیک نسبتاً بدیهی است؛ کافی است به نمودار الماسی نگاه کنید:
بهراحتی میتوان ناسازگاری را تشخیص داد: 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 کلاسیک در عمل آنقدرها هم بد نیست، زیرا معمولاً میتوان در کلاسهای کلاسیک از الماسها اجتناب کرد. اما همهی کلاسهای جدید از 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> همین و بس، دوستان،
منابع¶
© حق نشر 2001 Python Software Foundation.
این صفحه تحت مجوز بنیاد نرمافزار پایتون نسخه ۲ منتشر شده است. مثالها، دستورالعملها، و سایر کدهای موجود در مستندات همچنین تحت مجوز بدون بند BSD منتشر شدهاند. برای اطلاعات بیشتر به تاریخچه و مجوز مراجعه کنید. بنیاد نرمافزار پایتون یک شرکت غیرانتفاعی است. لطفاً کمک مالی کنید. آخرین بهروزرسانی در نوامبر 12, 2026 (06:19 UTC). آیا اشکالی یافتید؟ ساخته شده با Sphinx 9.1.0. |
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |