الدوال البانية (Constructors) في Python: فهم __init__

في الدرس السابق، تعلمنا كيف ننشئ صنفاً بسيطاً، لكننا لاحظنا أن جميع الكائنات كانت تشترك في نفس القيم. في الواقع، نحن نريد لكل كائن أن يكون له بياناته الخاصة؛ فكل طالب له اسم مختلف، وكل سيارة لها لون مختلف، وكل حساب بنكي له رصيد مختلف. هنا يأتي دور الدالة السحرية __init__، وهي بمثابة "المُنشئ" (Constructor) الذي يمنح كل كائن هويته الفريدة لحظة ولادته.

1. ما هي الدالة __init__؟

التعريف والغرض

الدالة __init__ (تُنطق "init" أو "دَندَر init") هي دالة خاصة جداً في بايثون. الاسم يأتي من كلمة "Initialize" أي "تهيئة". هذه الدالة:

  • تُستدعى تلقائياً وبشكل فوري بمجرد إنشاء كائن جديد من الصنف
  • لا تحتاج أن تستدعيها يدوياً - بايثون تفعل ذلك من تلقاء نفسها
  • وظيفتها الأساسية هي تهيئة (Initialize) خصائص الكائن الجديد بالقيم التي نمررها له
  • تُسمى أحياناً "Constructor" (المُنشئ) لأنها تُنشئ الحالة الأولية للكائن

فكر في __init__ كأنها "استمارة بيانات" يجب ملؤها عند تسجيل أي عضو جديد في نادٍ ما. بدون هذه الاستمارة، لن يكون للعضو أي بيانات شخصية.

مثال بسيط جداً

لنبدأ بأبسط مثال ممكن لفهم كيف تعمل __init__:

init_basic_example.py
class Robot:
    def __init__(self, name):
        # هذه الدالة تعمل فوراً عند إنشاء الروبوت
        self.name = name
        print(f"تم إنشاء الروبوت {self.name} بنجاح!")

# عند إنشاء الكائن، نمرر القيم مباشرة بين الأقواس
robot1 = Robot("رعد")
robot2 = Robot("برق")

# الوصول لبيانات كل روبوت
print(f"\nالروبوت الأول اسمه: {robot1.name}")
print(f"الروبوت الثاني اسمه: {robot2.name}")
النتيجة
تم إنشاء الروبوت رعد بنجاح! تم إنشاء الروبوت برق بنجاح! الروبوت الأول اسمه: رعد الروبوت الثاني اسمه: برق

ماذا حدث بالضبط؟

  1. عندما كتبنا Robot("رعد")، بايثون فعلت الآتي:
    • أنشأت كائناً جديداً في الذاكرة
    • استدعت __init__ تلقائياً
    • مررت "رعد" كمعامل للدالة
    • حفظت "رعد" في خاصية name داخل الكائن
  2. الآن robot1 و robot2 كائنان مختلفان، كل منهما له اسمه الخاص

2. فهم المعامل self بعمق

ما هو self بالضبط؟

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

قاعدة ذهبية:

self هو مرجع (reference) يشير إلى الكائن الذي استدعى الدالة. عندما تكتب robot1 = Robot("رعد")، فإن self داخل __init__ يشير إلى robot1 نفسه.

تشبيه بسيط لفهم self

تخيل أنك في مطعم، والنادل يسألك: "ماذا تريد أن تأكل؟". عندما تجيبه "أريد بيتزا"، النادل يفهم أن الطلب يخصك أنت (self) وليس الشخص الجالس في الطاولة المجاورة.

في البرمجة، عندما يكون لديك 10 روبوتات، وتقول لأحدهم "احفظ اسمك"، كيف يعرف الروبوت أنك تقصده هو وليس روبوتاً آخر؟ الجواب: self!

