الأصناف والكائنات في بايثون: اصنع قالبك الخاص

تعلّم كتابة صنف class في بايثون، وإنشاء كائنات منه، وحفظ بياناتها في خصائص بـ __init__، وكتابة دوال خاصة بها باستخدام self.

تخيّل أنك تصنع لعبة فيها عشرة لاعبين. لكل لاعب اسم ونقاط ومستوى. هل تكتب ثلاثين متغيّرًا وتتعب في تتبّعها؟ في هذا الدرس ستتعلّم طريقة أنيقة: تصنع “قالبًا” واحدًا للاعب، ثم تصنع منه كل اللاعبين الذين تريد.

🎯 ماذا ستتعلم؟

بنهاية هذا الدرس ستكون قادرًا على:

  • كتابة صنف بسيط بكلمة class.
  • إنشاء كائنات من الصنف واستخدامها.
  • حفظ بيانات كل كائن في خصائص باستخدام __init__.
  • كتابة دوال داخل الصنف تعمل بكلمة self.
  • فهم لماذا يملك كل كائن نسخته الخاصة من البيانات.

🧰 ماذا تحتاج قبل أن تبدأ؟

يجب أن تعرف المتغيّرات وأنواع البيانات وأن تكون كتبت دالة من قبل. سنستخدم أيضًا تنسيق النصوص في بعض الأمثلة. كل الأمثلة تعمل في مترجم بايثون في المتصفح بدون تثبيت.

الفكرة ببساطة

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

هذا بالضبط الصنف (class) في بايثون. هو قالب يصف شكل الشيء: ما البيانات التي يحملها، وماذا يستطيع أن يفعل. وكل شيء تصنعه من القالب يسمّى كائنًا (object).

القالب يُكتب مرة واحدة، والكائنات تُصنع منه بأي عدد تريده. وهذا هو أساس ما يسمّى البرمجة الكائنية (object-oriented programming).

الخطوة 1: أبسط صنف ممكن

نكتب الصنف بكلمة class ثم اسمه ثم نقطتين. من العادة أن يبدأ اسم الصنف بحرف كبير. داخل الصنف نضع الأسطر بمسافة بادئة، تمامًا مثل الدالة.

class Player:
    team = "الصقور"

p1 = Player()
print(p1.team)
الصقور

السطر p1 = Player() يصنع كائنًا جديدًا من الصنف. لاحظ القوسين بعد الاسم، فهما يعنيان “اصنع لي واحدًا”. ثم نقرأ ما بداخله بالنقطة: p1.team.

كل ما يحمله الكائن من بيانات يسمّى خاصية (attribute). هنا team خاصية قيمتها “الصقور”.

الخطوة 2: كائنات كثيرة من قالب واحد

قوة الصنف تظهر عندما تصنع منه أكثر من كائن. كل كائن يحمل نسخته الخاصة من الخصائص. إذا غيّرت واحدًا، لا يتأثر الآخر.

class Player:
    team = "الصقور"

p1 = Player()
p2 = Player()
p2.team = "النمور"
print(p1.team)
print(p2.team)
الصقور
النمور

غيّرنا فريق p2 فقط، وبقي p1 كما هو. فكّر في كعكتين من القالب نفسه: إذا وضعت الفراولة على واحدة، لا تظهر على الأخرى.

الخطوة 3: دالة داخل الصنف

الصنف لا يحمل بيانات فقط، بل يحمل أفعالًا أيضًا. الدالة داخل الصنف تسمّى طريقة (method). تكتبها بـ def كأي دالة، لكن معاملها الأول دائمًا self.

class Player:
    team = "الصقور"

    def cheer(self):
        print("هيا يا فريق", self.team)

p1 = Player()
p1.cheer()
هيا يا فريق الصقور

ما هو self؟ هو الكائن نفسه الذي نادى الطريقة. عندما تكتب p1.cheer()، تضع بايثون p1 في مكان self تلقائيًا. لذلك self.team تعني “فريق هذا الكائن بالذات”.

لاحظ أنك لا تكتب self بين القوسين عند النداء. بايثون تفعل ذلك عنك.

الخطوة 4: بيانات مختلفة لكل كائن بـ __init__

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

تُنفَّذ __init__ تلقائيًا في اللحظة التي تصنع فيها كائنًا جديدًا. اسمها بين شرطتين سفليتين من كل جهة. نستخدمها لوضع القيم الأولى في الخصائص.

class Player:
    def __init__(self, name, score):
        self.name = name
        self.score = score

