اذهب إلى المحتوى

البرمجة كائنية التوجه Object-Oriented Programming والأصناف Classes في لغة بايثون


Naser Dakhel

البرمجة كائنية التوجه - أو اختصارًا OOP- هي ميزة للغة البرمجة تسمح لك بجمع الدوال functions والمتغيرات variables معًا في أنواع بيانات data type جديدة، تسمى الأصناف classes، والتي يمكنك من خلالها إنشاء كائنات objects. يمكنك تقسيم البرنامج المترابط إلى أجزاء أصغر يسهل فهمها وتنقيح أخطائها عن طريق تنظيم الشيفرة البرمجية الخاصة بك إلى أصناف.

لا تضيف البرمجة كائنية التوجه تنظيمًا بالنسبة للبرامج الصغيرة، بل تقّدم تعقيدًا لا داعٍ له في بعض الحالات، وعلى الرغم من أن بعض اللغات، مثل جافا، تتطلب منك تنظيم كل الشيفرات البرمجية في أصناف، إلا أن ميزات البرمجة كائنية التوجه في بايثون اختيارية، إذ يمكن للمبرمجين الاستفادة من الأصناف إذا كانوا بحاجة إليها أو تجاهلها. يشير حديث مُبرمج بايثون Jack Diederich في PyCon 2012، "توقف عن كتابة الأصناف"، إلى العديد من الحالات التي يستخدم فيها المبرمجون الأصناف عندما تفي دالة بسيطة بالغرض، ولكن بصفتك مبرمجًا، يجب أن تكون على دراية بأساسيات الأصناف وكيف تعمل، لذلك سنتعرف في هذا المقال على ماهية الأصناف، ولماذا تُستخدَم في البرامج، ومفاهيم البرمجة القائمة عليها. البرمجة كائنية التوجه موضوع واسع، وهذا المقال مجرّد مقدمة.

تشبيه من العالم الحقيقي: تعبئة استمارة

لعلّك ملأت الاستمارات الورقية والإلكترونية عدة مرات في حياتك؛ منها لزيارات الطبيب أو لعمليات الشراء عبر الإنترنت أو للرد على دعوة لحضور حفل زفاف. تُستخدم الاستمارة بمثابة طريقة رسمية من قبل شخص آخر أو منظمة أخرى لجمع المعلومات التي يحتاجونها عنك. تسأل الاستمارات المختلفة أنواعًا متنوعة من الأسئلة، فمثلًا يجب عليك شرح حالتك الطبية الحساسة في استمارة الطبيب، بينما عليك الإبلاغ عن أي ضيوف تحضرهم معك إلى حفل الزفاف في استمارة الحفل.

في بايثون، يكون للصنف والنوع ونوع البيانات المعنى ذاته، ومثال عن ذلك النموذج الورقي أو الإلكتروني؛ فالصنف هو مخطط لكائنات بايثون (وتسمى أيضًا النُسَخ instances)، والتي تحتوي على البيانات الممثّلة لاسم، ويمكن أن يكون هذا الاسم هو مريض الطبيب، أو عملية شراء إلكترونية، أو ضيف حفل زفاف. تشبه الأصناف قالب استمارة فارغة، وتشبه الكائنات التي تُنشأ من ذلك الصنف الاستمارة المملوءة التي تحتوي على بيانات فعلية حول نوع الشيء الذي يمثله النموذج. على سبيل المثال، تُشبه استمارة استجابة دعوة حفل الزفاف RSVP الصنف، في حين تشبه دعوة حفل الزفاف RSVP المملوء الكائن في الشكل 1.

تشبيه من العالم الحقيقي

[الشكل 1: تُشبه قوالب استمارة دعوة الزفاف الأصناف، في حين تُشبه الاستمارات المعبأة الكائنات]

يمكنك أيضًا النظر إلى الأصناف والكائنات على أنها جداول بيانات، كما في الشكل 2.

البرمجة كائنية التوجه بايثون

[الشكل 2: جدول بيانات لجميع بيانات دعوات حفل الزفاف]

ستشكل ترويسات الأعمدة الأصناف، وتشكل الصفوف rows الفردية كائنًا.

