الأصناف ونموذج الكائنات
درس 1 من 5
1الأصناف ونموذج الكائنات (Classes & Object Model)
الصورة الكبيرةكيف تصنع نوعك الخاص، والفرق بين الصنف والكائن، وما الذي يفعله __init__ و self فعليًا في الذاكرة.
ماذا تشاهد في الرسم: من قالب Counter نصنع a ثم b، ولكل واحد count خاص به. عندما ننادي a.tick() يتغيّر a وحده ويبقى b صفرًا.
خارطة هذا الجزء
المفاهيم بالترتيب، اضغط أيًّا منها للقفز إليه:
طريقان إلى نفس الفكرة
الصنف مثل قالب الكعك، والكائن كعكة خرجت منه. القالب واحد، لكن كل كعكة لها حشوتها الخاصة. تعرّف في القالب ما تملكه كل كعكة (اللون، الحجم) وما تستطيع فعله، ثم تصنع منه ما تشاء من الكعكات.
class يعرّف نوعًا جديدًا، و__init__ يجهّز حالة الكائن عند إنشائه، وself يشير إلى الكائن الحالي فتخزّن فيه السمات self.name. الدوال داخل الصنف توابع methods تعمل على حالة كائنها، وكل كائن في الذاكرة مستقل بسماته عن غيره.
قبل أن تبدأ
🧩الدوال
تعرّف دالة بمُدخلات وتعيد قيمة.
📚القاموس
مجموعة بيانات بأسماء، وهي أقرب شيء للكائن قبل أن تتعلّمه.
الفكرة الجوهرية
الصنف قالب تصنع منه أشياء متشابهة، لكل شيء بياناته الخاصة. قبل كلمات مثل «الكائن» و«السمة»، اصنع طالبين وانظر ماذا يحدث.
1قالب وشيئان منه
class تعرّف القالب. __init__ دالة تعمل تلقائيًّا عند صنع شيء جديد، وself هو ذلك الشيء نفسه. self.name = name تحفظ الاسم داخله.
class Student:
def __init__(self, name, grade):
self.name = name
self.grade = grade
first = Student("سارة", 95)
second = Student("خالد", 80)
print(first.name, first.grade)
print(second.name, second.grade)
صنعنا طالبين من قالب واحد، ولكل واحد اسمه ودرجته. هذا هو الفرق بين الصنف (القالب) والكائن (الشيء المصنوع منه).
2دالة تعيش داخل الصنف
الدالة داخل الصنف تسمّى طريقة، وأول مُدخل لها دائمًا self لتصل إلى بيانات الكائن. تناديها بالنقطة: الكائن ثم نقطة ثم اسم الطريقة.
class Student:
def __init__(self, name, grade):
self.name = name
self.grade = grade
def passed(self):
return self.grade >= 60
s = Student("نورة", 58)
print(s.passed())
s.grade = 70
print(s.passed())
غيّرنا الدرجة فتغيّر جواب passed. الطريقة تقرأ البيانات الحالية في لحظة النداء.
3كل كائن يحمل حالته
عدّادان من نفس القالب. زدنا الأول مرتين والثاني مرة، فاختلفا. لو كانت البيانات مشتركة لما حدث ذلك.
class Counter:
def __init__(self):
self.count = 0
def tick(self):
self.count += 1
a = Counter()
b = Counter()
a.tick()
a.tick()
b.tick()
print(a.count, b.count)
هذه هي الفكرة التي يُبنى عليها كل ما بعدها: البيانات والسلوك معًا في وحدة واحدة، ولكل نسخة حالتها.
self في تعريف الطريقة، مثل def passed():. عند النداء s.passed() يمرّر بايثون الكائن تلقائيًّا كأول مُدخل، فتأتيك رسالة تقول إن الدالة أخذت مُدخلًا لم تتوقّعه. الحل أن يكون self أول مُدخل دائمًا.كيف يعمل، خطوة بخطوة: ماذا يحدث في سطر s = Student("سارة", 95)
بايثون يصنع كائنًا فارغًا من الصنف Student.
ينادي __init__ ويمرّر الكائن نفسه في self.
self.name = name يحفظ القيمة داخل هذا الكائن وحده.
يعود الكائن الجاهز ويُربط بالاسم s.
أعمق قليلًا
في بايثون كل شيء كائن: الأرقام والنصوص والقوائم كلها نُسخ (instances) من أصناف (classes) معرَّفة مسبقًا. هذا الجزء يعلّمك كيف تصنع أصنافك الخاصة.
- owner: str
- __balance: float (خاص)
+
deposit(amount)+
withdraw(amount)+ __str__()
صنف واحد ← عدد غير محدود من الكائنات (حسابات بنكية مختلفة)، كل واحد له حالته المستقلة.
__init__
الميثود التي تُستدعى تلقائيًا عند إنشاء كائن جديد؛ تُهيّئ خصائصه الابتدائية.
التغليف (Encapsulation)
إخفاء تفاصيل داخلية عن الخارج (بشرطة سفلية _ أو مزدوجة __) وإتاحة الوصول فقط عبر ميثودات محكومة تتحقق من صحة القيمة. الشرطة الواحدة مجرد اتفاق ضمني بين المبرمجين ("لا تلمس هذا من الخارج") ولا تمنع الوصول فعليًا؛ أما الشرطة المزدوجة فتُفعّل آلية Name Mangling: بايثون تُعيد تسمية __balance داخليًا إلى _BankAccount__balance، فيفشل الوصول إليها بالاسم المباشر من الخارج، ليست خصوصية حقيقية كما في لغات أخرى، بل عقبة متعمّدة تكفي لردع الوصول العرضي.
__str__ مقابل __repr__
__str__ نص "ودود" للمستخدم النهائي (يستخدمه print()). __repr__ نص دقيق للمطوّرين يُفضَّل أن يكون قابلاً لإعادة إنشاء الكائن منه.
class BankAccount:
"""يمثّل حسابًا بنكيًا بسيطًا برصيد محمي."""
def __init__(self, owner, balance=0):
self.owner = owner
self.__balance = balance # خاص: لا يُعدَّل مباشرة من الخارج
def deposit(self, amount):
if amount <= 0:
raise ValueError("مبلغ الإيداع يجب أن يكون موجبًا")
self.__balance += amount
def withdraw(self, amount):
if amount > self.__balance:
raise ValueError("الرصيد غير كافٍ")
self.__balance -= amount
def __str__(self):
return f"حساب {self.owner}: {self.__balance} ريال"
acc = BankAccount("سارة", 100)
acc.deposit(50)
print(acc) # يستدعي __str__ تلقائيًا -> "حساب سارة: 150 ريال"
# acc.__balance # لا يعمل مباشرة من الخارج بهذا الاسم بسبب التغليف
__balance بدل تركه عامًا؟
لو كان self.balance عامًا، يمكن لأي كود خارجي تعيينه لقيمة سالبة مباشرة (acc.balance = -500) بدون أي تحقق. التغليف يجبر كل تعديل على المرور عبر ميثود تتحقق من المنطق أولاً.
اختبر فهمك: متى تحتاج __repr__ منفصلة عن __str__؟
عندما تريد نصًا مختلفًا لغرضين مختلفين: __str__ لعرض ودّي للمستخدم (كإيصال)، و __repr__ لعرض تقني دقيق أثناء التصحيح (Debugging) يوضّح كل القيم الداخلية بدقة، مثل BankAccount(owner='سارة', balance=150).
مثال محلول
Account فيه self._balance = 0 وطريقة deposit(self, amount) ترفع ValueError إذا كان المبلغ صفرًا أو أقل، وإلا تضيفه. كل تعديل يمر من باب واحد يتحقق منه.خرافة مقابل حقيقة
| الخرافة | الحقيقة |
|---|---|
| الصنف والكائن اسمان لشيء واحد. | الصنف هو القالب، والكائن نسخة مصنوعة منه. من صنف واحد تصنع ما شئت من الكائنات، لكل واحد بياناته. |
self كلمة محجوزة سحرية. | هو مجرد اسم لأول مُدخل، وبايثون يمرّر فيه الكائن تلقائيًا. الاسم متعارف عليه لا إجباري. |
__balance بشرطتين خاص تمامًا ولا يُلمس. | بايثون يغيّر اسمه داخليًا فقط ليصعب الوصول العرضي إليه. هو اتفاق محترم، لا قفل. |
جرّب بنفسك
صنعت a = Counter() وb = Counter() ثم a.tick() ثلاث مرات. ما قيمة b.count؟
كل كائن يحمل count خاصًّا به أُنشئ في __init__. زيادة a لا تمسّ b، فتبقى قيمته صفرًا كما بدأت.
بطاقات مراجعة سريعة
تحقق من فهمك
كائنان من صنف واحد: هل يتشاركان قيمة self.count؟
لا، لكل كائن نسخته، وتغيير واحد لا يمسّ الآخر.
ما الذي يستدعيه print(obj)؟
طريقة __str__ إن وُجدت، وإلا __repr__.
لماذا نغلّف الرصيد بدل تركه عامًا؟
حتى يمر كل تعديل بطريقة تتحقق منه، فلا يصير الرصيد سالبًا بسطر واحد من الخارج.
مصادر هذا الدرس
مرتّبة بالنوع. ابدأ بالتوثيق الرسمي إذا تبي المرجع الدقيق، وبالمقال أو الفيديو إذا تبي الشرح أولًا.
توثيق رسمي3
خلّصت الدرس؟
سجّل الدخول ليُحفظ تقدّمك.