p1 = Player("سالم", 120)
p2 = Player("نورة", 200)
print(p1.name, "عنده", p1.score, "نقطة")
print(p2.name, "عندها", p2.score, "نقطة")
سالم عنده 120 نقطة
نورة عندها 200 نقطة

عندما كتبنا Player("سالم", 120)، ذهب “سالم” إلى name و 120 إلى score. ثم السطر self.name = name حفظ الاسم داخل الكائن نفسه. بدون self قبل الاسم، سيضيع المتغيّر بعد انتهاء الدالة.

🚀 للمتقدمين: الخاصية التي تكتبها مباشرة تحت class (مثل team في الخطوة 1) تسمّى خاصية صنف، وتتشاركها كل الكائنات. أما الخاصية التي تكتبها بـ self. داخل __init__ فهي خاصية كائن، ولكل كائن نسخته. جرّب أن تغيّر Player.team بعد إنشاء كائنين وانظر ماذا يحدث لهما.

الخطوة 5: طرق تغيّر البيانات

الطريقة تستطيع أن تقرأ الخصائص وتغيّرها أيضًا. هنا نصنع لاعبًا يجمع النقاط ويعرض حالته. لاحظ كيف تستخدم الطريقتان self للوصول إلى بيانات الكائن نفسه.

class Player:
    def __init__(self, name, score):
        self.name = name
        self.score = score

    def add_points(self, points):
        self.score = self.score + points

    def show(self):
        print(f"{self.name}: {self.score} نقطة")

p1 = Player("سالم", 120)
p1.show()
p1.add_points(30)
p1.add_points(50)
p1.show()
سالم: 120 نقطة
سالم: 200 نقطة

الطريقة add_points تأخذ معاملًا واحدًا هو points، وتضيفه إلى نقاط الكائن. بعد نداءين ارتفعت النقاط من 120 إلى 200. والطريقة show تطبع الحالة الحالية بتنسيق جميل.

الخطوة 6: الكائنات داخل قائمة

الكائن قيمة مثل أي قيمة أخرى، لذلك تستطيع وضعه في قائمة والمرور عليه بحلقة. هكذا تدير فريقًا كاملًا بأسطر قليلة.

class Player:
    def __init__(self, name, score):
        self.name = name
        self.score = score

    def show(self):
        print(f"{self.name}: {self.score} نقطة")

team = [Player("سالم", 120), Player("نورة", 200), Player("خالد", 90)]
for player in team:
    player.show()
سالم: 120 نقطة
نورة: 200 نقطة
خالد: 90 نقطة

في كل دورة يكون player كائنًا مختلفًا، ونداء player.show() يطبع بياناته هو. هذه هي الفكرة التي وعدناك بها في البداية: قالب واحد، ولاعبون كثيرون بلا فوضى.

🚀 للمتقدمين: إذا طبعت الكائن مباشرة بـ print(p1) سترى شيئًا غريبًا مثل <__main__.Player object at 0x...>. لتجعل بايثون تطبع نصًا مفهومًا، أضف طريقة خاصة اسمها __str__ تعيد نصًا بـ return. بعدها يعمل print(p1) كما تحب.

⚠️ أخطاء شائعة

هذه الأخطاء يقع فيها الجميع في أول لقاء مع الأصناف. تعرّف عليها الآن لتوفّر على نفسك وقت البحث لاحقًا.

1. نسيان self في تعريف الطريقة

❌
class Player:
    def cheer():
        print("هيا")

p1 = Player()
p1.cheer()
TypeError: Player.cheer() takes 0 positional arguments but 1 was given

الرسالة تقول: الطريقة لا تقبل معاملات، لكن بايثون أعطتها واحدًا. هذا المعامل هو الكائن نفسه. الحل أن تكتب self أولًا دائمًا:

# ✅
class Player:
    def cheer(self):
        print("هيا")

p1 = Player()
p1.cheer()
هيا

2. نسيان self. قبل الخاصية

❌
class Player:
    def __init__(self, name):
        name = name

p1 = Player("سالم")
print(p1.name)
AttributeError: 'Player' object has no attribute 'name'

السطر name = name لم يحفظ شيئًا في الكائن، بل أعاد وضع القيمة في المتغيّر المؤقت نفسه. الكائن بقي فارغًا. الحل هو self.name:

# ✅
class Player:
    def __init__(self, name):
        self.name = name

p1 = Player("سالم")
print(p1.name)
سالم

3. نسيان القوسين عند إنشاء الكائن

❌
class Player:
    team = "الصقور"

    def cheer(self):
        print("هيا يا فريق", self.team)