understanding_self.py
class Person:
    def __init__(self, name, age):
        # self.name تعني: "اسم هذا الشخص بالذات"
        self.name = name
        # self.age تعني: "عمر هذا الشخص بالذات"
        self.age = age
        
        # يمكننا طباعة معلومات عن الكائن الحالي
        print(f"تم إنشاء شخص: الاسم={self.name}, العمر={self.age}")

# إنشاء شخصين
person1 = Person("أحمد", 25)
person2 = Person("سارة", 30)

# كل شخص له بياناته الخاصة
print(f"\nالشخص الأول: {person1.name}, {person1.age} سنة")
print(f"الشخص الثاني: {person2.name}, {person2.age} سنة")
النتيجة
تم إنشاء شخص: الاسم=أحمد, العمر=25 تم إنشاء شخص: الاسم=سارة, العمر=30 الشخص الأول: أحمد, 25 سنة الشخص الثاني: سارة, 30 سنة
لماذا self.name = name وليس فقط name = name؟

هذا سؤال مهم جداً! دعنا نفهم الفرق:

  • name (بدون self): متغير محلي داخل الدالة فقط، سيختفي بمجرد انتهاء الدالة
  • self.name: خاصية تنتمي للكائن، ستبقى موجودة طوال حياة الكائن
self_vs_local.py
class BadExample:
    def __init__(self, name):
        # خطأ: name هنا متغير محلي فقط
        name = name
        # لم نحفظه في الكائن!

class GoodExample:
    def __init__(self, name):
        # صحيح: حفظنا القيمة في الكائن
        self.name = name

# المثال السيء
bad = BadExample("أحمد")
# print(bad.name)  # خطأ! AttributeError: 'BadExample' object has no attribute 'name'

# المثال الجيد
good = GoodExample("أحمد")
print(good.name)  # يعمل بشكل صحيح: أحمد

3. تمرير معاملات متعددة إلى __init__

بناء كائنات أكثر تعقيداً

في الواقع، الكائنات تحتاج لأكثر من معامل واحد. يمكنك تمرير أي عدد من المعاملات لـ __init__:

multiple_parameters.py
class Car:
    def __init__(self, brand, model, year, color):
        self.brand = brand    # الماركة
        self.model = model    # الموديل
        self.year = year      # سنة الصنع
        self.color = color    # اللون
        
    def display_info(self):
        print(f"سيارة {self.brand} {self.model}")
        print(f"سنة الصنع: {self.year}")
        print(f"اللون: {self.color}")

# إنشاء سيارة بأربع معاملات
car1 = Car("تويوتا", "كامري", 2023, "أبيض")
car2 = Car("هوندا", "أكورد", 2022, "أسود")

# عرض معلومات السيارات
print("=== السيارة الأولى ===")
car1.display_info()

print("\n=== السيارة الثانية ===")
car2.display_info()
النتيجة
=== السيارة الأولى === سيارة تويوتا كامري سنة الصنع: 2023 اللون: أبيض === السيارة الثانية === سيارة هوندا أكورد سنة الصنع: 2022 اللون: أسود
ترتيب المعاملات مهم!

عند استدعاء __init__، يجب تمرير المعاملات بنفس الترتيب الذي عُرّفت به:

class Book:
    def __init__(self, title, author, pages):
        self.title = title
        self.author = author
        self.pages = pages

# صحيح - الترتيب صحيح
book1 = Book("تعلم Python", "أحمد علي", 300)

# خطأ - الترتيب خاطئ (سيعمل لكن البيانات ستكون مشوشة)
book2 = Book(250, "تعلم Java", "محمد حسن")  # الصفحات في مكان العنوان!

print(f"كتاب 1: {book1.title} - {book1.pages} صفحة")
print(f"كتاب 2: {book2.title} - {book2.pages} صفحة")  # بيانات خاطئة!

4. القيم الافتراضية في __init__

جعل بعض المعاملات اختيارية

مثل الدوال العادية تماماً، يمكنك وضع قيم افتراضية للمعاملات في __init__. هذا يجعل بعض المعاملات اختيارية:

default_values.py
class User:
    def __init__(self, username, email, role="عضو", is_active=True):
        self.username = username
        self.email = email
        self.role = role          # قيمة افتراضية: "عضو"
        self.is_active = is_active  # قيمة افتراضية: True
    
    def display_info(self):
        status = "نشط" if self.is_active else "غير نشط"
        print(f"المستخدم: {self.username}")
        print(f"البريد: {self.email}")
        print(f"الدور: {self.role}")
        print(f"الحالة: {status}")

# مستخدم مع القيم الافتراضية
user1 = User("ahmed_99", "ahmed@example.com")

# مستخدم مع تحديد الدور
user2 = User("sara_admin", "sara@example.com", role="مدير")

# مستخدم مع تحديد كل القيم
user3 = User("old_user", "old@example.com", role="عضو", is_active=False)

print("=== المستخدم 1 ===")
user1.display_info()

print("\n=== المستخدم 2 ===")
user2.display_info()

print("\n=== المستخدم 3 ===")
user3.display_info()
النتيجة
=== المستخدم 1 === المستخدم: ahmed_99 البريد: ahmed@example.com الدور: عضو الحالة: نشط === المستخدم 2 === المستخدم: sara_admin البريد: sara@example.com الدور: مدير الحالة: نشط === المستخدم 3 === المستخدم: old_user البريد: old@example.com الدور: عضو الحالة: غير نشط

قاعدة مهمة: المعاملات ذات القيم الافتراضية يجب أن تأتي بعد المعاملات الإلزامية:

# خطأ - القيمة الافتراضية قبل الإلزامية
class Wrong:
    def __init__(self, name="أحمد", age):  # SyntaxError!
        pass

# صحيح - القيم الافتراضية في النهاية
class Correct:
    def __init__(self, age, name="أحمد"):
        self.age = age
        self.name = name

5. إجراء عمليات داخل __init__

__init__ ليست فقط لحفظ البيانات

يمكنك إجراء أي عمليات تريدها داخل __init__: حسابات، تحققات، طباعة رسائل، إلخ:

init_operations.py
class Employee:
    total_employees = 0  # عداد لجميع الموظفين
    
    def __init__(self, name, salary, department):
        self.name = name
        self.salary = salary
        self.department = department
        
        # حساب الضريبة تلقائياً (10%)
        self.tax = salary * 0.10
        
        # حساب الراتب الصافي
        self.net_salary = salary - self.tax
        
        # زيادة عداد الموظفين
        Employee.total_employees += 1
        
        # طباعة رسالة ترحيبية
        print(f"مرحباً {self.name}! تم تسجيلك في قسم {self.department}")
        print(f"أنت الموظف رقم {Employee.total_employees}")
        
    def display_payroll(self):
        print(f"\n--- كشف راتب {self.name} ---")
        print(f"القسم: {self.department}")
        print(f"الراتب الأساسي: {self.salary} ريال")
        print(f"الضريبة (10%): {self.tax} ريال")
        print(f"الراتب الصافي: {self.net_salary} ريال")
        print("-" * 30)

# إنشاء موظفين
emp1 = Employee("خالد", 5000, "المبيعات")
emp2 = Employee("ليلى", 7000, "التطوير")
emp3 = Employee("محمد", 6000, "التسويق")

# عرض كشوف الرواتب
emp1.display_payroll()
emp2.display_payroll()

print(f"\nإجمالي عدد الموظفين: {Employee.total_employees}")

6. التحقق من صحة البيانات في __init__

حماية الكائن من البيانات الخاطئة

من الممارسات الجيدة التحقق من صحة البيانات المُمررة قبل حفظها:

