Programming Basics SQL HTML CSS JavaScript React Python C++ Java JavaFX Swing Problem Solving English English Conversations Computer Fundamentals Linux Learn Typing

بايثونOverriding

  • مفهوم إعادة التعريف
  • مثال حول إعادة التعريف
  • الدالة super()

مفهوم إعادة التعريف

في الدرس السابق، تعلمنا كيفية جعل الكلاس يرث المتغيرات و الدوال الموجودة في كلاس آخر بالإضافة إلى إخفاء البيانات في الكلاس الأب حتى لا يحدث تضارب في الأسماء.

إعادة التعريف أو التعريف من جديد ( Overriding ) تعني أن يقوم الكلاس الإبن بإعادة تعريف نفس الدالة التي ورثها من الكلاس الأب. الدالة الجديدة تكون مشابهة للدالة الموروثة من حيث الشكل فقط، أي تملك نفس الإسم و عدد الباراميترات لكن محتواها مختلف.

الهدف الحقيقي من إعادة التعريف هو إتاحة الفرصة للكلاس الإبن ليعرّف الدوال حسب حاجته.

في دروس لاحقة، سنرث من كلاسات جاهزة في بايثون و نقوم بإعادة تعريف الدوال التي نرثها لكي تناسب التطبيقات التي نبنيها.


شروط إعادة التعريف

  • لا يمكن إعادة تعريف دالة معرّفة بالأساس بشكل خفي.
  • عدد و نوع باراميترات الدالة الجديدة يجب أن يطابق عدد و نوع باراميترات الدالة القديمة.

عدم تحقق هذين الشرطين يجعل مفسر بايثون يفهم أننا نريد إضافة دالة جديدة في الكلاس الإبن و ليس إعادة تعريف الدالة التي ورثها في الأصل من الكلاس الأب.

مثال حول إعادة التعريف

في المثال التالي قمنا بتعريف كلاس إسمه CountryInfo و يحتوي على دالة إسمها print_language().
بعدها قمنا بتعريف ثلاث كلاسات ترث من الكلاس CountryInfo مما يعني أن كل واحد منهم يحتوي على الدالة print_language().
هنا الفكرة أن أي كلاس يرث من كلاس CountryInfo قد يضطر إلى تعريف الدالة print_language() من جديد حتى تناسبه.

مثال

CountryInfo.py
# هذا الكلاس يعتبر الكلاس الأساسي لأي دولة في العالم مما يعني أن أي كلاس يمثل دولة يجب أن يرث منه
class CountryInfo:

    # هنا قمنا بإضافة دالة تطبع لغة البلد و قد اعتبرنا أن الإنجليزية هي اللغة الإفتراضية
    def print_language(self):
        print('English')
Australia.py
# حتى نستطيع الوراثة منه PrintInfo.py الموجود في الملف PrintInfo هنا قمنا بتضمين الكلاس
from CountryInfo import CountryInfo


# 'English' التي تطبع كلمة print_language() و بالتالي ورث الدالة CountryInfo يرث من الكلاس Australia هنا قمنا بتعريف كلاس إسمه
class Australia(CountryInfo):
    pass
    
    # من جديد لأن اللغة الإنجليزية هي لغة أستراليا print_language() هنا لا داعي لتعريف الدالة
Lebanon.py
# حتى نستطيع الوراثة منه PrintInfo.py الموجود في الملف PrintInfo هنا قمنا بتضمين الكلاس
from CountryInfo import CountryInfo


# 'English' التي تطبع كلمة print_language() و بالتالي ورث الدالة CountryInfo يرث من الكلاس Lebanon هنا قمنا بتعريف كلاس إسمه
class Lebanon(CountryInfo):

    # لأن اللغة العربية هي لغة لبنان 'English' بدل كلمة 'Arabic' من جديد لجعلها تطبع كلمة print_language() هنا يجب تعريف الدالة
    def print_language(self):
        print('Arabic')
Spain.py
# حتى نستطيع الوراثة منه PrintInfo.py الموجود في الملف PrintInfo هنا قمنا بتضمين الكلاس
from CountryInfo import CountryInfo


# 'English' التي تطبع كلمة print_language() و بالتالي ورث الدالة CountryInfo يرث من الكلاس Spain هنا قمنا بتعريف كلاس إسمه
class Spain(CountryInfo):

    # لأن اللغة الإسبانية هي لغة إسبانيا 'English' بدل كلمة 'Spanish' من جديد لجعلها تطبع كلمة print_language() هنا يجب تعريف الدالة
    def print_language(self):
        print('Spanish')
Test.py
# حتى نستطيع إنشاء كائنات منهم Spain و Lebanon ،Australia هنا قمنا بتضمين الكلاسات
from Australia import Australia
from Lebanon import Lebanon
from Spain import Spain


# Spain و Lebanon ,Australia هنا قمنا بإنشاء كائنات من البلدان الثلاثة
au = Australia()
lb = Lebanon()
sp = Spain()

# من كل كائن لعرض لغة كل بلد print_language() هنا قمنا باستدعاء الدالة
au.print_language()
lb.print_language()
sp.print_language()

نتيجة تشغيل الملف Test.

English
Arabic
Spanish

من خلال المثال السابق وضحنا فائدة إعادة تعريف الدالة التي يرثها الكلاس الإبن.


إمكانية الوصول للدالة الأصلية

