الدوال البانية (Constructors) في Python: فهم __init__
في الدرس السابق، تعلمنا كيف ننشئ صنفاً بسيطاً، لكننا لاحظنا أن جميع الكائنات كانت تشترك في نفس القيم. في الواقع، نحن نريد لكل كائن أن يكون له بياناته الخاصة؛ فكل طالب له اسم مختلف، وكل سيارة لها لون مختلف، وكل حساب بنكي له رصيد مختلف. هنا يأتي دور الدالة السحرية __init__، وهي بمثابة "المُنشئ" (Constructor) الذي يمنح كل كائن هويته الفريدة لحظة ولادته.
1. ما هي الدالة __init__؟
التعريف والغرض
الدالة __init__ (تُنطق "init" أو "دَندَر init") هي دالة خاصة جداً في بايثون. الاسم يأتي من كلمة "Initialize" أي "تهيئة". هذه الدالة:
- تُستدعى تلقائياً وبشكل فوري بمجرد إنشاء كائن جديد من الصنف
- لا تحتاج أن تستدعيها يدوياً - بايثون تفعل ذلك من تلقاء نفسها
- وظيفتها الأساسية هي تهيئة (Initialize) خصائص الكائن الجديد بالقيم التي نمررها له
- تُسمى أحياناً "Constructor" (المُنشئ) لأنها تُنشئ الحالة الأولية للكائن
فكر في __init__ كأنها "استمارة بيانات" يجب ملؤها عند تسجيل أي عضو جديد في نادٍ ما. بدون هذه الاستمارة، لن يكون للعضو أي بيانات شخصية.
مثال بسيط جداً
لنبدأ بأبسط مثال ممكن لفهم كيف تعمل __init__:
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}")
ماذا حدث بالضبط؟
- عندما كتبنا
Robot("رعد")، بايثون فعلت الآتي:- أنشأت كائناً جديداً في الذاكرة
- استدعت
__init__تلقائياً - مررت "رعد" كمعامل للدالة
- حفظت "رعد" في خاصية
nameداخل الكائن
- الآن
robot1وrobot2كائنان مختلفان، كل منهما له اسمه الخاص
2. فهم المعامل self بعمق
ما هو self بالضبط؟
self هي كلمة سحرية في بايثون تمثل الكائن الحالي الذي يتم العمل عليه الآن. دعنا نفهم هذا بعمق:
self هو مرجع (reference) يشير إلى الكائن الذي استدعى الدالة. عندما تكتب robot1 = Robot("رعد")، فإن self داخل __init__ يشير إلى robot1 نفسه.
تشبيه بسيط لفهم self
تخيل أنك في مطعم، والنادل يسألك: "ماذا تريد أن تأكل؟". عندما تجيبه "أريد بيتزا"، النادل يفهم أن الطلب يخصك أنت (self) وليس الشخص الجالس في الطاولة المجاورة.
في البرمجة، عندما يكون لديك 10 روبوتات، وتقول لأحدهم "احفظ اسمك"، كيف يعرف الروبوت أنك تقصده هو وليس روبوتاً آخر؟ الجواب: self!
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} سنة")
لماذا self.name = name وليس فقط name = name؟
هذا سؤال مهم جداً! دعنا نفهم الفرق:
name(بدون self): متغير محلي داخل الدالة فقط، سيختفي بمجرد انتهاء الدالةself.name: خاصية تنتمي للكائن، ستبقى موجودة طوال حياة الكائن
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__:
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()
ترتيب المعاملات مهم!
عند استدعاء __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__. هذا يجعل بعض المعاملات اختيارية:
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()
قاعدة مهمة: المعاملات ذات القيم الافتراضية يجب أن تأتي بعد المعاملات الإلزامية:
# خطأ - القيمة الافتراضية قبل الإلزامية
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__: حسابات، تحققات، طباعة رسائل، إلخ:
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__
حماية الكائن من البيانات الخاطئة
من الممارسات الجيدة التحقق من صحة البيانات المُمررة قبل حفظها:
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)
7. مثال عملي شامل: نظام إدارة طلاب
تطبيق كل ما تعلمناه
لنبني صنفاً كاملاً يجمع كل المفاهيم التي تعلمناها:
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