البرمجة الموجهة للكائنات OOP في Python
جزء1 - Class and Object
يبرز اسلوب البرمجة الموجهة للكائنات (Object-Oriented Programming - OOP) كأحد الأساليب الرئيسية التي تساعد على تنظيم الكود بشكل فعال .و لغة البرمجة Python واحدة من أهم اللغات التي تدعم هذا الإسلوب... فكيف يتم العمل بالبرمجة الموجهة للكائنات (OOP) مع Python..!!!
في هذا المقال، سنستكشف عالم البرمجة الموجهة للكائنات (OOP) في Python، وسنلقي نظرة عميقة على كيفية استخدام الفئات والكائنات Class & Object. لبناء برامج مرنة ومنظمة. سنتعرف على المفاهيم الأساسية للـ OOP وهي الـ Class و الـ Object وكيفية تطبيقها بشكل عملي في Python...لنبدأ…
- في هذا المقال نتعرف على:
- عن البرمجة الموجهة للكائنات (OPP) في بايثون.
- الفئات والكائنات Class & Objects.
- السمات Attributes.
- الدوال أو الأساليب Methods.
- الفئات المتداخلة Inner Classes.
عن البرمجة الموجهة للكائنات (OPP) في بايثون
بايثون هي لغة متعددة النماذج، فهي تسمح لنا بالبرمجة بأسلوب إجرائي (Procedural Programming ) وأسلوب البرمجة الموجهة للكائنات (Object-Oriented Programming) وإسلوب البرمجة الوظيفية (Functional Programming)، كما يمكن البرمجة بمزيج من الأساليب التي تدعمها Python فالبرمجة بلغة بايثون لا تجبرنا على طريقة معينة.
من الممكن تمامًا كتابة أي برنامج بأسلوب إجرائي، وبالنسبة للبرامج الصغيرة جدًا (حتى 500 سطر مثلًا)، نادرًا ما يمثل ذلك مشكلة. لكن بالنسبة لمعظم البرامج، وخاصة البرامج المتوسطة والكبيرة الحجم، توفر البرمجة الموجهة للكائنات (OOP) العديد من المزايا. يغطي هذا المقال جميع المفاهيم والتقنيات الأساسية للقيام بالبرمجة الموجهة للكائنات في لغة البرمجة Python.
OOP هو اختصار يرمز إلى نموذج البرمجة الشيئية أو البرمجة الموجهة للكائنات (Object-Oriented Programming). يتم تعريفه على أنه نموذج برمجة يستخدم مفهوم الكائنات الذي يشير إلى كيانات العالم الحقيقي ذات الحالة والسلوك. للمزيد حول OOP يمكن الإطلاع على مقال عن البرمجة الموجهة للكائنات OOP.
Python من لغات البرمجة التي تدعم نهج البرمجة الموجهة للكائنات OOP. وهذا يجعل من السهل إنشاء واستخدام الفئات (Classes) والكائنات (Objects). ويعتمد العمل مع البرمجة الموجهة بالكائنات OOP على إدراك وعدة مفاهيم برمجية من أهمها التالي:
- الفئات والكائنات Class & Objects.
- التغليف Encapsulation.
- الوراثة Inheritance.
- تعدد الأشكال Polymorphism.
في هذا المقال و من خلال تعليمات لغة بايثون سنتعرف بالتفصيل على طريقة العمل وتطبيق مفهومي الفئات (Classes ) و الكائنات (Objects) .
الفئات والكائنات Class & Objects
الـ Class هو نموذج يحتوي سمات كائن معين من العالم الحقيقي و سلوك هذه الكائن ، هذه الكائنات ليست مجرد بيانات وليست وظائف فقط، ولكنها مزيج من الاثنين معًا. فكل كائن في العالم الحقيقي له سمات وسلوك مرتبط به. مثلاً :
- في Class الخاص بالطلاب : تعرف السمات Attributes اسم الطالب وفصله ,والمواد المسجل فيها وعلاماته وما إلى ذلك، بينما يمكن حساب النسبة المئوية لدرجات الطالب بواسطة Method.
- في Class الخاص بالموظفين : تعرف السمات Attributes باسم الموظف ووظيفته وقسمه وراتبه وما إلى ذلك، بينما حساب الحوافز المستحقة للموظف بواسطة Method.
- في Class الخاص بالفواتير : رقم الفاتورة، العميل، رمز المنتج واسمه، السعر والكمية، وما إلى ذلك، في الفاتورة.بينما يمكن تطبيق ضريبة السلع والخدمات على قيمة الفاتورة بواسطة Method.
اما الـ Object هو التمثيل الحقيقي للـ Class فمثلاً الـ Class الخاص بالطلاب يمثل الـ Object بيانات لطالب معين من الطلاب لهذا يطلق عليه اسم المثيل Instance .
الشكل التالي يوضح فكرة عمل الـ Class والـ Object :
فيما يلي نظرة سريعة على بعض المصطلحات المرتبطة بالبرمجة بإسلوب OOP :
- الأسلوب (Method) : نوع خاص من الوظائف ( الدوال Functions ) يتم تعريفها في الـ Class.(المزيد في مقال الدوال Functions في Python)
- متغير المثيل (Instance variable) : ويعرف أيضا بـسمة المثيل (Instance Attribute ) متغير يتم تعريفه داخل الدالة وينتمي فقط إلى النسخة الحالية للـ Class بمعنى لكل Object نسخة خاصة من هذا المتغير تحمل بيانات هذا الـ Object. هذا النوع من المتغيرات يمثل الـ Class عند حالة معينة لهذا تعرف هذه المتغيرات بمتغيرات الحالة.
- متغير الفئة (Class variable) : ويعرف أيضاً بـسمة الفئة (Class Attribute) متغير مشترك بين كافة مثيلات (الـ Objects الخاصة بهذا الـ Class) (instances of a class). يتم تعريف متغيرات الفئة داخل الـ Class ولكن خارج أي من أساليب (Methods) الـ Class.لا يتم استخدام متغيرات الفئة بشكل متكرر مثل متغيرات المثيل (Instance variable).
- عنصر البيانات (Data member): أي متغير سواء متغير فئة Class variable أو متغير حالة Instance variable يحتفظ بالبيانات المرتبطة بالـ Class وكائناتها .
- السمات (Attributes): هي عناصر البيانات (متغيرات الـ Class و متغيرات الحالة).
- الفئة Class: نموذج أولي محدد من قبل المستخدم لكائن(Object) يحدد مجموعة من السمات (Attributes) التي تميز أي كائن أي Class، ويحتوي الـ Class على الأساليب (Methods) التي تصف سلوك الـ Class و يمكن الوصول الى السمات و الأساليب الخاصة بـالـ Class عبر تدوين النقطة dot notation. .
- الكائن (Object): تمثيل فريد لبنية البيانات التي يتم تعريفها بواسطة الـ Class. يشتمل الكائن على قيم لعناصر البيانات (متغيرات الـ Class ومتغيرات الحالة) والأساليب (Methods). (نسخة من الـ Class ببيانات حقيقية).
- المثيل (Instance): كل كائن فردي ( An individual object ) من Class معين . بمعنى أي تمثيل لحالة معينة من الـ Class بواسطة كائن Object يسمى "مثيل Instance" مثلاً الكائن object الذي ينتمي إلى فئة Circle هو مثيل للفئة Circle.
تعتبر هذه مجموعة لأهم المصطلحات المستخدمة مع اسلوب البرمجة الموجهة للكائنات في بايثون او اي لغة برمجية. والمزيد منها سنتعرف عليه فيما بعد.
أمثلة تطبيقية على Class & Object
وللتعرف أكثر على الـ Class & Object تابع /ي الأمثلة التطبيقة التالية عن مفاهيم الـ Class & Objects في لغة بايثون:
إنشاء Class & Object
في المثال التالي سنقوم بإنشاء Class خاص بالموظفين وسنقوم باضافة و دوال للتعامل مع بيانات الـ Objects المختلفة ، ثم سنرى طريقة عمل الـ Objects لهذا الـ Class ونوضح طريقة العمل مع البيانات :
#Create a class and named it "Employees"
class Employees:
#The frist method in Employees class
def SetEmployee(self, name):
self.name = name
#The second method in Employees class
def GetEmployee(self):
print("The employee name is", self.name)
ولإنشاء Object من الـ Class أعلاه نكتب….
...........
#Create the first object (instance) to the Employees class
employee1 = Employees()
#calling the Employees class methods by it's instance
employee1.SetEmployee("Nora")
employee1.GetEmployee()
#Create the second instance
employee2 = Employees()
employee2.SetEmployee("Sara")
employee2.GetEmployee()
...........
في المثال السابق نلاحظ:
- قمنا بإنشاء الـ Class بواسطة الكلمة المحجوزة class متبوعه بإسم الـ Class. ثم قمنا باضافة الدوال (Methods) للـ Class واستخدمت الكلمة المحجوزة self مع الدوال لتشير إلى الـ Object المستخدم حالياً.
- ثم قمنا بإنشاء الـ Objects للـ Class، والدوال يتم إنشاؤها داخل الـ Class وتستدعى بواسطة مثيلات (Instances) الـ class (الكائنات Objects التي إنشائها لهذا الـ Class).
الـ Constructor
المثال التالي يوضح طريقة اضافة الـ Attributes (بيانات الكائن) في الـ Class بواسطة الـ Constructor. و الـ Constructor نوع خاص من الدوال يستخدم بشكل أساسي لتهيئة القيم للـ Attributes فهو كما نعلم يستدعى بشكل تلقائي عند إنشاء الـ Object (تم شرح الـ Constructor في مقال بعنوان مقدمة في Class & Object للغة #C)، في بايثون يتم استخدام الدالة المدمجة __init__ ( self [,args...] ) كا Constructor لنرى المثال التالي:
class Employees:
#create the constructor
def __init__(self, name):
self.name = name
def GetEmployees(self):
print("The employee name is", self.name)
مع الـ Constructor يمكن اسناد القيم مباشرة لحظة انشاء الـ Object، بالطريقة التالية..
...........
#Create the first instance to the Employees class
employee1 = Employees("Nora") #calling the constructor
employee1.GetEmployees()
#Create the second instance
employee2 = Employees("Sara")
employee2.GetEmployees()
...........
في بعض لغات البرمجة يمكن انشاء اكثر من Constructor للـ Class الواحد وهي في هذه الحالة تعمل بمدأ Overload أي أنها تتشابه في الاسم وتختلف في الـ Parameters.المثال التالي يوضح طريقة عمل الـ Constructor في بايثون مع عدد متنوع من البيانات :
class Employees:
def __init__(self, name, employee_id, department, salary):
self.name = name
self.employee_id = employee_id
self.department = department
self.salary = salary
def GetEmployees(self):
print("Employee Name:", self.name)
print("Employee ID:", self.employee_id)
print("Department:", self.department)
print("Salary:", self.salary)
def main():
#Create the instance to the Employees class
employee1 = Employees("Nora", 123, "HR", 12000)
employee1.GetEmployees()
#create the second instance
employee2 = Employees("Sara", 321, "IT", 20000)
employee2.GetEmployees()
if __name__ == '__main__':
main()
نلاحظ ان الكود بهذا الشكل غير مرن حيث لابد من توافر كامل البيانات عند إنشاء الـ Object مما يشكل صعوبة في الأداء والتعديل، فضلاً أن حجم الكود سيكون أكبر مع العمل مع عدد بيانات أكثر.
أحد الطرق المستخدمة مع هذه الحالة في بايثون هو استخدام "kwargs " مع الـ Constructor مما يجعله يستقبل البيانات ويحفظها بصيغة قاموس Dictionary (المزيد في مقال عن البيانات التخطيطية Mapping types في Python القواميس Dictionaries) ويمكننا من اختصار الكود الى الشكل التالي:
class Employees:
def __init__(self,**kwargs):
self.Data = kwargs
def GetEmployees(self):
print("The Employee info:")
for x,y in self.Data.items():
print(x ,":", y)
print("\n")
def main():
employee1 = Employees(name ="Nora", employee_id=123, department="HR", salary= 12000)
employee1.GetEmployees()
employee2 = Employees(name= "Sara", employee_id =321, department= "IT",salary= 20000)
employee2.GetEmployees()
if __name__ == '__main__':
main()
كما نلاحظ مع استخدام kwargs تم استقبال البيانات في الـ الClass على شكل قاموس Dictionary لهذا تم التعامل معها بواسطة الدوال الخاصة بالقواميس.
السمات Attributes
تُعرف السمات (Attributes ) بالخصائص أو المتغيرات (Variables) المحددة داخل الـ Class . توفر الـ Attribute معلومات حول نوع البيانات التي تحتوي عليها الفئة (Class). هناك نوعان من السمات في بايثون وهما Instance Attributes و Class Attributes. أوضحنا مفهوم كلاً منهما أعلاه وفيما يلي أهم الفروق:
Instance Attributes | Class Attributes |
---|---|
يتم تعريف الـ Instance Attributes داخل الـ Constructor. أو داخل اي داله (__init__() function) | يتم تعريفها على مستوى الـ Class اي داخل الـ Class وخارج اي داله حتى الـ Constructor. |
يتم الوصول إليها بواسطة الـObject حيث يتم كتابة سم الـ Object ثم نقطة ثم اسم الـ Instance Attribute. | يمكن الوصول إليها بواسطة أي من اسم الـ Class أو اسم الـ Object . |
القيم للـ Instance Attributes مختلفة من object لآخر. | القيم للـ Class Attributes مشتركة بين الـ objects من Class معين. |
التعديل على قيم الـ Instance Attributes سؤثر فقط على الـ object الذي تمت عليه عملية التعديل. | التعديل على قيم الـ Class Attributes يؤثر على قيمة المتغير في بقية الـ objects من هذا الـ Class. |
يوضح المثال الفرق بين الـ Instance Attributes و الـ Class Attributes:
class Employees:
#Declaring the "employee_count" as class attributes
employee_count = 0
def __init__(self,**kwargs):
#The "kwargs" is an instance attribute
self.Data = kwargs
#Access to class attributes by class name
Employees.employee_count += 1 #make change on class attribute value
def GetEmployees(self):
print("The info of Employee",Employees.employee_count,"is:")
for x,y in self.Data.items():
print(x ,":", y)
print("\n")
...........
...........
#Class attributes can be accessed by both class name and object name.
print("The number of Employees is:", employee2.employee_count)
الدوال أو الأساليب Methods
في بايثون تعتبر الدوال أو الأساليب(Methods) المحرك الأساسي للبرنامج وخصوصاً التي تعمل بنهج البرمجة الموجهة للكائنات OOP . وتنتمي الأساليب (Methods) إلى كائن Object من فئة class وتستخدم لأداء عمليات محددة. ويمكننا تقسيم الدوال أو الأساليب Methods المستخدمة مع الـ Class & Object في نهج OOP في لغة بايثون إلى ثلاث فئات مختلفة وهي:
دالة المثيل (Instance Method):
وهي الدوال التي يتم تعريفها في الـ Class بشكل اعتيادي ويتم الوصول لها واستخدامها بواسطة الـ Objects ، يمكن لهذا النوع من الدوال الوصول إلى متغيرات المثيل ( instance Attributes) الخاصة بكائن معين . يمكنها أيضًا الوصول إلى متغير الفئة (class Attributes) لأنه مشترك بين جميع الكائنات. ويتضح هذا النوع من الدوال في الأمثلة السابقة…
دالة الفئة (Class Method)
دالة الفئة Class method في بايثون هي دوال مرتبطة بالـ Class وليس بمثيل (Instance) الـ class . هذه الدوال يمكن استدعاؤها بإسم الـ Class فقط ، وليس بواسطة الـ Objects .و توفر Python طريقتين لإنشاء دالة من نوع Class method :
1-بواسطة الدالة classmethod()
تحتوي لغة Python على دالة مدمجة classmethod() والتي تقوم بتحويل الـ instance method إلى class method ويتم استدعاؤها بالإشارة إلى الـ class فقط وليس الكائن object .كما يوضح المثال التالي :
class Students:
student_count = 0
def __init__(self,**kwargs):
self.Data = kwargs
Students.student_count += 1
#An instance method
def StudentCount(self):
print("Student Number is :", self.student_count)
#A class method
StCounter = classmethod(StudentCount)
def main():
st1 = Students(Name ="Nora", CoursesNo= 6)
st2 = Students(Name= "Sara", CoursesNo= 5)
st3 = Students(Name= "Reem", CoursesNo= 7)
#calling the instance method
st2.StudentCount()
#clalling the class method
Students.StCounter()
if __name__ == '__main__':
main()
بواسطة @classmthod
استخدام @classmthod وهي الطريقة الأمثل لتعريف class method حيث إنها أكثر ملاءمة من إعلان الدالة كا instance method أولاً ثم تحويلها إلى class method. فيما يلي مثال يوضح طريقة استخدام @classmthod للإعلان عن class method:
class Students:
student_count = 0
def __init__(self,Name , CoursesNo):
self.Name = Name
self.CoursesNo = CoursesNo
Students.student_count += 1
#A class method
@classmethod
def newStudent(cls,Name,CoursesNo):
return cls(Name,CoursesNo)
#The second class method
@classmethod
def StudentCount(cls):
print( "The students number is:",cls.student_count)
def main():
st1 = Students(Name ="Nora", CoursesNo= 6)
st2 = Students(Name= "Sara", CoursesNo= 5)
st3 = Students(Name= "Reem", CoursesNo= 7)
#using the class method
st4 = Students.newStudent("Asma",4)
#clalling the class method
Students.StudentCount()
if __name__ == '__main__':
main()
ملاحظة : في بايثون، تعني "cls" أن الدالة تنتمي إلى الـ class . بينما تعني "self" أن الدالة مرتبطة بمثيل للفئة (Object). وبالتالي يمكن الوصول إلى عناصر البيانات التي تحمل "cls" من خلال اسم الـ Class. من ناحية أخرى، يمكن الوصول إلى عناصر البيانات الذي يحمل "self" بواسطة مثيل instance للفئة (الكائن Object ).
الدالة الثابتة (Static Method)
في بايثون، الدالة الثابتة static method هي نوع من الدوال التي لا تتطلب لإستدعائها إنشاء object ،هي تشبه إلى حد كبير الـ class method ولكن الاختلاف هو أن الـ static method لا تحتوي على argument إلزامية مثل الإشارة إلى الكائن− self أو الإشارة إلى الفئة − cls. وتُستخدم الـ static methods للوصول إلى عناصر البيانات الثابتة (static ) للـ Class معين. وتوفر Python طريقتين لإنشاء دالة ثابتة Static method وهي:
1-بواسطة الدالة staticmethod()
لإنشاء دالة ثابتة static method باستخدام الدالة المدمجة القياسية المسماة staticmethod().التي تقوم بتحويل الـ instance method إلى static method . كما يوضح المثال التالي:
class Students:
student_count = 0
def __init__(self,Name , CoursesNo):
self.Name = Name
self.CoursesNo = CoursesNo
Students.student_count += 1
def StudentCount():
print("Student Number is :", Students.student_count)
#A static method
Stcounter = staticmethod(StudentCount)
def main():
st1 = Students(Name ="Nora", CoursesNo= 6)
st2 = Students(Name= "Sara", CoursesNo= 5)
st3 = Students(Name= "Reem", CoursesNo= 7)
#clalling the static method vis class
Students.Stcounter()
#calling the static method via object
st2.Stcounter()
if __name__ == '__main__':
main()
.
2- بواسطة @staticmethod
الطريقة الثانية لإنشاء دالة ثابتة static method هي استخدام @staticmethod في Python. عندما نستخدم هذه الطريقة مع أي دالة ، فإنه يعطي اشارة الى الـ compiler إلى أن الدالة المحددة هي static method . ويوضح المثال التالي طريقة استخدام @staticmethod:
class Students:
student_count = 0
def __init__(self,Name , CoursesNo):
self.Name = Name
self.CoursesNo = CoursesNo
Students.student_count += 1
#A static method
@staticmethod
def StudentCount():
print("Student Number is :", Students.student_count)
def main():
st1 = Students(Name ="Nora", CoursesNo= 6)
st2 = Students(Name= "Sara", CoursesNo= 5)
st3 = Students(Name= "Reem", CoursesNo= 7)
#clalling the static method vis class
Students.StudentCount()
#calling the static method via object
st2.StudentCount()
if __name__ == '__main__':
main()
خصائص الدالة ثابتة Static Method
- نظرًا لأن ال دالة ثابتة static method لا يمكنها الوصول إلى الـ class attributes,، فيمكن استخدامها كدالة مساعدة لأداء المهام التي يتم إعادة استخدامها بشكل متكرر.
- والدوال الثابتة static methods يمكننا استدعائها باستخدام اسم الفئة class name . وبالتالي، فهي تلغي الاعتماد على الكائنات Objects.
- الدالة الثابتة static method يمكن التنبؤ بها دائمًا حيث يظل سلوكها دون تغيير بغض النظر عن حالة الـ Class .
- يمكننا إعلان أي دالة كدالة ثابتة static method لمنع التجاوز overriding.
ملاحظة : غالبًا ما يخلط معظمنا بين دوال الفئة Class methods و الدوال الثابتة Static Methods . تذكر دائمًا، لان كلاهما يتم استدعائها بواسطة اسم الـ class . ولكن لا تتمتع الـ Static Methods بالوصول إلى "cls" وبالتالي لا يمكنها تعديل حالة الـ Class.
الفئات المتداخلة Inner Classes
في بايثون يٌعرف الـ Class المعرف داخل class أخر باسم الفئة الداخلية (Inner Class). في بعض الأحيان، تُسمى الـ inner class أيضًا بالفئة المتداخلة nested class . إذا تم إنشاء object للـ inner class، يمكن أن يستخدم هذا الـ object بواسطة الفئة الأصلية (الخارجية). يصبح كائن الفئة الداخلية أحد سمات (Attributes) الفئة الخارجية. وتلقائيًا ترث الفئة الداخلية سمات الفئة الخارجية دون إنشاء وراثة رسميًا.
بمعنى ان تكون الفئة الخارجية (Outer class)، مثل حاوية للفئة الداخلية (inner class) ، بحيث يمكن ان تصل الـ inner class لكل عناصر الـ Outer class ، وتعامل الـ inner class كعنصر بيانات تابع للـ outer class . وتكون الصيغة العامة للإنشاء inner class كتالي:
class outer: def __init__(self): pass class inner: def __init__(self): pass
ويوضح المثال التالي كيفية العمل مع الفئات المتداخلة Inner classes في لغة بايثون:
class Organization:
def __init__(self):
#The inner classes are attribute
self.Fin = self.Finance()
self.market = self.Marketing()
def organization(self):
print("This an Organization class")
#An inner class
class Finance:
def finance(self):
print("This is Finance Department")
#An inner class
class Marketing:
def marketing(self):
print("This is Marketing Department")
def main():
# instance of OuterClass
Org = Organization()
Org.organization()
#Instance of innerClasses
Fin = Org.Fin
Fin.finance()
market = Org.market
market.marketing()
if __name__ == '__main__':
main()
وفي بايثون يمكن تقسيم الـ inner classes الى :
فئة داخلية متعددة Multiple Inner Class:
في الفئات الداخلية المتعددة، (Multiple Inner Class) يحتوي الـ class الخارجي الواحد على أكثر من class داخلي. ويعمل كل class داخلي بشكل مستقل ولكن يمكن أن يتفاعل مع الـ Outer class. تماما كما في المثال أعلاه .
فئة داخلية متعددة المستويات Multilevel Inner Class:
يشير إلى فئة داخلية تحتوي على فئة داخلية أخرى، بحيث تنشئ مستويات متعددة من الفئات المتداخلة. والشكل التالي يوضح الفرق بين الأشكال التي يمكن أن يكون عليها الـ inner class :
وفي المثال تطبيق لطريقة العمل مع Multilevel Inner Class:
class Organization:
def __init__(self):
self.Fin = self.Finance()
def organization(self):
print("This an Organization class")
#An inner class
class Finance:
def __init__(self):
self.count = self.Accounting()
def finance(self):
print("This is Finance Department")
#An inner class inside the Finance class
class Accounting:
def accounting(self):
print("This is Accounting Section")
def main():
# instance of Outer Class
Org = Organization()
Org.organization()
#Instance of inner Classes
Fin = Org.Fin
Fin.finance()
#Instance of inner - inner Classes
count = Fin.count
count.accounting()
if __name__ == '__main__':
main()
جميع الأمثلة لهذا المقال متوفرة هنـا.
من خلال فهم الـ OOP في Python، يمكن للمطورين بناء تطبيقات قوية وقابلة للصيانة بشكل أفضل، وزيادة إعادة استخدام الكود وفهمها بشكل أفضل. قمنا في هذا المقال باستعراض المفاهيم الأساسية لهذا الأسلوب وهما Class & Object مع أمثلة تطبيقية توضح كيفية استخدامهما في Python بطريقة بسيطة وفعالة….