p1 = Player
p1.cheer()
TypeError: Player.cheer() missing 1 required positional argument: 'self'

بدون القوسين، p1 صار اسمًا آخر للقالب نفسه، وليس كائنًا جديدًا. لذلك لم تجد بايثون كائنًا تضعه في self. تذكّر: القوسان يعنيان “اصنع لي واحدًا”.

# ✅
class Player:
    team = "الصقور"

    def cheer(self):
        print("هيا يا فريق", self.team)

p1 = Player()
p1.cheer()
هيا يا فريق الصقور

🧪 جرّب بنفسك

التحدي 1: صنف الطالب

اصنع صنفًا اسمه Student يحفظ الاسم والصف في __init__. ثم أنشئ طالبين واطبع اسم كل واحد وصفّه.

# أكمل الصنف ثم أنشئ طالبين
class Student:
    pass

print("جاهز")
جاهز
💡 اضغط لرؤية الحل
class Student:
    def __init__(self, name, grade):
        self.name = name
        self.grade = grade

s1 = Student("مريم", 7)
s2 = Student("يوسف", 9)
print(s1.name, "في الصف", s1.grade)
print(s2.name, "في الصف", s2.grade)
مريم في الصف 7
يوسف في الصف 9

التحدي 2: الحصّالة

اصنع صنفًا اسمه PiggyBank يبدأ بمبلغ 0. أضف طريقة add تزيد المبلغ، وطريقة show تطبعه. جرّب إضافة 10 ثم 25 ثم اعرض المجموع.

# أكمل الطريقتين add و show
class PiggyBank:
    def __init__(self):
        self.money = 0

bank = PiggyBank()
print(bank.money)
0
💡 اضغط لرؤية الحل
class PiggyBank:
    def __init__(self):
        self.money = 0

    def add(self, amount):
        self.money = self.money + amount

    def show(self):
        print("في الحصّالة:", self.money, "ريالًا")

bank = PiggyBank()
bank.add(10)
bank.add(25)
bank.show()
في الحصّالة: 35 ريالًا

التحدي 3: أفضل لاعب

عندك قائمة من كائنات Player. اكتب حلقة تجد اللاعب صاحب أعلى نقاط واطبع اسمه. لا تستخدم أي دالة جاهزة، بل قارن بنفسك.

class Player:
    def __init__(self, name, score):
        self.name = name
        self.score = score

team = [Player("سالم", 120), Player("نورة", 200), Player("خالد", 90)]
# ابحث عن صاحب أعلى نقاط واطبع اسمه
print(len(team))
3
💡 اضغط لرؤية الحل
class Player:
    def __init__(self, name, score):
        self.name = name
        self.score = score

team = [Player("سالم", 120), Player("نورة", 200), Player("خالد", 90)]
best = team[0]
for player in team:
    if player.score > best.score:
        best = player
print("أفضل لاعب:", best.name, "بـ", best.score, "نقطة")
أفضل لاعب: نورة بـ 200 نقطة

✅ اختبر نفسك

  1. ما الفرق بين الصنف والكائن؟

    • أ) لا فرق بينهما.
    • ب) الصنف قالب، والكائن شيء مصنوع من القالب.
    • ج) الكائن قالب، والصنف شيء مصنوع منه.
  2. ماذا يمثّل self داخل الطريقة؟

    • أ) الكائن الذي نادى الطريقة.
    • ب) اسم الصنف.
    • ج) أول معامل يكتبه المستخدم عند النداء.
  3. صح أم خطأ: __init__ تُنفَّذ تلقائيًا عند إنشاء كائن جديد.

💡 اضغط لرؤية الإجابات
  1. ب). الصنف يصف الشكل، والكائن نسخة حقيقية مصنوعة منه.
  2. أ). بايثون تضع الكائن نفسه في self تلقائيًا، ولا تكتبه أنت عند النداء.
  3. صح. لذلك نستخدمها لوضع القيم الأولى في الخصائص.

📝 الخلاصة

  • الصنف قالب يُكتب بـ class، والكائن نسخة تُصنع منه بالقوسين Player().
  • الخصائص هي بيانات الكائن، وتصل إليها بالنقطة مثل p1.name.
  • الطريقة دالة داخل الصنف، ومعاملها الأول دائمًا self.
  • __init__ تعمل تلقائيًا عند الإنشاء، واستخدم self. لحفظ القيم داخل الكائن.
  • كل كائن مستقل عن غيره، ويمكنك جمع الكائنات في قائمة والمرور عليها بحلقة.

⏭️ الخطوة التالية

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