استعرضنا في المقال السابق مفهوم الوراثة في البرامج كائنية التوجه، سنتابع في هذا المقال الموضوع ذاته إذ سنستعرض بعض التوابع المهمة بهذا الخصوص، إضافةً إلى مناقشة مفهوم الوراثة المتعددة الموجودة في لغة بايثون.
الدالتين isinstance() و isssubclass()
يمكننا تمرير الكائن عندما نريد معرفة نوعه إلى الدالة type()
المضمنة كما تحدثنا سابقًا، ولكن إذا أردنا التحقق من نوع كائن فيُفضل استخدام الدالة المبنية مسبقًا isinstance()
، التي تعيد الدالة قيمة True
إذا كان الكائن في الصنف المعطى أو صنفها الفرعي.
اكتب ما يلي في الصدفة التفاعلية:
>>> class ParentClass: ... pass ... >>> class ChildClass(ParentClass): ... pass ... >>> parent = ParentClass() # إنشاء كائن ParentClass >>> child = ChildClass() # إنشاء كائن ChildClass >>> isinstance(parent, ParentClass) True >>> isinstance(parent, ChildClass) False 1 >>> isinstance(child, ChildClass) True 2 >>> isinstance(child, ParentClass) True
لاحظ أن isinstance()
تشير إلى أن كائن ChildClass
في child
هو نسخةٌ من ChildClass
(السطر ذو الرقم 1) ونسخةٌ من ParentClass
(السطر ذو الرقم 2)، وهذا منطقي لأن كائن ChildClass
له علاقة من نوع "is a" مع نوع كائن ParentClass
، أي أنه نوع من هذا الكائن.
يمكن أيضًا تمرير صف tuple من كائنات الأصناف مثل وسيط ثانٍ لمعرفة إذا كان الوسيط الأول هو واحد من الأصناف الموجودة في الصف:
# تُعيد True إذا كانت القيمة 42 عددًا صحيحًا أو سلسلة نصية أو قيمة بوليانية >>> isinstance(42, (int, str, bool)) True if 42 is an int, str, or bool. True
الدالة المضمنة الأخرى issubclass()
أقل شيوعًا من isinstance()
ويمكنها التعرُّف ما إذا كان كائن الصنف الممر إلى الوسيط الأول هو صنف فرعي (أو نفس الصنف) لكائن الصنف المرر إلى الوسيط الثاني:
>>> issubclass(ChildClass, ParentClass) # ChildClass صنف فرعي من ParentClass True >>> issubclass(ChildClass, str) # ChildClass ليس صنفًا فرعيًا من من str False >>> issubclass(ChildClass, ChildClass) # ChildClass هو ChildClass True
يمكنك تمرير صف من كائنات الصنف بمثابة وسيط ثاني إلى issubclass()
كما هو الحال مع Isinstance()
، وذلك لرؤية ما إذا كان الوسيط الأول هو صنف فرعي لأي من الأصناف في الصف. الفارق الأساسي بين isinstance()
و issubclass()
هو أن issubclass()
تمرر كائني صنف و isinstance()
تمرر كائن وكائن صنف.
توابع الصنف
ترتبط توابع الصنف مع صنف أكثر مقارنةً بالكائنات المفردة مثل التوابع العادية. يمكنك ملاحظة تابع الصنف في الشيفرة عندما ترى علامتين، هما: المزخرف @classmethod
قبل تعليمة التابع def
، واستخدام cls
معاملًا أولًا كما في المثال التالي:
class ExampleClass: def exampleRegularMethod(self): print('This is a regular method.') @classmethod def exampleClassMethod(cls): print('This is a class method.') # استدعاء تابع الصنف دون إنشاء نسخة كائن ExampleClass.exampleClassMethod() obj = ExampleClass() # بالنظر إلى السطر السابق، السطرين التاليين متكافئين obj.exampleClassMethod() obj.__class__.exampleClassMethod()
يعمل المعامل cls
مثل self
ولكن self
تشير إلى كائن بينما يشير المعامل cls
إلى صنف الكائن، هذا يعني أن الشيفرة في تابع الصنف لا يمكنها الوصول إلى خاصيات الكائن المفردة أو استدعاء توابع الكائن العادية. تستدعي توابع الأصناف توابع أصناف أخرى وتستطيع الوصول إلى سمات الصنف. نستخدم الاسم cls
لأن class
هي كلمة مفتاحية في بايثون وكما هو الحال مع باقي الكلمات المفتاحية مثل if
و while
و import
، فنحن لا نستطيع استخدامها في أسماء المعاملات، ونستدعي غالبًا سمات الأصناف من خلال كائن الصنف، مثل ExampleClass.exampleClassMethod()
، إلا أنه يمكننا استدعاؤهم من خلال أي كائن من الصنف كما في obj.exampleClassMethod()
.
لا تُستخدم توابع الصنف عمومًا وأكثر الحالات استخدامًا هي لتوفير بديل عن توابع الباني constructorإضافةً للتابع __init__()
. على سبيل المثال، ماذا لو كانت دالة الباني تقبل سلسةً نصيةً من البيانات يحتاجها الكائن الجديد أو سلسلة نصية لاسم ملف يحتوي البيانات التي يحتاجها الكائن الجديد؟ لا نحتاج إلى قائمة معاملات التابع ()__init__
لأنها ستكون طويلة ومعقدة، ونستخدم تابع دالة يعيد كائن جديد بدلًا من ذلك.
مثلًا، لننشئ صنف AsciiArt
(مررنا عليه سابقًا) الذي يستخدم محارف نصية ليشكل صورة:
class AsciiArt: def __init__(self, characters): self._characters = characters @classmethod def fromFile(cls, filename): with open(filename) as fileObj: characters = fileObj.read() return cls(characters) def display(self): print(self._characters) # Other AsciiArt methods would go here... face1 = AsciiArt(' _______\n' + '| . . |\n' + '| \\___/ |\n' + '|_______|') face1.display() face2 = AsciiArt.fromFile('face.txt') face2.display()
لدى صنف AsciiArt
تابع ()__init__
الذي يمكن أن يمرر محارف النص الصورة مثل سلسلة نصية. لديه أيضًا تابع صنف fromFile()
الذي يمكن أن يمرر السلسلة النصية لاسم الملف مثل ملف نصي يحتوي فن آسكي ASCII art. يُنشئ كلا التابعين كائنات AsciiArt
.
نفذ البرنامج وسيكون هناك ملف face.txt يحتوي على وجه فن آسكي ASCII، ليكون الخرج على النحو التالي:
_______ | . . | | \___/ | |_______| _______ | . . | | \___/ | |_______|
يجعل تابع الصنف fromFile()
الشيفرة الخاصة بك سهلة القراءة مقارنةً بجعل ()__init__
يفعل كل شيء.
ميزة أُخرى لتابع الصنف هو أن صنف فرعي من AsciiArt
يمكن أن يرث تابع fromFile()
الخاص (وإعادة تعريفه إذا لزم)، وهذا هو سبب استدعاء cls(characters)
في تابع صنف AsciiArt
بدلًا من AsciiArt(characters)
. يعمل استدعاء ()cls
أيضًا في الأصناف الفرعية للصنف AsciiArt
دون تعديل لأن صنف AsciiArt
ليس متوفرًا في التابع، ولكن استدعاء AsciiArt()
يستدعي ()__init__
الخاص بصنف AsciiArt
بدلًا من ()__init__
الخاص بالصنف الفرعي. يمكنك التفكير في cls
على أنها "كائن يمثل هذا الصنف".
خذ بالحسبان أنه يجب أن تستخدم التوابع العادية معامل self
في مكان ما في الشيفرة الخاصة بهم، ويجب على تابع الصنف دائمًا استخدام المعامل cls
. إذا لم يستخدم أبدًا تابع الصنف المعامل cls
، فهذه إشارة أن تابع الصنف الخاص بك يجب أن يكون تابعًا عاديًا.
سمات الأصناف
سمة الصنف هي متغير ينتمي إلى صنف بدلًا من كائن. ننشئ سمة صنف داخل الصنف ولكن خارج كل التوابع كما أنشأنا متغيرات عامة في ملف ".py" ولكن خارج كل الدوال. هذا مثال عن سمة صنف اسمها count
التي تحصي عدد كائنات CreatCounter
المُنشأة.
class CreateCounter: count = 0 # هذه سمة لصنف def __init__(self): CreateCounter.count += 1 print('Objects created:', CreateCounter.count) # تطبع 0 a = CreateCounter() b = CreateCounter() c = CreateCounter() print('Objects created:', CreateCounter.count) # تطبع 3
لدى صنف CreatCounter
سمة صنف واحدة اسمها count
. كل كائنات CreatCounter
لديهم هذه السمة بدلًا من أن يكون لكل منهم سمات count
منفصلة. لهذا يعد السطر CreateCounter.count += 1
في دالة الباني كل كائن CreatCounter
مُنشأ.
عندما تنفذ البرنامج، يكون الخرج على النحو التالي.
Objects created: 0 Objects created: 3
نادرًا ما نستخدم سمات الصنف حتى هذا المثال "عد كم كائن CreatCounter
مُنشأ" يمكن عمله باستخدام متغير عام بدلًا من سمة صنف.
التوابع الساكنة
لا يحتوي التابع الساكن معاملي self
و cls
، فالتوابع الساكنة هي دوال لأنها لا تستطيع الوصول إلى سمات أو توابع الصنف وكائناتها. نادرًا ما ستحتاج لاستخدام التوابع الساكنة في بايثون، وإذا قررت إنشاء واحد ننصح جدًا بإنشاء تابع عادي بدلًا عنه.
نعرّف التوابع الساكنة بوضع مزخرف @staticmethod
قبل تعليمة def
الخاصة بهم. هذا مثال عن تابع ساكن:
class ExampleClassWithStaticMethod: @staticmethod def sayHello(): print('Hello!') # لم يُنشأ أي كائن، فاسم الصنف يسبق sayHello() Note that no object is created, the class name precedes sayHello(): ExampleClassWithStaticMethod.sayHello()
لا يوجد فرق تقريبًا بين التابع الساكن sayHello()
في صنف ExampleClassWithStaticMethod
والدالة sayHello()
. ربما تفضل بالواقع استخدام دالة لأنك تستطيع استدعائها دون الدخول إلى اسم الصنف مسبقًا.
التوابع الساكنة شائعة في لغات برمجة أخرى ليس لديها ميزات لغة بايثون المرنة. تضمين التوابع الساكنة inclusion of static methods في بايثون هو لمحاكاة اللغات الأخرى ولا يقدم قيمةً عملية.
متى تستخدم الأصناف والميزات كائنية التوجه الساكنة؟
نادرًا ما تحتاج لاستخدام توابع الصنف وسمات الصنف والتوابع الساكنة، فهم عرضةً للاستخدام الزائد إذا كنت تعتقد بالتساؤل التالي: " لماذا لا استخدم الدوال أو المتغيرات العامة بدلًا عن ذلك؟" هذا تلميح لعدم استخدام توابع الأصناف أو سمات الأصناف أو التوابع الساكنة.
السبب الوحيد الذي جعلنا نناقش هذه المفاهيم في سلسلة المقالات متوسطة المستوى هو للتعرف عليهم عندما تراهم في الشيفرة، ولكن لا يشجع كثير من المبرمجين على استخدامهم، إذ سيكونوا مفيدين إذا أردت استخدام هيكلية خاصة بك مع مجموعة معقدة من الأصناف التي تتوقع أن تكون أصناف فرعية للمبرمجين الذين سيستخدمون الهيكلية، لكنك لا تحتاجهم عندما تكتب تطبيقات بايثون مباشرةً.
للمزيد عن هذه الميزات وعن احتياجهم أو لا، اقرأ منشور فيليب ج. ايبي Phillip J. Eby "بايثون ليس جافا" الموجود على الرابط dirtsimple.org/2004/12/python-is-not-java.html ومنشور ريان تومايكو Ryan Tomayko "مفهوم التابع الساكن" على الرابط tomayko.com/blog/2004/the-static-method-thing.
كلمات مهمة كائنية التوجه
يبدأ شرح البرمجة كائنية التوجه OOP بالكثير من المصطلحات مثل الوراثة والتغليف Encapsulation والتعددية الشكلية Polymorphism. أهمية معرفة هذه المصطلحات مبالغ فيه، ولكن يجب عليك أن يكون لديك فهم أساسي لهم، شرحنا الوراثة سابقًا، لذا سنشرح المصطلحات الباقية تاليًا، ويمكنك الاطلاع على مقال البرمجة كائنية التوجه (Object Oriented Programming) في لغة سي شارب #C على أكاديمية حسوب لمزيدٍ من المعلومات حول هذه المصطلحات.
التغليف
لدى كلمة تغليف معنيين شائعين ولكن متقاربين. التعريف الأول هو تجميع البيانات المتعلقة والشيفرة في وحدة واحدة، أي لتغلف يعني أن تضع في صندوق. هذا ما تفعله الأصناف عمومًا؛ فهي تدمج السمات والتوابع. مثلًا، يغلف صنف WizCoin
ثلاثة أعداد صحيحة لـ knuts و sickles و galleons إلى كائن WizCoin
واحد.
أما التعريف الثاني فهو تقنية لإخفاء المعلومات تسمح للكائنات بإخفاء تفاصيل تنفيذ معقدة عن كيفية عمل الكائنات. رأينا ذلك في "السمات والتوابع الخاصة"، إذ يقدم كائن BankAccount
توابع deposit()
و withdraw()
لإخفاء تفاصيل كيفية التعامل مع السمة _balance
. تعمل الدوال مثل صندوق أسود: كيفية حساب الدالة math.sqrt()
للجذر التربيعي لأي رقم مخفية، كل ما عليك معرفته هو أن تعيد الدالة الجذر التربيعي للرقم الممرر لها.
التعددية الشكلية polymorphism
تسمح التعددية الشكلية بمعالجة كائنات من نوع ما على أنها كائنات من نوع آخر، فمثلًا تعيد الدالة ()len
طول الوسيط الممرر إليها، ويمكنك تمرير سلسلة نصية إلى هذه الدالة لمعرفة عدد المحارف المكونة منه، وكذلك يمكن قائمة list أو قاموس dictionary لمعرفة عدد العناصر، أو عدد أزواج مفتاح-قيمة key-value على الترتيب. يدعى نموذج التعددية الشكلية هذا باسم الدوال المعممة generic functions أو التعددية الشكلية القياسية parametric polymorphism لأنها تعالج كائنات ذات أنواع مختلفة.
يمكنك الاطلاع على مقال مفهوم البرمجة المعممة Generic Programming على أكاديمية حسوب لمزيدٍ من المعلومات عن البرمجة المعممة. يمكن الإشارة إلى التعددية الشكلية بمصطلح التعددية الشكلية الخاصة ad hoc polymorphism أو زيادة تحميل للعامل operator overloading، إذ يمكن أن يأخذ المعامل (مثل +
أو *
) سلوكًا مختلفًا اعتمادًا على نوع الكائنات التي تُجرى عليها العملية؛ فمثلًا يجري المعامل +
عملية الجمع الحسابي عندما تُجرى العملية على عددين صحيحين أو عشريين، لكنها توصل السلاسل النصية في حال كانت العملية على سلسلتين بدلًا من عددين.
لماذا لا نستخدم الوراثة؟
من السهل زيادة تعقيد الأصناف باستخدام الوراثة. كما يشير لوتشيانو رامالهو Luciano Ramalho : "وضع الكائنات في هرمية يرضي حس الترتيب لدينا، ولكن المبرمجين يفعلونها للتسلية". ننشئ أصناف وأصناف فرعية وأصناف تحت فرعية عندما تكون الحاجة لصنف واحد أو اثنين في الوحدة لتفي بالغرض، ولكن تذكر حكمة بايثون الأهم التي ناقشناها سابقًا؛ الحل البسيط أفضل من الحل المعقد.
يسمح استخدام البرمجة كائنية التوجه OOP بتنظيم الشيفرة الخاصة بك إلى وحدات units (في هذه الحالة أصناف) سهلة التعامل بدلًا من ملف ".py" واحد يحتوي مئات التوابع المعرفة بدون ترتيب معين. تفيد الوراثة إذا كان لديك عدة دوال تعمل في نفس القاموس أو هيكل قائمة البيانات؛ ففي هذه الحالة من المفيد ترتيبهم في صنف.
هناك بعض الأمثلة لعدم إنشاء الأصناف أو استخدام الوراثة:
-
إذا كان الصنف يتألف من توابع لا تستخدم المعاملين
self
وcls
، احذف الأصناف واستخدم الدوال بدلًا من التوابع. - إذا أنشأت أب بصنف ابن واحد ولم تُنشئ كائنات من الصنف الأب، يمكنك جمعهم بصنف واحد.
- إذا أنشأت أكثر من ثلاثة أو أربعة مستويات من الأصناف الفرعية، ربما تكون قد استخدمت الوراثة على نحوٍ زائد، اجمع هذه الأصناف الفرعية إلى أصناف أقل.
كما توضّح سابقًا في نسختي برنامج إكس أو tic-tac-toe (مع برمجة كائنية التوجه وبدونها)، من الممكن الحصول على برنامج يعمل على نحوٍ سليم وبدون أخطاء دون استخدام الأصناف. لست بحاجة لتصميم برنامج مثل شبكة معقدة من الأصناف، إذ أن الحل البسيط أفضل من الحل المعقد الذي لا يعمل. يتحدث جول سبلوسكي Joel Spolsky عن ذلك في منشوره "لا تدع المصممين رواد الفضاء أن يخيفوك" الموجود على الرابط joelonsoftware.com/2001/04/21/dont-let-architecture-astronauts-scare-you.
يجب أن تعرف الآن كيفية عمل مفاهيم البرمجة كائنية التوجه مثل الوراثة، لأنها تساعدك على تنظيم الشيفرة الخاصة بك وجعل التطوير ومعالجة الأخطاء أسهل. تتمتع لغة بايثون بالمرونة، فهي تقدم لك ميزات برمجة كائنية التوجه، لكنها أبضًا لا تطلب منك استخدامها عندما لا تناسب احتياجات البرنامج الخاص بك.
الوراثة المتعددة
في العديد من لغات البرمجة يكون الصنف أب واحد فقط، ولكن بايثون تدعم آباء متعددين عن طريق تقديم ميزة تدعى الوراثة المتعددة multiple inheritance. مثلًا، يمكننا الحصول على صنف Airplane
مع تابع flyInTheAir()
وصنف Ship
مع تابع floatOnWater()
، ويمكننا إنشاء صنف FlyingBoat
يرث كلًا من Airplane
و Ship
عن طريق تحديدهما في تعليمة class
مفصولين بفواصل.
افتح ملف جديد في محرر النصوص واحفظ التالي flayingboat.py:
class Airplane: def flyInTheAir(self): print('Flying...') class Ship: def floatOnWater(self): print('Floating...') class FlyingBoat(Airplane, Ship): pass
سيرث الكائن المُنشأ التابعين flyInTheAir()
و floatOnWater()
كما سنرى في الصدفة التفاعلية:
>>> from flyingboat import * >>> seaDuck = FlyingBoat() >>> seaDuck.flyInTheAir() Flying... >>> seaDuck.floatOnWater() Floating...
الوراثة المتعددة مفهوم بسيط طالما كانت أسماء توابع الأصناف مميزة ولا تتقاطع، وتسمى هذه الأصناف mixins (هذا مصطلح عام لهذا النوع من الأصناف، إذ لا يوجد في بايثون كلمة mixin
مفتاحية)، ولكن ماذا سيحصل إذا ورثنا عدة أصناف معقدة تتشارك بأسماء التوابع؟
مثلًا تذكر أصناف لوحة إكس أو MiniBoard
و HintTTTBoard
سابقًا، ماذا لو أردنا صنف يظهر لوحة إكس أو مصغرة مع تقديم بعض النصائح؟ يمكننا إعادة استخدام هذه الأصناف الموجودة باستخدام الوراثة المتعددة. ضِف التالي إلى نهاية ملف tictactoe_oop.py ولكن قبل تعليمة if
التي تستدعي الدالة main()
:
class HybridBoard(HintBoard, MiniBoard): pass
لا يوجد شيء في هذا الصنف، إذ يُعيد استخدام الشيفرة عن طريق وراثة HintBoard
و MiniBoard
. عدّل الشيفرة في الدالة main()
لتُنشئ كائن HybridBoard
:
gameBoard = HybridBoard() # إنشاء كائن TTT للّوحة
لدى كلا الصنفين الأب MiniBoard
و HintBoard
تابع اسمه getBoardStr()
فما الذي ترثه HybridBoard
؟ عندما تنفذ البرنامج سيظهر الخرج لوحة إكس أو مصغرة تحتوي على بعض التلميحات:
--snip-- X.. 123 .O. 456 X.. 789 X can win in one more move.
يبدو أن بايثون دمجت سحريًا تابع getBoardStr()
الخاص بصنف MiniBoard
و getBoardStr()
الخاص بصنف HintBoard
، وهذا ممكن لأننا كتبنا التابعين بشكل يمكّنهما العمل مع بعضهما. إذا بدلت ترتيب الأصناف في تعليمة class
في صنف HybridBoard
لتصبح على النحو التالي:
class HybridBoard(MiniBoard, HintBoard):
فستخسر التلميحات كليًا:
--snip-- X.. 123 .O. 456 X.. 789
لتفهم لماذا حصل ذلك يجب عليك فهم ترتيب استبيان التوابع method resolution order -أو اختصارًا MRO- الخاص ببايثون وكيفية عمل دالة super()
.
ترتيب استبيان التابع
لدى برنامج إكس أو الخاص بنا أربعة أصناف لتمثيل الألواح، ثلاثة معرفة بتابع getBoardStr()
وواحدة بتابع getBoardStr()
موروث كما في الشكل 2
[الشكل 2: الأصناف الأربعة في برنامج لوحات إكس أو]
عندما نستدعي getBoardStr()
على الكائن HybridBoard
، يعرف بايثون أن الصنف HybridBoard
ليس لديه تابع بذلك الاسم لذا تفحص أصناف الأب، ولكن لدى الصنف هذا صنفين أب وكلاهما لديه تابع getBoardStr()
، أي منها يُستدعى؟
يمكننا معرفة ذلك من التحقق من ترتيب استبيان التابع MRO الخاص بصنف HybridBoard
وهي القائمة المرتبة من الأصناف التي يتحقق منها بايثون عند وراثة التوابع، أو عندما يستدعي التابع دالة super()
. يمكنك رؤية ترتيب استبيان التابع للصنف HybridBoard
عن طريق استدعاء mro()
في الصدفة التفاعلية:
>>> from tictactoe_oop import * >>> HybridBoard.mro() [<class 'tictactoe_oop.HybridBoard'>, <class 'tictactoe_oop.HintBoard'>, <class 'tictactoe_oop.MiniBoard'>, <class 'tictactoe_oop.TTTBoard'>, <class 'object'>]
يمكنك من خلال القيمة المُعادة رؤية أنه عندما يُستدعى التابع على HybridBoard
، يتحقق بايثون من صنف HybridBoard
؛ فإذا لم يكن موجودًا، يتحقق بايثون من صنف HintBoard
وبعدها من صنف MiniBoard
وأخيرًا من صنف TTTBoard
. في آخر كل قائمة ترتيب استبيان الدوال MRO هناك صنف object
مضمّن يمثل الصنف الأب لكل الأصناف في بايثون.
معرفة ترتيب استبيان الدوال MRO من أجل وراثة واحدة أمر سهل؛ فقط اصنع سلسلة chain من أصناف الأب، أما بالنسبة للوراثة المتعددة سيكون الأمر أصعب. يتبع ترتيب استبيان الدوال MRO الخاص ببايثون خوارزمية C3 -التي تقع تفاصيل مناقشتها خارج سياق موضوعنا- ولكنك تستطيع تحديد ترتيب استبيان الدوال MRO بتذكر قاعدتين:
- يتحقق بايثون من الأصناف الابن قبل أصناف الأب.
-
يتحقق من الأصناف الموروثة في القائمة من اليسار إلى اليمين في تعليمة
class
.
إذا استدعينا getBoardStr()
على كائن HybridBoard
، يتحقق بايثون من الصنف HybridBoard
أولًا وبعدها ونظرًا لكون أصناف الأب من اليسار إلى اليمين هي HintBoard
و MiniBorad
، يتحقق بايثون من HintBoard
. لدى الصنف الأب هذا تابع getBoardStr()
لذا يرثها HybridBorad
ويستدعيها.
لا ينتهي الأمر هنا، يستدعي التابع super().getBoardStr()
، إذ أن كلمة "super" هي كلمة مضللة نوعًا ما لدالة super()
الخاصة ببايثون، لأنها لا تعيد الصنف الأب ولكن الصنف الذي يليها في ترتيب استبيان التوابع MRO، وهذا يعني عندما نستدعي getBoardStr()
على الكائن HybridBoard
، يكون الصنف التالي في ترتيب استبيان التوابع MRO بعد HintBoard
هو MiniBoard
وليس الصنف الأب TTTBoard
، لذا استدعاء super().getBoardStr()
يستدعي تابع getBoardStr()
لصنف MiniBoard
الذي يعيد سلسلة نصية للوحة إكس أو المصغرة. تعلّق الشيفرة المتبقية في getBoardStr()
الخاصة بصنف HintBoard
بعد استدعاء super()
نص التلميح لهذه السلسة النصية.
إذا غيرنا تعليمة class
في صنف HybridBoard
لتضع MiniBoard
أولًا و HintBoard
ثانيًا، سيضع ترتيب استبيان التوابع MRO الصنف MiniBoard
قبل الصنف HintBoard
، ما يعني أن HybridBorad
ترث getBoardStr()
من MiniBoard
التي لا تحتوي استدعاء super()
. هذا الترتيب هو الذي سبّب الخطأ الذي جعل لوحة إكس أو المصغرة تظهر بدون تلميحات؛ فبدون استدعاء super()
تابع getBoardStr()
الخاص بصنف MiniBoard
لا يستدعي تابع getBoardStr()
الخاص بصنف HintBoard
.
تسمح لك الوراثة المتعددة في إنشاء وظائف كثيرة في كمية قليلة من الشيفرة، لكنها تقود إلى شيفرة معقدة وصعبة القراءة. فضّل الوراثة الواحدة أو أصناف mixin أو عدم الوراثة، هذه التقنيات غالبًا ما تكون قادرة على تنفيذ مهام البرنامج الخاص بك.
الخلاصة
كما تعيد type()
نوع الكائن المرر لها، تعيد توابع isinstace()
و issubclass()
نوع ومعلومات الوراثة عن الكائن الممرر لها.
يمكن أن تحتوي الأصناف توابع كائن وسمات، لكنها تحتوي أيضًا توابع صنف وسمات صنف وتوابع ساكنة، على الرغم من أنها نادرة الاستخدام لكن يمكنها أن تسمح بالتقنيات كائنية التوجه التي لا تستطيع المتغيرات العامة والدوال أن تقدمها.
بسمح بايثون للأصناف أن ترث من عدة آباء، على الرغم من أن ذلك ينتج شيفرة صعبة الفهم. تستطيع دالة super()
وتوابع الصنف اكتشاف كيف سترث التوابع اعتمادًا على ترتيب استبيان التوابع MRO، إذ يمكنك مشاهدة ترتيب استبيان التوابع MRO الخاص بصنف في الصدفة التفاعلية عن طريق استدعاء التابع mro()
على الصنف.
غطينا في هذا المقال والمقالات السابقة مفاهيمًا عامة في البرمجة كائنية التوجه OOP، وسنتحدث تاليًا عن تقنيات برمجة كائنية التوجه OOP خاصة ببايثون.
ترجمة -وبتصرف- لقسم من الفصل Object-Oriented Programming And Inheritance من كتاب Beyond the Basic Stuff with Python.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.