يأتي غالبًا ذكر الأصناف والكائنات على أنها نماذج بيانات للعناصر في العالم الحقيقي، ولكن لا تخلط بين الخريطة map والمنطقة؛ فما تحتويه الأصناف يعتمد على ما يحتاج البرنامج لفعله. يوضح الشكل 3 بعض الكائنات من أصناف مختلفة تمثل جميعها شخصًا، وتخزن معلومات مختلفة تمامًا باختلاف اسم الشخص.

البرمجة كائنية التوجه

[الشكل 3: أربعة كائنات مصنوعة من أصناف مختلفة تمثل شخصًا، اعتمادًا على ما يحتاج التطبيق إلى معرفته عن الشخص]

يجب أيضًا أن تعتمد المعلومات الموجودة في أصنافك على احتياجات برنامجك، إذ تستخدم العديد من برامج البرمجة كائنية التوجه التعليمية صنف Car مثالًا أساسيًا دون الإشارة إلى أن ما يوجد في الصنف يعتمد كليًا على نوع البرنامج الذي تكتبه. لا يوجد هناك ما يدعى صنف Car العام الذي من الواضح أنه يحتوي على تابع honkHorn()‎‎‎‎ أو سمة numberOfCupholders لمجرد أنها خصائص تمتلكها سيارات العالم الحقيقي؛ فقد يكون برنامجك لتطبيق ويب لبيع السيارات أو للعبة فيديو لسباق السيارات أو لمحاكاة حركة المرور على الطرق؛ وقد يكون لصنف السيارات الخاصة بتطبيق الويب لبيع السيارات على الويب سمات milesPerGallon أو manufacturersSuggestedRetailPrice (تمامًا كما قد تستخدم جداول بيانات وكالة السيارات هذه كعمود)، لكن لن تحتوي لعبة الفيديو ومحاكاة حركة المرور على الطرق على هذه الأصناف، لأن هذه المعلومات ليست ذات صلة بهما. قد يحتوي صنف السيارات الخاصة بلعبة الفيديو على explodeWithLargeFireball()‎‎، ولكن لن يحتوي تطبيق المحاكاة أو بيع السيارات على الصنف ذاته.

إنشاء كائنات من الأصناف

سبق لك استخدام الأصناف والكائنات في بايثون، حتى لو لم تُنشئ الأصناف بنفسك. تذكّر وحدة datetime، التي تحتوي على صنف باسم date، إذ تُمثل كائنات صنف datetime.date (تسمى أيضًا ببساطة كائنات اdatetime.date أو كائنات date) تاريخًا محددًا. أدخل ما يلي في الصدفة التفاعلية Interactive Shell لإنشاء كائن من صنف datetime.date:

>>> import datetime
>>> birthday = datetime.date(1999, 10, 31) # مرّر قيمة السنة والشهر واليوم
>>> birthday.year
1999
>>> birthday.month
10
>>> birthday.day
31
>>> birthday.weekday()‎‎ # يمثّل‫ weekday() تابعًا؛ لاحظ القوسين
6

السمات Attributes -أو يطلق عليها أحيانًا الخاصيات- هي متغيرات مرتبطة بالكائنات. يؤدي استدعاء datetime.date()‎‎ إلى إنشاء كائن date جديد، جرت تهيئته باستخدام الوسطاء 1999, 10, 31، بحيث يمثل الكائن تاريخ 31 أكتوبر 1999. نعيّن هذه الوسطاء على أنها سمات للصنف date، وهي year و month و day، التي تحتوي على جميع كائنات date.

يمكن -باستخدام هذه المعلومات- لتابع الصنف weekday()‎‎ حساب يوم الأسبوع. في هذا المثال، تُعاد القيمة 6 ليوم الأحد، لأنه وفقًا لتوثيق بايثون عبر الإنترنت، القيمة المُعادة من weekday()‎‎ هي عدد صحيح يبدأ من 0 ليوم الاثنين وينتهي بالعدد 6 ليوم الأحد.

يسرد توثيق بايثون العديد من التوابع الأخرى التي تمتلكها كائنات صنف date. على الرغم من أن كائن date يحتوي على سمات وتوابع متعددة، لكنه لا يزال كائنًا واحدًا يمكنك تخزينه في متغير، مثل birthday في هذا المثال.

