الكلمة المفتاحية self في Python: الجسر بين الدالة والكائن
كلمة self هي واحدة من أكثر المفاهيم غموضاً للمبتدئين في البرمجة الكائنية. لماذا نكتبها دائماً؟ ماذا تعني بالضبط؟ لماذا لا نمررها عند الاستدعاء؟ في هذا الدرس، سنزيل كل الغموض ونفهم self من الجذور. إنها ببساطة الجسر الذي يربط الدالة بالكائن الذي استدعاها - إنها الطريقة التي يعرف بها الكائن "من أنا؟".
1. ما هي self بالضبط؟
التعريف البسيط
self هي مرجع (Reference) يشير إلى الكائن الحالي الذي استدعى الأسلوب. دعنا نفكك هذا التعريف:
- مرجع (Reference): مثل "مؤشر" أو "عنوان" يشير إلى شيء في الذاكرة
- الكائن الحالي: الكائن المحدد الذي نعمل عليه الآن
- استدعى الأسلوب: الكائن الذي كتبنا اسمه قبل النقطة عند استدعاء الدالة
تشبيه المطعم
تخيل أنك في مطعم به 10 طاولات. النادل يأتي إلى طاولتك ويسأل: "ماذا تريد أن تطلب؟"
كلمة "أنت" في هذا السؤال تعني الشخص الجالس في هذه الطاولة بالذات. النادل لا يسأل الطاولة المجاورة، بل يسألك أنت تحديداً.
في البرمجة، self تعني "هذا الكائن بالذات" - الكائن الذي استدعى الدالة.
مثال بسيط جداً
class Person:
def __init__(self, name):
self.name = name
def introduce(self):
print(f"مرحباً، أنا {self.name}")
# إنشاء شخصين
person1 = Person("أحمد")
person2 = Person("سارة")
# استدعاء الأسلوب
person1.introduce() # عندما نستدعي من person1، self = person1
person2.introduce() # عندما نستدعي من person2، self = person2
شرح ما يحدث خلف الكواليس:
- السطر 13: عندما نكتب
person1.introduce()، بايثون تحول هذا داخلياً إلى:Person.introduce(person1) - بايثون تمرر
person1تلقائياً كأول معامل (وهوself) - داخل الدالة،
selfالآن يشير إلىperson1 - لذلك
self.nameيعنيperson1.nameوهو "أحمد" - السطر 14: نفس الشيء، لكن
selfالآن يشير إلىperson2
2. لماذا نحتاج self؟
المشكلة بدون self
لنرى ماذا يحدث إذا لم نستخدم self:
class Counter:
def __init__(self):
# خطأ: count متغير محلي فقط!
count = 0
def increment(self):
# خطأ: count غير موجود هنا!
# count += 1 # UnboundLocalError!
pass
# المشكلة: كل كائن يحتاج نسخته الخاصة من البيانات
counter1 = Counter()
counter2 = Counter()
# كيف نميز بين count الخاص بـ counter1 و count الخاص بـ counter2؟
# الجواب: self!
الحل باستخدام self:
class Counter:
def __init__(self):
# self.count يعني: "عداد هذا الكائن بالذات"
self.count = 0
def increment(self):
# self.count يعني: "عداد هذا الكائن بالذات"
self.count += 1
print(f"العداد الآن: {self.count}")
# إنشاء عدادين مستقلين
counter1 = Counter()
counter2 = Counter()
# كل عداد مستقل عن الآخر
counter1.increment() # self = counter1، لذا self.count = counter1.count
counter1.increment() # self = counter1، لذا self.count = counter1.count
counter2.increment() # self = counter2، لذا self.count = counter2.count
print(f"\nالعداد 1: {counter1.count}") # 2
print(f"العداد 2: {counter2.count}") # 1
شرح سطراً بسطر:
- السطر 4:
self.count = 0تعني: "أنشئ متغيراً اسمه count يخص هذا الكائن" - السطر 8:
self.count += 1تعني: "زد عداد هذا الكائن بمقدار 1" - السطر 15: عند استدعاء
counter1.increment()، بايثون تمررcounter1كـself - داخل الدالة،
self.countيصبحcounter1.count - السطر 17: عند استدعاء
counter2.increment()،selfالآن يشير إلىcounter2 - لذلك
self.countيصبحcounter2.count(كائن مختلف، عداد مختلف!)
3. self ليست كلمة محجوزة!
حقيقة مفاجئة
self ليست كلمة محجوزة في بايثون! يمكنك استخدام أي اسم تريده، لكن self هي الاتفاقية المتبعة عالمياً:
# تقنياً، هذا يعمل (لكن لا تفعله أبداً!)
class Dog:
def __init__(this, name): # استخدمنا "this" بدلاً من "self"
this.name = name
def bark(myself): # استخدمنا "myself"
print(f"{myself.name} ينبح!")
dog = Dog("ماكس")
dog.bark() # يعمل، لكنه مربك جداً!
# الطريقة الصحيحة والمتفق عليها عالمياً
class Cat:
def __init__(self, name): # دائماً استخدم "self"
self.name = name
def meow(self):
print(f"{self.name} تموء!")
cat = Cat("ميسي")
cat.meow() # واضح ومفهوم للجميع
self هو اتفاقية عالمية يتبعها جميع مبرمجي بايثون. استخدام اسم آخر سيجعل كودك مربكاً وصعب القراءة.
4. استخدامات self المختلفة
1. الوصول إلى الخصائص
class Student:
def __init__(self, name, age):
# إنشاء خصائص باستخدام self
self.name = name
self.age = age
def display_info(self):
# قراءة الخصائص باستخدام self
print(f"الاسم: {self.name}")
print(f"العمر: {self.age}")
def have_birthday(self):
# تعديل الخصائص باستخدام self
self.age += 1
print(f"عيد ميلاد سعيد! العمر الجديد: {self.age}")
2. استدعاء أساليب أخرى
class Calculator:
def __init__(self):
self.result = 0
def add(self, number):
"""إضافة رقم"""
self.result += number
return self
def multiply(self, number):
"""ضرب في رقم"""
self.result *= number
return self
def display(self):
"""عرض النتيجة"""
print(f"النتيجة: {self.result}")
return self
def reset(self):
"""إعادة تعيين"""
self.result = 0
# استدعاء أسلوب آخر باستخدام self
self.display()
return self
# استخدام متسلسل (Method Chaining)
calc = Calculator()
calc.add(5).multiply(3).display().reset()
شرح سطراً بسطر:
- السطر 7: نضيف رقماً إلى
self.result(نتيجة هذا الكائن) - السطر 8: نُرجع
self(الكائن نفسه) للسماح بالاستدعاء المتسلسل - السطر 23: نستدعي
self.display()- أسلوب يستدعي أسلوباً آخر - السطر 28: استدعاء متسلسل:
calc.add(5)يُرجعcalc، ثم.multiply(3)يُرجعcalc، وهكذا
3. تمرير الكائن نفسه لدوال أخرى
class Person:
def __init__(self, name):
self.name = name
def introduce_to(self, other_person):
"""التعريف بنفسك لشخص آخر"""
print(f"{self.name} يقول: مرحباً {other_person.name}!")
# يمكننا تمرير self (الكائن الحالي) لأساليب أخرى
other_person.greet(self)
def greet(self, person):
"""الرد على التحية"""
print(f"{self.name} يرد: أهلاً {person.name}!")
person1 = Person("أحمد")
person2 = Person("سارة")
person1.introduce_to(person2)
5. ماذا يحدث خلف الكواليس؟
التحويل الداخلي
عندما تكتب object.method(args)، بايثون تحولها داخلياً إلى Class.method(object, args):
class Dog:
def __init__(self, name):
self.name = name
def bark(self, times):
for i in range(times):
print(f"{self.name} ينبح!")
dog = Dog("ماكس")
# الطريقة العادية (ما نكتبه)
dog.bark(2)
# ما تفعله بايثون خلف الكواليس (نادراً ما نكتبه هكذا)
Dog.bark(dog, 2)
# كلا الطريقتين تعطيان نفس النتيجة!
شرح التحويل:
- السطر 12: نكتب
dog.bark(2)- الطريقة المعتادة - بايثون تقرأ هذا وتقول: "حسناً،
dogهو كائن من نوعDog" - تبحث عن أسلوب
barkفي صنفDog - تستدعيه هكذا:
Dog.bark(dog, 2)- تمرر الكائن كأول معامل! - السطر 15: نفس الشيء بالضبط، لكن كتبناه بشكل صريح
6. مثال شامل: نظام حسابات بنكية
تطبيق متكامل يوضح استخدام self
class BankAccount:
# خاصية صنف (مشتركة)
bank_name = "البنك الوطني"
account_count = 0
def __init__(self, owner, initial_balance):
# self.owner يعني: "مالك هذا الحساب بالذات"
self.owner = owner
# self.balance يعني: "رصيد هذا الحساب بالذات"
self.balance = initial_balance
# self.transactions يعني: "معاملات هذا الحساب بالذات"
self.transactions = []
# زيادة عداد الحسابات (خاصية صنف)
BankAccount.account_count += 1
# self.account_number يعني: "رقم هذا الحساب بالذات"
self.account_number = BankAccount.account_count
# استدعاء أسلوب آخر باستخدام self
self._add_transaction("فتح حساب", initial_balance)
def _add_transaction(self, description, amount):
"""إضافة معاملة (أسلوب داخلي)"""
# self.transactions يعني: "قائمة معاملات هذا الحساب"
self.transactions.append({
'description': description,
'amount': amount,
'balance_after': self.balance
})
def deposit(self, amount):
"""إيداع مبلغ"""
if amount <= 0:
print("المبلغ يجب أن يكون موجباً!")
return False
# self.balance يعني: "رصيد هذا الحساب"
self.balance += amount
# استدعاء أسلوب آخر
self._add_transaction("إيداع", amount)
print(f"تم إيداع {amount} ريال في حساب {self.owner}")
print(f"الرصيد الجديد: {self.balance} ريال")
return True
def withdraw(self, amount):
"""سحب مبلغ"""
if amount <= 0:
print("المبلغ يجب أن يكون موجباً!")
return False
# self.balance يعني: "رصيد هذا الحساب"
if amount > self.balance:
print(f"رصيد غير كافي! الرصيد الحالي: {self.balance}")
return False
self.balance -= amount
self._add_transaction("سحب", -amount)
print(f"تم سحب {amount} ريال من حساب {self.owner}")
print(f"الرصيد المتبقي: {self.balance} ريال")
return True
def transfer_to(self, other_account, amount):
"""تحويل مبلغ لحساب آخر"""
print(f"\n--- تحويل من {self.owner} إلى {other_account.owner} ---")
# السحب من هذا الحساب (self)
if self.withdraw(amount):
# الإيداع في الحساب الآخر
other_account.deposit(amount)
print("تم التحويل بنجاح!")
return True
return False
def display_info(self):
"""عرض معلومات الحساب"""
print(f"\n{'='*50}")
print(f"معلومات الحساب")
print(f"{'='*50}")
print(f"البنك: {BankAccount.bank_name}") # خاصية صنف
print(f"رقم الحساب: {self.account_number}") # خاصية كائن
print(f"المالك: {self.owner}") # خاصية كائن
print(f"الرصيد: {self.balance} ريال") # خاصية كائن
print(f"عدد المعاملات: {len(self.transactions)}")
print(f"{'='*50}")
def display_transactions(self):
"""عرض سجل المعاملات"""
print(f"\n--- سجل معاملات {self.owner} ---")
for i, trans in enumerate(self.transactions, 1):
print(f"{i}. {trans['description']}: {trans['amount']:+} ريال "
f"(الرصيد بعدها: {trans['balance_after']})")
# استخدام النظام
print("=== نظام الحسابات البنكية ===\n")
# إنشاء حسابات (كل حساب له self خاص به)
account1 = BankAccount("أحمد", 5000)
account2 = BankAccount("سارة", 3000)
# عرض معلومات الحسابات
account1.display_info()
account2.display_info()
# عمليات على الحساب الأول
print("\n--- عمليات على حساب أحمد ---")
account1.deposit(1000)
account1.withdraw(500)
# عمليات على الحساب الثاني
print("\n--- عمليات على حساب سارة ---")
account2.deposit(2000)
# تحويل بين الحسابات
account1.transfer_to(account2, 1500)
# عرض الحالة النهائية
print("\n=== الحالة النهائية ===")
account1.display_info()
account2.display_info()
# عرض المعاملات
account1.display_transactions()
account2.display_transactions()
print(f"\nإجمالي الحسابات المفتوحة: {BankAccount.account_count}")
شرح استخدام self في هذا المثال:
- السطر 7-12:
self.owner,self.balance,self.transactions- كل حساب له نسخته الخاصة - السطر 20:
self._add_transaction()- أسلوب يستدعي أسلوباً آخر في نفس الكائن - السطر 38:
self.balance += amount- تعديل رصيد هذا الحساب فقط - السطر 64:
self.ownerوother_account.owner- حسابان مختلفان - السطر 67:
self.withdraw()- السحب من الحساب الحالي - السطر 69:
other_account.deposit()- الإيداع في حساب آخر
7. أخطاء شائعة مع self
الخطأ 1: نسيان self في تعريف الأسلوب
# خطأ
class Person:
def greet(): # نسينا self!
print("مرحباً")
person = Person()
# person.greet() # TypeError: greet() takes 0 positional arguments but 1 was given
# صحيح
class Person:
def greet(self): # يجب إضافة self
print("مرحباً")
الخطأ 2: نسيان self عند الوصول للخصائص
# خطأ
class Car:
def __init__(self, color):
self.color = color
def display(self):
# خطأ: color غير معرّف!
print(f"اللون: {color}") # NameError!
# صحيح
class Car:
def __init__(self, color):
self.color = color
def display(self):
# يجب استخدام self.color
print(f"اللون: {self.color}")
الخطأ 3: تمرير self يدوياً عند الاستدعاء
class Dog:
def bark(self):
print("واف واف!")
dog = Dog()
# خطأ - لا تمرر self يدوياً!
# dog.bark(dog) # TypeError: bark() takes 1 positional argument but 2 were given
# صحيح - بايثون تمرر self تلقائياً
dog.bark() # يعمل بشكل صحيح
ملخص الدرس
selfهي مرجع يشير إلى الكائن الحالي الذي استدعى الأسلوب- بايثون تمرر الكائن تلقائياً كأول معامل (self) عند استدعاء أي أسلوب
- نستخدم
selfللوصول إلى خصائص وأساليب الكائن الحالي self.attributeتعني "خاصية هذا الكائن بالذات"self.method()تعني "استدعِ أسلوباً آخر في هذا الكائن"- عند كتابة
obj.method()، بايثون تحولها إلىClass.method(obj) selfليست كلمة محجوزة، لكنها الاتفاقية العالمية (لا تغيرها!)- كل كائن له
selfخاص به يشير إليه
الخطوة التالية
الآن بعد أن أتقنت أساسيات الأصناف والكائنات وself، حان الوقت لتعلم واحدة من أقوى ميزات OOP: الوراثة (Inheritance) - كيف تجعل الأصناف ترث من بعضها لتوفير الكود وبناء تسلسلات هرمية!