إذا عدت للكلاس Lebanon،, ستجد أننا أعدنا تعريف الدالة print_language() حتى تطبع Arabic بدلاً من English في حال إنشاء كائن من الكلاس Lebanon و إستدعاءها منه.

فعلياً، الكلاس Lebanon أصبح يملك دالتين إٍسمهما print_language() و ليس دالة واحدة كالتالي:

  • الأولى هي التي ورثها في الأساس من الكلاس CountryInfo.
  • الثانية هي التي قمنا بتعريفها فيه من جديد في الكلاس Lebanon.

بما أن الكلاس Lebanon يملك دالتين لهما نفس الإسم و عدد الباراميترات، كيف عرف مفسّر بايثون أي دالة منهما سينفذ عند تشغيل الكود؟

مفسّر بايثون يبحث في المرتبة الأولى عن الدالة التي قمت باستدعائها في الكلاس الذي تم منه إنشاء الكائن.
إذا وجد الدالة في الكلاس فإنه يقوم بتنفيذها. إن لم يجدها فيه فإنه يبحث عنها في الكلاس الأب له. إن وجدها في الكلاس الأب يقوم بتنفيذها.

في الكلاس Lebanon لا يزال بإمكانك الوصول للدالة print_language() التي ورثها من الكلاس CountryInfo بالإعتماد على الدالة super().

الدالة super()

هذه الدالة تجعلك قادر على الوصول إلى الدالة المعرّفة في الكلاس الأب ( Superclass ) من الكلاس الإبن ( Subclass ) سواء كان يوجد تضارب في الأسماء أو لا يوجد و بالتالي يصبح الكلاس الإبن قادر على أن يعيد كتابة الدالة التي ورثها من الكلاس الأب و أن يظل قادر على الوصول إلى الدالة التي ورثها من الكلاس الأب أيضاً.

بمعنى آخر فإن هذه الدالة أن تجعل المبرمج قادر على الإستفادة من الدالة الموجودة في الكلاس الأب و إضافة أشياء جديدة عليها في الكلاس الإبن.
ستتعلم من المثال التالي كيف تقوم باستدعاء الدالة __init__() الموجودة في الكلاس الأب بشكل تلقائي من الدالة __init__() الموجودة في الكلاس الإبن.

تذكر أن الباراميترات التي يتم تعريفها بداخل الدالة __init__() يتم تحويلها إلى خصائص للكائن الذي يتم إنشاؤه و هذه الدالة تستدعى بشكل تلقائي عند إنشاء الكائن.


في المثال التالي قمنا بتعريف كلاس إسمه Person يحتوي على خاصيتين هما name و age.
هاتين الخاصيتين يتم إنشاؤهما في لحظة إنشاء كائن من الكلاس لأننا وضعناهما في الدالة __init__().

بعدها قمنا بتعريف كلاس إسمه Student يرث من الكلاس Person.
في هذا الكلاس قمنا بإعادة تعريف الدالة __init__() و وضعنا فيها نفس الباراميترات الموجودة في الدالة __init__() التي ورثها الكلاس و أضفنا فيها باراميتر جديد إسمه specialization. بعدها قمنا بتعريف دالة أخرى إسمها print_info() تعرض كل قيم الخصائص الموجودة في الكلاس.

مثال

Person.py
# هذا الكلاس يعتبر الكلاس الأساسي لأي إنسان حيث أن كل إنسان يملك إسم و عمر
class Person:

    # التي سيملكها أي كائن ننشئه منه Person التي وضعنا فيها خصائص الكلاس __init__() هنا قمنا بتعريف الدالة
    def __init__(self, name, age):
        self.name = name
        self.age = age
Student.py
# حتى نستطيع الوراثة منه Person.py الموجود في الملف Person هنا قمنا بتضمين الكلاس
from Person import Person

# لأنه يمكنه الإستفادة منه Person يعتبر الكلاس الأساسي لإنشاء أي طالب، هذا الكلاس جعلناه يرث من الكلاس Student هنا قمنا بتعريف كلاس إسمه
class Student(Person):

    # و التي سيملكها أي كائن ننشئه منه Student التي وضعنا فيها خصائص الكلاس __init__() هنا قمنا بتعريف الدالة
    def __init__(self, name, age, specialization):
        # اللتين ندخلهما عند إنشاء كائن age و قيمة الباراميتر name و مررنا لها قيمة البارميتر Person الموجودة في الكلاس __init__() هنا قمنا باستدعاء الدالة 
        super().__init__(name, age)
        # التي ذكرناها في هذا الكلاس سيتم إعطائها آخر قيمة نمررها في أقواس الكائن الذي ننشئه من هذا الكلاس specialization الخاصية
        self.specialization = specialization

    # Student هنا قمنا بتعريف دالة تطبع كل قيم الخصائص التي يملكها أي كائن ننشئه من الكلاس
    def print_info(self):
        print('name:', self.name)
        print('age:', self.age)
        print('specialization:', self.specialization)
Test.py
# حتى نستطيع إنشاء كائن منه Student.py الموجود في الملف Student هنا قمنا بتضمين الكلاس
from Student import Student

# s و خزناه في Student هنا قمنا بإنشاء كائن من الكلاس
s = Student('Mhamad', 24, 'Computer Science')

# حتى تعرض قيم خصائصه s من الكائن print_info() هنا قمنا باستدعاء الدالة
s.print_info()

نتيجة تشغيل الملف Test.

name: Mhamad
age: 24
specialization: Computer Science