إنشاء صنف بسيط- WizCion

دعنا ننشئ صنف WizCoin الذي يمثل عددًا من العملات في عالم سحري خيالي. فئات هذه العملة هي: knuts، و sickles (بقيمة 29 knuts)، و galleons (بقيمة 17 sickles أو 493 knuts). ضع في حساباتك أن العناصر الموجودة في صنف WizCoin تمثل كميةً من العملات، وليس مبلغًا من المال. على سبيل المثال، ستُخبرك أنك تمتلك خمسة أرباع سنت وعشرة سنتات بدلًا من 1.35 دولار.

في ملف جديد باسم wizcoin.py، ضِف الشيفرة التالي لإنشاء صنف WizCoin. لاحظ أن اسم دالة __init__ له شرطتان سفليتان قبل وبعد init (سنناقش __init__ في " التابعين ‎ ‎__init__()‎ والمعامل self" لاحقًا):

1 class WizCoin:
2     def __init__(self, galleons, sickles, knuts):
        """‫إنشاء كائن WizCoin جديد باستخدام galleons و sickles و knuts"""
        self.galleons = galleons
        self.sickles  = sickles
        self.knuts    = knuts
        # ‫ملاحظة: لا يوجد لتوابع ()__init‎__ قيمة مُعادة إطلاقًا

3     def value(self):
        """‎‫حساب القيمة بفئة knuts في غرض WizCoin لكل العملات"""
        return (self.galleons * 17 * 29) + (self.sickles * 29) + (self.knuts)

4     def weightInGrams(self):
        """حساب وزن العملات بالجرام"""
        return (self.galleons * 31.103) + (self.sickles * 11.34) + (self.knuts * 5.0)

يعرّف هذا البرنامج صنفًا جديدًا يدعى WizCoin باستخدام التعليمة الأولى class، ويؤدي إنشاء صنف إلى إنشاء نوع جديد من الكائنات، إذ أن استخدام عبارة class لتعريف صنف يشبه عبارات def التي تعرّف دوالًا جديدة. توجد تعريفات لثلاثة توابع داخل كتلة التعليمات البرمجية التي تلي تعليمة class، هي: ‎__init __()‎‎ (اختصارًا للتهيئة)، و value()‎‎، و weightInGrams()‎‎. لاحظ أن جميع التوابع لها معامل أوّل يدعى self الذي سنكتشفه في القسم التالي.

تكون أسماء الوحدات، مثل wizcoin في ملف wizcoin.py بأحرف صغيرة عادةً، بينما تبدأ أسماء الأصناف، مثل WizCoin بحرف كبير، ولكن للأسف، لا تتبع بعض الأصناف في مكتبة بايثون هذا الاصطلاح مثل صنف date.

للتدرب على إنشاء كائنات جديدة لصنف WizCoin، ضِف الشيفرة المصدرية التالية في نافذة محرر ملفات منفصلة واحفظ الملف باسم wcexample1.py في المجلد wizcoin.py:

import wizcoin

1 purse = wizcoin.WizCoin(2, 5, 99) # تُمرّر الأعداد الصحيحة إلى التابع‫ ()__init‎__
print(purse)
print('G:', purse.galleons, 'S:', purse.sickles, 'K:', purse.knuts)
print('Total value:', purse.value()‎‎)
print('Weight:', purse.weightInGrams()‎‎, 'grams')

print()‎‎

2 coinJar = wizcoin.WizCoin(13, 0, 0) # تُمرّر الأعداد الصحيحة إلى التابع‫ ()__init‎__
print(coinJar)
print('G:', coinJar.galleons, 'S:', coinJar.sickles, 'K:', coinJar.knuts)
print('Total value:', coinJar.value()‎‎)
print('Weight:', coinJar.weightInGrams()‎‎, 'grams')

