إنشاء الأصناف (Classes) في Python
في الدرس السابق، فهمنا لماذا نحتاج للبرمجة الكائنية. الآن حان الوقت للعمل! في هذا الدرس، سنتعلم كيف ننشئ أول صنف (Class) لنا في بايثون. سنبدأ من الصفر المطلق، ونبني صنفاً بسيطاً، ثم نضيف له الخصائص والأساليب تدريجياً. بنهاية هذا الدرس، ستكون قادراً على كتابة أصناف احترافية تحاكي الواقع وتحل مشاكل حقيقية.
1. البنية الأساسية لإنشاء صنف (Class)
ما هو الصنف بالضبط؟
قبل أن نبدأ بكتابة الكود، دعنا نفهم المفهوم بعمق. الصنف (Class) هو قالب أو مخطط يحدد:
- ما هي البيانات التي سيحملها الكائن (الخصائص - Attributes)
- ما هي الأفعال التي يمكن للكائن القيام بها (الأساليب - Methods)
فكر في الصنف كأنه "استمارة فارغة" تحدد الحقول المطلوبة (الاسم، العمر، العنوان، إلخ). عندما تملأ هذه الاستمارة ببيانات حقيقية، تحصل على "كائن" (Object) فعلي.
الصيغة الأساسية لتعريف صنف
لإنشاء صنف في بايثون، نستخدم الكلمة المفتاحية class متبوعة باسم الصنف ونقطتين :. الصيغة العامة:
# الصيغة الأساسية
class ClassName:
# محتوى الصنف هنا
pass
# مثال بسيط: صنف فارغ
class Dog:
pass
# إنشاء كائن من الصنف
my_dog = Dog()
print(type(my_dog)) #
print(my_dog) # <__main__.Dog object at 0x...>
شرح الكود سطراً بسطر:
- السطر 7: نعرّف صنفاً اسمه
Dog(الكلب). اسم الصنف يبدأ بحرف كبير (PascalCase) حسب الاتفاقية البرمجية. - السطر 8:
passتعني "لا تفعل شيئاً"، نستخدمها عندما يكون الصنف فارغاً مؤقتاً (كأننا نقول: سأملأ هذا لاحقاً). - السطر 11: ننشئ كائناً (Object) من الصنف باستخدام
Dog()- لاحظ الأقواس! هذه الأقواس تخبر بايثون: "أنشئ نسخة جديدة من هذا الصنف". - السطر 12:
type()تخبرنا أنmy_dogهو من نوعDog. - السطر 13: عند طباعة الكائن، بايثون تخبرنا أنه كائن من نوع Dog موجود في عنوان معين في الذاكرة.
- استخدم PascalCase (كل كلمة تبدأ بحرف كبير):
Student,BankAccount,ShoppingCart - اجعل الاسم وصفياً يعبر عن ماهية الكائن (استخدم أسماء الأشياء، وليس الأفعال)
- تجنب الأسماء العامة جداً مثل
DataأوInfoأوThing - استخدم الأسماء بصيغة المفرد:
BookوليسBooks
مثال بسيط جداً: صنف الكتاب
لنبدأ بمثال أبسط من الكلب. لنتخيل أننا نريد تمثيل كتاب في مكتبة:
# تعريف صنف بسيط للكتاب
class Book:
pass
# إنشاء كتاب
my_book = Book()
# الآن my_book هو كائن من نوع Book
print(f"نوع المتغير: {type(my_book)}")
print(f"هل my_book كائن من Book؟ {isinstance(my_book, Book)}")
في هذه المرحلة، الصنف فارغ تماماً - ليس له أي خصائص أو وظائف. إنه مجرد "قالب فارغ". لكن هذا هو أساس كل شيء!
2. إضافة الخصائص (Attributes) للصنف
ما هي الخصائص؟
الخصائص (Attributes) هي المتغيرات التي تحمل البيانات داخل الصنف. إنها تصف "حالة" الكائن. مثلاً:
- كتاب له: عنوان، مؤلف، عدد صفحات، سعر
- سيارة لها: لون، موديل، سرعة قصوى، رقم لوحة
- طالب له: اسم، عمر، درجات، تخصص
هناك نوعان من الخصائص في بايثون، ولكل منهما استخدام مختلف:
أ) خصائص الصنف (Class Attributes) - البيانات المشتركة
هذه خصائص مشتركة بين جميع الكائنات المنشأة من الصنف. تُعرّف مباشرة داخل الصنف (وليس داخل أي دالة). فكر فيها كـ "إعدادات افتراضية" أو "معلومات ثابتة" تنطبق على الجميع.
class Dog:
# خصائص على مستوى الصنف (مشتركة بين كل الكلاب)
species = "Canis familiaris" # كل الكلاب من نفس الفصيلة
legs = 4 # كل الكلاب لها 4 أرجل
is_mammal = True # كل الكلاب من الثدييات
# إنشاء كائنات
dog1 = Dog()
dog2 = Dog()
dog3 = Dog()
# كل الكائنات تشارك نفس القيم
print(f"كلب 1 - الفصيلة: {dog1.species}, الأرجل: {dog1.legs}")
print(f"كلب 2 - الفصيلة: {dog2.species}, الأرجل: {dog2.legs}")
print(f"كلب 3 - الفصيلة: {dog3.species}, الأرجل: {dog3.legs}")
# يمكن الوصول للخاصية من الصنف نفسه (بدون إنشاء كائن)
print(f"\nمن الصنف مباشرة: {Dog.species}")
متى نستخدم Class Attributes؟
- القيم الثابتة: عندما تكون القيمة ثابتة لجميع الكائنات (مثل عدد الأرجل للكلاب)
- الإعدادات الافتراضية: قيم افتراضية يمكن تغييرها لاحقاً لكل كائن
- العدادات: لحساب عدد الكائنات المنشأة (سنرى مثالاً لاحقاً)
- الثوابت: قيم لا تتغير أبداً (مثل الحد الأقصى للسرعة في لعبة)
مثال عملي: عداد الطلاب
لنرى كيف يمكننا استخدام خاصية صنف لحساب عدد الطلاب المسجلين:
class Student:
# عداد مشترك لجميع الطلاب
total_students = 0
school_name = "مدرسة النجاح"
# إنشاء طلاب
student1 = Student()
student2 = Student()
student3 = Student()
# الوصول للعداد المشترك
print(f"عدد الطلاب الحالي: {Student.total_students}")
print(f"اسم المدرسة: {Student.school_name}")
# يمكن الوصول أيضاً من خلال أي كائن
print(f"من خلال student1: {student1.school_name}")
3. إضافة الأساليب (Methods) للصنف
ما هي الأساليب؟
الأساليب (Methods) هي دوال تعيش داخل الصنف وتحدد ما يمكن للكائن فعله. إنها "السلوكيات" أو "الأفعال" التي يقوم بها الكائن. الفرق الرئيسي بين الدالة العادية والأسلوب:
- الدالة العادية: مستقلة، تعمل على بيانات تُمرر لها كمعاملات
- الأسلوب (Method): ينتمي لصنف، ويعمل على بيانات الكائن نفسه
كل أسلوب داخل الصنف يجب أن يبدأ بمعامل خاص اسمه self. هذا المعامل يمثل الكائن الحالي الذي يستدعي الأسلوب.
class Dog:
species = "Canis familiaris"
# أسلوب (Method) - دالة داخل الصنف
def bark(self):
print("واف واف!")
def sleep(self):
print("الكلب نائم...")
def eat(self):
print("الكلب يأكل طعامه")
# إنشاء كائن
my_dog = Dog()
# استدعاء الأساليب
my_dog.bark() # واف واف!
my_dog.sleep() # الكلب نائم...
my_dog.eat() # الكلب يأكل طعامه
شرح الكود:
- السطر 5: نعرّف أسلوباً اسمه
bark. لاحظ المعاملself- هو إلزامي! - السطر 18: نستدعي الأسلوب باستخدام
my_dog.bark(). لاحظ أننا لم نمررselfيدوياً - بايثون تفعل ذلك تلقائياً!
كل أسلوب داخل الصنف يجب أن يبدأ بمعامل self. هذا المعامل يمثل الكائن الحالي الذي استدعى الأسلوب. عندما تكتب my_dog.bark()، بايثون تحول هذا داخلياً إلى Dog.bark(my_dog). سنفهم self بعمق أكبر في درس مخصص.
أساليب تستقبل معاملات
الأساليب يمكنها استقبال معاملات إضافية (بعد self) تماماً مثل الدوال العادية:
class Calculator:
def add(self, a, b):
result = a + b
print(f"{a} + {b} = {result}")
return result
def multiply(self, a, b):
result = a * b
print(f"{a} × {b} = {result}")
return result
# إنشاء آلة حاسبة
calc = Calculator()
# استخدام الأساليب
calc.add(5, 3) # 5 + 3 = 8
calc.multiply(4, 7) # 4 × 7 = 28
# يمكننا حفظ النتيجة
total = calc.add(10, 20)
print(f"المجموع المحفوظ: {total}")
4. إنشاء كائنات متعددة من نفس الصنف
القوة الحقيقية للأصناف
القوة الحقيقية للأصناف تظهر عندما ننشئ عدة كائنات من نفس الصنف. كل كائن مستقل تماماً عن الآخر، له عنوانه الخاص في الذاكرة، ويمكنه تنفيذ أساليبه بشكل مستقل.
class Dog:
def bark(self):
print("واف واف!")
def introduce(self):
print("أنا كلب")
# إنشاء 3 كلاب مختلفة
dog1 = Dog()
dog2 = Dog()
dog3 = Dog()
# كل كائن مستقل
print("الكلب الأول:")
dog1.bark()
print("\nالكلب الثاني:")
dog2.introduce()
print("\nالكلب الثالث:")
dog3.bark()
# التحقق من أن كل كائن مختلف
print(f"\nهل dog1 و dog2 نفس الكائن؟ {dog1 == dog2}") # False
print(f"عنوان dog1 في الذاكرة: {id(dog1)}")
print(f"عنوان dog2 في الذاكرة: {id(dog2)}")
print(f"هل العناوين مختلفة؟ {id(dog1) != id(dog2)}") # True
ملاحظات مهمة:
- كل كائن له عنوان فريد في الذاكرة (يمكن الحصول عليه بـ
id()) - حتى لو كانت الكائنات من نفس الصنف، فهي مستقلة تماماً
- يمكنك إنشاء عدد لا نهائي من الكائنات من نفس الصنف
5. مثال عملي كامل: صنف الحساب البنكي
بناء صنف واقعي خطوة بخطوة
لنبني صنفاً أكثر واقعية يمثل حساباً بنكياً بسيطاً. سنبدأ بسيطاً ثم نضيف المزيد من الوظائف:
class BankAccount:
# خاصية على مستوى الصنف
bank_name = "البنك الوطني"
interest_rate = 0.02 # معدل فائدة 2%
# أسلوب لعرض معلومات البنك
def show_bank_info(self):
print(f"مرحباً بك في {self.bank_name}")
print(f"معدل الفائدة السنوي: {self.interest_rate * 100}%")
# أسلوب لعرض رسالة ترحيبية
def welcome(self):
print("شكراً لاختيارك خدماتنا البنكية")
print("نحن هنا لخدمتك على مدار الساعة")
# إنشاء حسابين
print("=== الحساب الأول ===")
account1 = BankAccount()
account1.show_bank_info()
account1.welcome()
print("\n=== الحساب الثاني ===")
account2 = BankAccount()
account2.show_bank_info()
# الوصول للخاصية المشتركة
print(f"\nاسم البنك من الصنف مباشرة: {BankAccount.bank_name}")
ملاحظة: هذا المثال بسيط جداً. في الدرس القادم، سنتعلم كيف نضيف خصائص فريدة لكل حساب (مثل اسم المالك والرصيد) باستخدام الدالة السحرية __init__.
6. الفرق بين الصنف والكائن (فهم عميق)
تشبيه شامل
لفهم الفرق بشكل عميق، دعنا نستخدم عدة تشبيهات:
| الصنف (Class) | الكائن (Object) |
|---|---|
| المخطط / القالب / التصميم | النسخة الحقيقية / المنتج النهائي |
| يُعرّف مرة واحدة فقط | يمكن إنشاء عدد لا نهائي منه |
| يحدد الخصائص والأساليب (ماذا يمكن أن يكون) | يحمل قيماً فعلية للخصائص (ما هو فعلاً) |
class Dog: |
my_dog = Dog() |
| مثل "وصفة الكعكة" | مثل "الكعكة الجاهزة" |
| مثل "استمارة فارغة" | مثل "استمارة مملوءة" |
| مثل "قالب الكوكيز" | مثل "كوكيز مخبوز" |
مثال توضيحي: قالب الكوكيز
تخيل أن لديك قالب كوكيز على شكل نجمة:
- القالب (الصنف) يحدد الشكل: نجمة بخمس رؤوس، قطر 5 سم
- يمكنك استخدام هذا القالب لصنع 100 كوكيز (100 كائن)
- كل كوكيز (كائن) له نفس الشكل، لكن يمكن أن يكون له لون مختلف، أو نكهة مختلفة
- القالب نفسه لا يمكنك أكله، لكن الكوكيز المصنوع منه يمكنك أكله
class CookieMold:
"""قالب الكوكيز - الصنف"""
shape = "نجمة"
size = "5 سم"
def describe(self):
print(f"هذا كوكيز على شكل {self.shape} بحجم {self.size}")
# صنع كوكيز من القالب - إنشاء كائنات
cookie1 = CookieMold()
cookie2 = CookieMold()
cookie3 = CookieMold()
# كل كوكيز له نفس الشكل (من نفس القالب)
cookie1.describe()
cookie2.describe()
cookie3.describe()
# لكن كل كوكيز كائن مستقل
print(f"\nعدد الكوكيز المصنوع: 3")
print(f"هل cookie1 و cookie2 نفس الكوكيز؟ {cookie1 is cookie2}")
7. أخطاء شائعة عند إنشاء الأصناف
الخطأ 1: نسيان self في الأساليب
هذا من أكثر الأخطاء شيوعاً للمبتدئين. كل أسلوب داخل الصنف يجب أن يبدأ بـ self:
# خطأ شائع
class Dog:
def bark(): # نسينا self!
print("واف واف!")
dog = Dog()
dog.bark() # TypeError: bark() takes 0 positional arguments but 1 was given
# الصحيح
class Dog:
def bark(self): # يجب إضافة self
print("واف واف!")
dog = Dog()
dog.bark() # يعمل بشكل صحيح
لماذا يحدث هذا الخطأ؟ عندما تكتب dog.bark()، بايثون تمرر dog تلقائياً كأول معامل. إذا لم تضع self، ستحاول بايثون تمرير معامل لدالة لا تستقبل معاملات!
الخطأ 2: نسيان الأقواس عند إنشاء الكائن
class Dog:
def bark(self):
print("واف واف!")
# خطأ - بدون أقواس
dog = Dog # هذا يشير للصنف نفسه، وليس كائناً!
print(type(dog)) #
# محاولة استدعاء الأسلوب ستفشل
# dog.bark() # AttributeError
# الصحيح - مع أقواس
dog = Dog() # الآن أنشأنا كائناً
print(type(dog)) #
dog.bark() # يعمل بشكل صحيح
الخطأ 3: استخدام أسماء محجوزة للأصناف
تجنب استخدام أسماء الأصناف المدمجة في بايثون:
# خطأ - أسماء محجوزة
class list: # list اسم محجوز
pass
class str: # str اسم محجوز
pass
class int: # int اسم محجوز
pass
# الصحيح - استخدم أسماء وصفية
class StudentList:
pass
class CustomString:
pass
class IntegerWrapper:
pass
الخطأ 4: الخلط بين خصائص الصنف وخصائص الكائن
هذا خطأ دقيق لكنه مهم جداً:
class Counter:
count = 0 # خاصية صنف (مشتركة)
def increment(self):
# خطأ: هذا ينشئ خاصية كائن جديدة!
self.count = self.count + 1
c1 = Counter()
c2 = Counter()
c1.increment()
print(f"c1.count: {c1.count}") # 1
print(f"c2.count: {c2.count}") # 0 (لم يتأثر!)
print(f"Counter.count: {Counter.count}") # 0 (لم يتأثر!)
# الصحيح: إذا أردت تعديل خاصية الصنف
class Counter:
count = 0
def increment(self):
Counter.count += 1 # تعديل خاصية الصنف
8. نصائح وأفضل الممارسات
1. اجعل الأصناف بسيطة ومركزة
كل صنف يجب أن يمثل شيء واحد فقط. لا تحاول جمع كل شيء في صنف واحد:
- جيد:
class User,class Product,class Order - سيء:
class EverythingManager
2. استخدم أسماء واضحة ووصفية
اسم الصنف يجب أن يخبرك فوراً بما يمثله:
- جيد:
class EmailValidator,class PaymentProcessor - سيء:
class Helper,class Manager
3. أضف توثيق للأصناف
استخدم Docstrings لشرح الغرض من الصنف:
class BankAccount:
"""
صنف يمثل حساباً بنكياً بسيطاً.
يوفر وظائف أساسية مثل الإيداع والسحب
وعرض الرصيد الحالي.
"""
def deposit(self, amount):
"""إيداع مبلغ في الحساب"""
pass
ملخص الدرس
- نستخدم
classلتعريف صنف جديد - اسم الصنف يجب أن يبدأ بحرف كبير (PascalCase)
- Class Attributes: خصائص مشتركة بين جميع الكائنات، تُعرّف مباشرة داخل الصنف
- Methods: دوال داخل الصنف، يجب أن تبدأ بـ
self - ننشئ كائناً باستخدام
ClassName()مع الأقواس - كل كائن مستقل عن الآخر، حتى لو كانا من نفس الصنف
- الصنف هو القالب/المخطط، والكائن هو النسخة الحقيقية
الخطوة التالية
تعلمنا كيف ننشئ صنفاً بخصائص مشتركة. لكن ماذا لو أردنا أن يكون لكل كائن خصائصه الفريدة؟ هنا يأتي دور الدالة السحرية __init__!