data_validation.py
class BankAccount:
    def __init__(self, owner, initial_balance):
        # التحقق من أن الاسم ليس فارغاً
        if not owner or len(owner.strip()) == 0:
            print("خطأ: يجب إدخال اسم المالك!")
            self.owner = "غير معروف"
        else:
            self.owner = owner
        
        # التحقق من أن الرصيد الأولي موجب
        if initial_balance < 0:
            print("تحذير: لا يمكن أن يكون الرصيد سالباً. تم تعيينه إلى 0")
            self.balance = 0
        else:
            self.balance = initial_balance
        
        print(f"تم إنشاء حساب لـ {self.owner} برصيد {self.balance} ريال")

# حساب صحيح
account1 = BankAccount("أحمد", 1000)

# حساب باسم فارغ
account2 = BankAccount("", 500)

# حساب برصيد سالب
account3 = BankAccount("سارة", -100)
النتيجة
تم إنشاء حساب لـ أحمد برصيد 1000 ريال خطأ: يجب إدخال اسم المالك! تم إنشاء حساب لـ غير معروف برصيد 500 ريال تحذير: لا يمكن أن يكون الرصيد سالباً. تم تعيينه إلى 0 تم إنشاء حساب لـ سارة برصيد 0 ريال

7. مثال عملي شامل: نظام إدارة طلاب

تطبيق كل ما تعلمناه

لنبني صنفاً كاملاً يجمع كل المفاهيم التي تعلمناها:

student_management.py
class Student:
    # خاصية صنف: اسم المدرسة
    school_name = "مدرسة النجاح الثانوية"
    total_students = 0
    
    def __init__(self, name, age, grade, subjects=None):
        # حفظ البيانات الأساسية
        self.name = name
        self.age = age
        self.grade = grade
        
        # إذا لم تُمرر مواد، استخدم قائمة فارغة
        if subjects is None:
            self.subjects = []
        else:
            self.subjects = subjects
        
        # إنشاء قاموس للدرجات
        self.grades_dict = {}
        
        # حساب سنة الميلاد تقريباً
        from datetime import datetime
        current_year = datetime.now().year
        self.birth_year = current_year - age
        
        # زيادة عداد الطلاب
        Student.total_students += 1
        self.student_id = Student.total_students
        
        # رسالة ترحيبية
        print(f"مرحباً {self.name}! تم تسجيلك في {Student.school_name}")
        print(f"رقمك الطلابي: {self.student_id}")
    
    def add_grade(self, subject, grade):
        """إضافة درجة لمادة معينة"""
        self.grades_dict[subject] = grade
        print(f"تم إضافة درجة {grade} في مادة {subject} للطالب {self.name}")
    
    def get_average(self):
        """حساب المعدل"""
        if not self.grades_dict:
            return 0
        return sum(self.grades_dict.values()) / len(self.grades_dict)
    
    def display_info(self):
        """عرض معلومات الطالب"""
        print(f"\n{'='*40}")
        print(f"معلومات الطالب")
        print(f"{'='*40}")
        print(f"الاسم: {self.name}")
        print(f"العمر: {self.age} سنة (مواليد {self.birth_year})")
        print(f"الصف: {self.grade}")
        print(f"الرقم الطلابي: {self.student_id}")
        
        if self.grades_dict:
            print(f"\nالدرجات:")
            for subject, grade in self.grades_dict.items():
                print(f"  - {subject}: {grade}")
            print(f"\nالمعدل: {self.get_average():.2f}")
        else:
            print("\nلا توجد درجات مسجلة بعد")
        print(f"{'='*40}")

# إنشاء طلاب
student1 = Student("أحمد محمد", 16, "الأول الثانوي")
student2 = Student("سارة علي", 17, "الثاني الثانوي")

# إضافة درجات
print("\n--- إضافة الدرجات ---")
student1.add_grade("الرياضيات", 95)
student1.add_grade("الفيزياء", 88)
student1.add_grade("الكيمياء", 92)

student2.add_grade("الرياضيات", 98)
student2.add_grade("الأحياء", 94)

# عرض معلومات الطلاب
student1.display_info()
student2.display_info()

# عرض إحصائيات المدرسة
print(f"\nإجمالي عدد الطلاب في {Student.school_name}: {Student.total_students}")