تُنشئ استدعاءات WizCoin()‎‎ كائن WizCoin وتُنفّذ الشيفرة في دالة‎‎__init __()‎‎. نمرّر هنا ثلاثة أعداد صحيحة مثل وسطاء إلى WizCoin()‎‎ ثم يُعاد توجيه تلك الوسطاء إلى معاملات ‎‎__init __()‎‎، تُعيَّن الوسطاء إلى سمات كائنات self.galleons و self.sickles و self.knuts. يجب علينا استيراد wizcoin ووضع .wizcoin قبل اسم دالة WizCoin()‎‎ تمامًا كما تتطلب دالة time.sleep()‎‎ أن تستورد وحدة time ووضع .time قبل اسم الدالة أولًا.

عند تنفيذ البرنامج، سيبدو الخرج كما يلي:

<wizcoin.WizCoin object at 0x000002136F138080>
G: 2 S: 5 K: 99
Total value: 1230
Weight: 613.906 grams

<wizcoin.WizCoin object at 0x000002136F138128>
G: 13 S: 0 K: 0
Total value: 6409
Weight: 404.339 grams

إذا تلقيت رسالة خطأ، مثل:

ModuleNotFoundError: No module named 'wizcoin'

تحقق أن الملف يحمل اسم wizcoin.py وأنه موجود في المجلد wcexample1.py ذاته.

لا تمتلك كائنات WizCoin توضيحات نصية مفيدة، لذلك تعرض طباعة purse و coinJar عنوان تخزين بين قوسين (ستتعلم كيفية تغيير لاحقًا).

يمكننا استدعاء التابعين value()‎‎ و weightInGrams()‎‎ على كائنات WizCoin التي خصصناها لمتغيري purse و coinJar، كما يمكننا استدعاء تابع السلسلة lower()‎‎ على كائن سلسلة نصية. تحسب هذه التوابع القيم بناءً على سمات كائنات galleons و sickles و knuts.

يُفيد استخدام الأصناف classes والبرمجة كائنية التوجه في إنتاج شيفرات برمجية أكثر قابلية للصيانة؛ أي شيفرة يسهل قراءتها وتعديلها وتوسيعها مستقبلًا.

التابع ‎‎ __init __()‎‎والمعامل self

التوابع هي دوال مرتبطة بكائنات من صنف معين. تذكر أن low()‎‎ هو تابع لسلسلة، ما يعني أن استدعائه يكون على كائنات سلسلة نصية string. يمكنك استدعاء lower()‎‎ من سلسلة، مثل ‎'Hello'.lower()‎‎ ولكن لا يمكنك استدعائها على قائمة مثل: ‎['dog', 'cat'].lower()‎‎. لاحظ أيضًا أن التوابع تأتي بعد الكائن، والشيفرة الصحيحة هي ‎'Hello'.lower()‎‎، وليست lower('Hello'‎)‎. على عكس تابع مثل lower()‎‎، لا ترتبط دالة مثل len()‎‎ بنوع بيانات واحد؛ إذ يمكنك تمرير سلاسل وقوائم وقواميس وأنواع أخرى كثيرة من الكائنات إلى الدالة len()‎‎.

نُنشئ كائنات عن طريق استدعاء اسم الصنف مثل دالة كما رأيت سابقًا، ويُشار إلى هذه الدالة على أنها دالة بانية constructor (أو باني، أو تُختصر باسم ctor، وتُنطق "see-tore") لأنها تُنشئ كائنًا جديدًا. نقول أيضًا أن الباني يبني نسخةً جديدةً للصنف.

يؤدي استدعاء الباني إلى إنشاء كائن جديد ثم تنفيذ تابع ‎‎__init __()‎‎، ولا يُطلب من الأصناف أن يكون لديها تابع ‎‎__init __()‎‎، لكنها تملك هذا دائمًا تقريبًا. تابع ‎‎__init __()‎‎ هو المكان الذي تُعيّن فيه القيم الأولية للسمات عادةً. على سبيل المثال، تذكر أن تابع ‎‎__init __()‎‎ الخاص بالصنف WizCoin يبدو كما يلي:

 def __init__(self, galleons, sickles, knuts):
        """‫إنشاء كائن WizCoin جديد باستخدام galleons و sickles و knuts"""
        self.galleons = galleons
        self.sickles  = sickles
        self.knuts    = knuts
        # ‫ملاحظة: لا يوجد لتوابع ()__init‎__ قيمة مُعادة إطلاقًا

