الكلمة المفتاحية self في Python: الجسر بين الدالة والكائن

كلمة self هي واحدة من أكثر المفاهيم غموضاً للمبتدئين في البرمجة الكائنية. لماذا نكتبها دائماً؟ ماذا تعني بالضبط؟ لماذا لا نمررها عند الاستدعاء؟ في هذا الدرس، سنزيل كل الغموض ونفهم self من الجذور. إنها ببساطة الجسر الذي يربط الدالة بالكائن الذي استدعاها - إنها الطريقة التي يعرف بها الكائن "من أنا؟".

1. ما هي self بالضبط؟

التعريف البسيط

self هي مرجع (Reference) يشير إلى الكائن الحالي الذي استدعى الأسلوب. دعنا نفكك هذا التعريف:

  • مرجع (Reference): مثل "مؤشر" أو "عنوان" يشير إلى شيء في الذاكرة
  • الكائن الحالي: الكائن المحدد الذي نعمل عليه الآن
  • استدعى الأسلوب: الكائن الذي كتبنا اسمه قبل النقطة عند استدعاء الدالة
تشبيه المطعم

تخيل أنك في مطعم به 10 طاولات. النادل يأتي إلى طاولتك ويسأل: "ماذا تريد أن تطلب؟"

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

في البرمجة، self تعني "هذا الكائن بالذات" - الكائن الذي استدعى الدالة.

مثال بسيط جداً
self_basic_example.py
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:

without_self_problem.py
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:

with_self_solution.py
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
النتيجة
العداد الآن: 1 العداد الآن: 2 العداد الآن: 1 العداد 1: 2 العداد 2: 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 هي الاتفاقية المتبعة عالمياً:

self_is_convention.py
# تقنياً، هذا يعمل (لكن لا تفعله أبداً!)
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. استدعاء أساليب أخرى
self_calling_methods.py
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()
النتيجة
النتيجة: 15 النتيجة: 0

شرح سطراً بسطر:

  • السطر 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):

behind_the_scenes.py
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
bank_system_complete.py
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) - كيف تجعل الأصناف ترث من بعضها لتوفير الكود وبناء تسلسلات هرمية!

الدرس التالي: الوراثة (Inheritance) في Python
المحرر الذكي

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

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

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

انضم الآن