8. أخطاء شائعة عند استخدام __init__

الخطأ 1: كتابة الاسم بشكل خاطئ

يجب أن يكون الاسم __init__ بالضبط (شرطتان سفليتان قبل وبعد كلمة init):

# خطأ - أسماء خاطئة
class Wrong1:
    def _init_(self, name):  # شرطة واحدة فقط - خطأ!
        self.name = name

class Wrong2:
    def init(self, name):    # بدون شرطات - خطأ!
        self.name = name

# صحيح
class Correct:
    def __init__(self, name):  # شرطتان قبل وبعد
        self.name = name

# الأسماء الخاطئة لن تعمل تلقائياً
obj1 = Wrong1("أحمد")
# print(obj1.name)  # AttributeError!

obj2 = Correct("أحمد")
print(obj2.name)  # يعمل بشكل صحيح
الخطأ 2: نسيان self عند تعريف الخصائص
class Person:
    def __init__(self, name, age):
        # خطأ: name و age متغيرات محلية فقط
        name = name
        age = age
        # لم نحفظهما في الكائن!
    
    def say_hi(self):
        # سيحدث خطأ هنا
        print(f"مرحباً {self.name}")  # AttributeError!

# الصحيح
class Person:
    def __init__(self, name, age):
        self.name = name  # حفظ في الكائن
        self.age = age    # حفظ في الكائن
الخطأ 3: محاولة إرجاع قيمة من __init__

دالة __init__ يجب أن تعيد دائماً None. لا يمكنك استخدام return لإرجاع قيمة:

# خطأ
class Wrong:
    def __init__(self, name):
        self.name = name
        return "تم الإنشاء"  # TypeError!

# صحيح - لا return أو return None فقط
class Correct:
    def __init__(self, name):
        self.name = name
        # لا return، أو:
        # return None
الخطأ 4: استخدام قيم افتراضية قابلة للتغيير (Mutable)

هذا خطأ خطير جداً! لا تستخدم قوائم أو قواميس فارغة كقيم افتراضية:

# خطأ خطير!
class Wrong:
    def __init__(self, name, items=[]):  # خطر!
        self.name = name
        self.items = items

# المشكلة: كل الكائنات ستشارك نفس القائمة!
obj1 = Wrong("أحمد")
obj2 = Wrong("سارة")

obj1.items.append("كتاب")
print(obj2.items)  # ['كتاب'] - تأثرت!

# الصحيح
class Correct:
    def __init__(self, name, items=None):
        self.name = name
        if items is None:
            self.items = []  # قائمة جديدة لكل كائن
        else:
            self.items = items
ملخص الدرس
  • دالة __init__ تُستدعى تلقائياً عند إنشاء كائن جديد
  • تُستخدم لتهيئة خصائص الكائن ومنحه بياناته الفريدة
  • المعامل self ضروري جداً - يمثل الكائن الحالي
  • self.variable تحفظ البيانات في الكائن (تبقى موجودة)
  • variable بدون self هو متغير محلي (يختفي بعد انتهاء الدالة)
  • يمكن تمرير أي عدد من المعاملات لـ __init__
  • يمكن وضع قيم افتراضية للمعاملات (لكن احذر من القيم القابلة للتغيير!)
  • يمكن إجراء حسابات وتحققات داخل __init__
  • __init__ لا يُرجع قيمة (دائماً None)

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

بعد أن تعلمنا كيف نهيئ الكائن بخصائص فريدة، لنتعمق أكثر في فهم أنواع الخصائص المختلفة والفرق الدقيق بين خصائص الصنف وخصائص الكائن.

الدرس التالي: الخصائص (Attributes) في Python
المحرر الذكي

اكتب الكود وشاهد النتيجة فوراً

جرب الآن مجاناً
قناة ديف عربي

تابع أحدث الدروس والتحديثات مباشرة على واتساب

انضم الآن