عندما يستدعي برنامج wcexample1.py ما يلي: WizCoin (2, 5, 99)‎، يبني بايثون كائن WizCoin جديد، ثم يمرر ثلاثة وسطاء (2 و 5 و 99) إلى استدعاء ‎‎__init __()‎‎، لكن للتابع ‎‎__init __()‎‎ أربعة معاملات، هي: self و galleons و sickles و knuts، والسبب هو أن جميع التوابع لها معامل أول يدعى self. عندما يُستدعى تابع ما على كائن، يُمرّر الكائن تلقائيًا لمعامل self، وتُعيَّن بقية الوسطاء للمعاملات بصورة طبيعية.

إذا رأيت رسالة خطأ، مثل:

TypeError: ‎__init__()‎‎ takes 3 positional arguments but 4 were given

ربما تكون قد نسيت إضافة معامل self إلى تعليمة def الخاصة بالتابع.

لا يتعين عليك تسمية المعامل الأول للتابع بالاسم self، إذ يمكنك تسميته بأي شيء آخر، لكن استخدام self أمر تقليدي، واختيار اسم مختلف سيجعل الشيفرة الخاصة بك أقل قابلية للقراءة لمبرمجي بايثون الآخرين. عندما تقرأ الشيفرة، فإن وجود self مثل معامل أول هو أسرع طريقة يمكنك من خلالها تمييز التوابع عن الدوال، وبالمثل، إذا كانت شفرة تابعك لا تحتاج أبدًا إلى استخدام معامل self، فهذه علامة على أن تابعك يجب أن يكون مجرد دالة.

لا تُعيَّن الوسطاء 2 و 5 و 99 في WizCoin (2, 5, 99)‎ تلقائيًا إلى سمات الكائن الجديد؛ إذ نحتاج إلى عبارات الإسناد الثلاث في ‎‎__init __()‎‎ لإجراء ذلك. تُسمّى معاملات ‎‎__init __()‎‎ غالبًا باسم السمات ذاته، لكن يشير وجود self في self.galleons إلى أنها سمة من سمات الكائن، بينما يُعد galleons معاملًا.

يعد تخزين وسطاء الباني في سمات الكائن مهمةً شائعةً لتابع ‎‎‎__init __()‎‎ للأصناف. نفّذ استدعاء datetime.date()‎‎ في القسم السابق مهمةً مماثلةً باستثناء أن الوسطاء الثلاثة التي مررناها كانت لسمات year و month و day لكائن date الذي أُنشئ حديثًا.

لقد سبق لك أن استدعيت الدوال int()‎‎ و str()‎‎ و float()‎‎ و bool()‎‎ للتحويل بين أنواع البيانات، مثل str (3.1415)‎ للحصول على قيمة السلسلة '3.1415' بناءً على القيمة العشرية 3.1415. وصفنا ما سبق عندها على أنها دوال، لكن int و str و float و bool في الواقع أصناف، والدوال int()‎‎ و str()‎‎ و float()‎‎ و bool()‎‎ هي دوال بانية تعيد عددًا صحيحًا جديدًا أو سلسلة أو عدد عشري أو كائنات منطقية. يوصي دليل أسلوب بايثون باستخدام أحرف كبيرة لأسماء أصنافك، مثل WizCoin، على الرغم من أن العديد من أصناف بايثون المضمنة لا تتبع هذا الاصطلاح.

يعيد استدعاء دالة الإنشاء WizCoin()‎‎ الكائن WizCoin الجديد، لكن التابع ‎‎__init __()‎‎ لا يحتوي أبدًا على عبارة return بقيمة مُعادة. تؤدي إضافة قيمة إعادة إلى حدوث هذا الخطأ:

TypeError: ‎‎__init__()‎‎ should return None.‎

السمات

السمات attributes -أو الخاصيات- هي متغيرات مرتبطة بكائن، ويصف توثيق بايثون السمات بأنها "أي اسم يتبع النقطة" على سبيل المثال، لاحظ تعبير birthday.year في القسم السابق، السمة year هي اسم يتبع النقطة.

يمتلك كل كائن مجموعة السمات الخاصة به، فعندما أنشأ برنامج wcexample1.py كائنين WizCoin وخزّنهما في متغيرات purse و coinJar كان لسماتهما قيم مختلفة. يمكنك الوصول إلى هذه السمات وتعيينها تمامًا مثل أي متغير. للتدرب على إعداد السمات: افتح نافذة محرر ملفات جديدة وأدخل الشيفرة التالية، واحفظها بالاسم wcexample2.py في مجلد الملف wizcoin.py ذاته:

import wizcoin

change = wizcoin.WizCoin(9, 7, 20)
print(change.sickles) # تطبع 7
change.sickles += 10
print(change.sickles) # تطبع 17

pile = wizcoin.WizCoin(2, 3, 31)
print(pile.sickles) # تطبع 3
pile.someNewAttribute = 'a new attr' # إنشاء سمة جديدة
print(pile.someNewAttribute)

عند تنفيذ هذا البرنامج، يبدو الخرج كما يلي:

7
17
3
a new attr

يمكنك التفكير في سمات الكائن بطريقة مشابهة لمفاتيح القاموس، إذ يمكنك قراءة وتعديل القيم المرتبطة بها وتعيين سمات جديدة للكائن، تقنيًا تُعدّ التوابع سمات للأصناف أيضًا.

السمات والتوابع الخاصة

يمكن تمييز السمات على أنها تتمتع بوصول خاص في لغات مثل C++‎ أو جافا، ما يعني أن المصرِّف compiler أو المُفسر interpreter يسمح فقط للشيفرة الموجودة في توابع الأصناف بالوصول إلى سمات كائنات تلك الصنف فقط أو تعديلها، لكن هذا الأمر غير موجود في بايثون، إذ تمتلك جميع السمات والتوابع وصولًا عامًا public access فعال، ويمكن للشيفرة خارج الصنف الوصول إلى أي سمة وتعديلها في أي كائن من ذلك الصنف.

الوصول الخاص مفيد، إذ يمكن مثلًا أن تحتوي كائنات صنف BankAccount على سمة balance التي لا يجب الوصول إليها إلا لتوابع صنف BankAccount. لهذه الأسباب، ينص اصطلاح بايثون على بدء أسماء السمات أو التوابع الخاصة بشرطة سفلية واحدة. تقنيًا، لا يوجد ما يمنع الشيفرة خارج الصنف من الوصول إلى السمات والتوابع الخاصة، ولكن من الممارسات المُثلى تُملي بالسماح لتوابع الصنف فقط بالوصول إليها.

افتح نافذة محرر ملفات جديدة، وأدخل الشيفرة التالية، واحفظها باسم privateExample.py. تحتوي كائنات صنف BankAccount في هذه الشيفرة على السمتين ‎_name و ‎_balance الخاصتين والتي يمكن فقط لتابعَي deposit()‎‎ و withdraw()‎‎ الوصول إليهما مباشرةً:

class BankAccount:
    def __init__(self, accountHolder):
        # ‫يمكن لتوابع ‎ BankAccount الوصول إلى self._balance ولكن الشيفرة خارج هذا الصنف لا يمكنها الوصول
1         self._balance = 0
2         self._name = accountHolder
        with open(self._name + 'Ledger.txt', 'w') as ledgerFile:
            ledgerFile.write('Balance is 0\n')

    def deposit(self, amount):
3         if amount <= 0:
            return # لا تسمح بقيم سالبة
        self._balance += amount
4         with open(self._name + 'Ledger.txt', 'a') as ledgerFile:
            ledgerFile.write('Deposit ' + str(amount) + '\n')
            ledgerFile.write('Balance is ' + str(self._balance) + '\n')

    def withdraw(self, amount):
5         if self._balance < amount or amount < 0:
            return # لا يوجد نقود كافية في الحساب أو أن الرصيد سالب
        self._balance -= amount
6         with open(self._name + 'Ledger.txt', 'a') as ledgerFile:
            ledgerFile.write('Withdraw ' + str(amount) + '\n')
            ledgerFile.write('Balance is ' + str(self._balance) + '\n')

acct = BankAccount('Alice') # أنشأنا حساب خاص بأليس
acct.deposit(120) # ‫يمكن تعديل السمة ‫‎_balance‎‎ باستخدام deposit()‎
acct.withdraw(40) # ‫يمكن تعديل السمة ‫‎_balance‎‎ باستخدام withdraw()‎

# ‫‎التغيير من ‎_name و ‎_balance أمر غير محبّذ ولكنه ممكن
7 acct._balance = 1000000000
acct.withdraw(1000)

8 acct._name = 'Bob' # ‎‫نستطيع الآن التعديل على سجل Bob!
acct.withdraw(1000) # عملية السحب هذه مسجلة في‫ BobLedger.txt!

عند تنفيذ privateExample.py، تكون الملفات التي تُنشأ غير دقيقة لأننا عدّلنا على ‎_balance و ‎_name خارج الصنف، مما أدى إلى حالات غير صالحة. يحتوي AliceLedger.txt على الكثير من المال بداخله:

Balance is 0
Deposit 120
Balance is 120
Withdraw 40
Balance is 80
Withdraw 1000
Balance is 999999000

يوجد الآن ملف BobLedger.txt برصيد حساب لا يمكن تفسيره، على الرغم من أننا لم ننشئ كائن BankAccount لسجل Bob إطلاقًا:

Withdraw 1000
Balance is 999998000

تكون الأصناف المصممة جيدًا في الغالب قائمة بحد ذاتها self-contained، مما يوفر توابع لضبط السمات على القيم الصحيحة. تُميَّز السمتين ‎_balance و ‎_name برقمي السطرين 1 و2، والطريقة الصالحة الوحيدة لتعديل قيمة صنف BankAccount هي من خلال التابعين deposit()‎‎ و withdraw()‎‎؛ إذ يحقق هذان التابعان من تعليمة (3) وتعليمة (5) للتأكد من أن ‎_balance لم توضع في حالة غير صالحة (مثل قيمة عدد صحيح سالب). يسجل هذان التابعان أيضًا كل معاملة لحساب الرصيد الحالي في تعليمة (4) وتعليمة (6).

يمكن أن تضع الشيفرة البرمجية التي تعدل هذه السمات وتقع خارج الصنف، مثل تعليمة ‎acct._balance = 1000000000‎ (التعليمة 7) أو تعليمة acct._name = 'Bob'‎ (التعليمة ? ذلك الكائن في حالة غير صالحة ويتسبب بأخطاء وعمليات تدقيق من فاحص البنك. يصبح تصحيح الأخطاء أسهل باتباع اصطلاح بادئة الشرطة السفلية للوصول الخاص، والسبب هو أنك تعرف أن سبب الخطأ سيكون داخل شيفرة الصنف بدلًا من أي مكان في البرنامج بأكمله.

لاحظ أنه على عكس جافا واللغات الأخرى، لا تحتاج بايثون إلى توابع getter و setter العامة للسمات الخاصة، وتستخدم بدلًا من ذلك الخاصيات properties، كما هو موضح لاحقًا.

دالة type()‎‎ وسمة qualname

يخبرنا تمرير كائن إلى دالة type()‎‎ المضمنة بنوع بيانات الكائن من خلال قيمته المُعادة، والكائنات التي تُعاد من دالة type()‎‎ هي أنواع كائنات، وتسمى أيضًا كائنات الصنف. تذكر أن مصطلح النوع ونوع البيانات والصنف لها المعنى ذاته في بايثون. لمعرفة ما تُعيده دالة type()‎‎ للقيم المختلفة، أدخل ما يلي في الصدفة التفاعلية:

>>> type(42)  # The object 42 has a type of int.
<class 'int'>
>>> int # int is a type object for the integer data type.
<class 'int'>
>>> type(42) == int  # Type check 42 to see if it is an integer.
True
>>> type('Hello') == int  # Type check 'Hello' against int.
False
>>> import wizcoin
>>> type(42) == wizcoin.WizCoin  # Type check 42 against WizCoin.
False
>>> purse = wizcoin.WizCoin(2, 5, 10)
>>> type(purse) == wizcoin.WizCoin # Type check purse against WizCoin.
True

لاحظ أن int هو نوع كائن وهو نفس نوع الكائن الذي يُعيده type(42)‎، ولكن يمكن أيضًا تسميته بدالة بانية int()‎‎؛ إذ لا تحوّل الدالة int ('42')‎ وسيط السلسلة '42'، وتُعيد بدلًا من ذلك كائن عدد صحيح بناءً على المعطيات.

لنفترض أنك بحاجة إلى تسجيل بعض المعلومات حول المتغيرات في برنامجك لمساعدتك على تصحيحها لاحقًا. يمكنك فقط كتابة سلاسل إلى ملف السجل، ولكن تمرير كائن النوع إلى str()‎‎ سيعيد سلسلة تبدو فوضوية إلى حد ما. بدلًا من ذلك، استخدم السمة __qualname__، التي تمتلكها جميع أنواع الكائنات، لكتابة سلسلة أبسط يمكن للبشر قراءتها:

>>> str(type(42))  # Passing the type object to str() returns a messy string.
"<class 'int'>"
>>> type(42).__qualname__ # The __qualname__ attribute is nicer looking.
'int'

تُستخدم سمة __qualname__ غالبًا لتجاوز تابع __repr __()‎‎، والتي سنشرحها بمزيد من التفصيل لاحقًا.

الخلاصة

البرمجة كائنية التوجه هي ميزة مفيدة لتنظيم الشيفرة البرمجية الخاصة بك. تتيح لك الأصناف تجميع البيانات والشيفرات البرمجية معًا في أنواع بيانات جديدة. يمكنك أيضًا إنشاء كائنات من هذه الأصناف عن طريق استدعاء بانيها (اسم الصنف المُستدعى مثل دالة)، والتي بدورها تستدعي تابع ‎__init __()‎‎ الخاص بالصنف. التوابع هي دوال مرتبطة بالكائنات، والسمات هي متغيرات مرتبطة بالكائنات. تحتوي جميع التوابع على معامل أول self، والذي يُعيّن للكائن عند استدعاء التابع. يسمح هذا للتوابع بقراءة سمات الكائن أو تعيينها واستدعاء توابعها.

على الرغم من أن بايثون لا تسمح لك بتحديد الوصول الخاص أو العام للسمات، إلا أنها تمتلك اصطلاحًا باستخدام بادئة شرطة سفلية لأي تابع أو سمات يجب استدعاؤها أو الوصول إليها فقط من توابع الصنف الخاصة. يمكنك -باتباع هذه الاتفاقية- تجنب إساءة استخدام الصنف ووضعها في حالة غير صالحة يمكن أن تسبب أخطاء. سيعيد استدعاء type(obj)‎ كائن صنف النوع obj. تحتوي كائنات الصنف على سمة __qualname___ التي تحتوي على سلسلة بشكل يمكن للبشر قراءته من اسم الصنف.

في هذه المرحلة، ربما تفكر، لماذا يجب أن نهتم باستخدام الأصناف والسمات والتوابع بينما يمكننا إنجاز المهمة ذاتها مع الدوال؟ تُعد البرمجة كائنية التوجه طريقةً مفيدةً لتنظيم الشيفرات البرمجية الخاصة بك في أكثر من مجرد ملف "‎.py" يحتوي على 100 دالة فيه. من خلال تقسيم البرنامج إلى عدة أصناف مصممة جيدًا، يمكنك التركيز على كل صنف على حدة.

البرمجة كائنية التوجه هي نهج يركز على هياكل البيانات وطرق التعامل مع هياكل البيانات تلك. هذا النهج ليس إلزاميًا لكل برنامج، ومن الممكن بالتأكيد الإفراط في استخدام البرمجة كائنية التوجه، لكن البرمجة كائنية التوجه توفر فرصًا لاستخدام العديد من الميزات المتقدمة التي سنستكشفها في الفصلين التاليين. أول هذه الميزات هو الوراثة inheritance التي سنتعمق فيها في الفصل التالي.

ترجمة -وبتصرف- لقسم من الفصل Object-Oriented Programming And Classes من كتاب Beyond the Basic Stuff with Python.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...