Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

السمات الخاصة

جزء من قسم Object Oriented Programming في رحلة Python على Coddy — الدرس 15 من 64.

تستخدم السمات الخاصة (Private attributes) الشرطات السفلية للإشارة إلى أنه لا ينبغي الوصول إلى بيانات معينة مباشرة من خارج الفئة (class).

لماذا نحتاج إلى السمات الخاصة؟ تخيل أن لديك حساباً بنكياً. لا تريد أن يقوم أي شخص بتغيير رصيدك مباشرة - يجب أن يمروا عبر القنوات المناسبة (طرق الإيداع/السحب) حتى يتمكن البنك من التحقق من صحة المعاملة، والتحقق من الاحتيال، والاحتفاظ بالسجلات، وما إلى ذلك. تعمل السمات الخاصة بنفس الطريقة - فهي تحمي بياناتك من التغيير بشكل غير صحيح.

إليك مثالاً على استخدام الشرطة السفلية الواحدة (اتفاقية لـ "الاستخدام الداخلي"):

class Person:
    def __init__(self, name, age):
        self._name = name    # "محمي" - استخدام داخلي
        self._age = age      # "محمي" - استخدام داخلي

لا يزال من الممكن الوصول إلى السمات التي تبدأ بشرطة سفلية واحدة، ولكنها إشارة لعدم القيام بذلك:

person = Person("Alice", 30)
print(person._name)  # يعمل، ولكن لا يُنصح به

استخدم الشرطات السفلية المزدوجة لخصوصية أقوى (تشويه الأسماء):

class Person:
    def __init__(self, name, age):
        self.__name = name   # "خاص" - يتم تشويه الاسم
        self.__age = age     # "خاص" - يتم تشويه الاسم
    
    def get_name(self):
        return self.__name
    
    def set_age(self, age):
        if age >= 0:
            self.__age = age
        else:
            print("Age must be positive!")

لماذا نستخدم دوال الجلب (getter) والتعيين (setter)؟ إنها تشبه حراس البوابة. بدلاً من السماح لأي شخص بتغيير __age مباشرة (مما قد يؤدي إلى أعمار سالبة أو بيانات غير صالحة أخرى)، فإننا نجبرهم على استخدام set_age() التي تتحقق من صحة المدخلات أولاً. هذا يمنع الأخطاء ويضمن سلامة البيانات.

هذا "الكود الإضافي" له غرض:

  • get_name() - وصول محكوم لقراءة الاسم
  • set_age(age) - يتحقق من صحة العمر قبل السماح بالتغييرات (لا يسمح بالأعمار السالبة!)
  • بدون هذه الدوال، يمكن لشخص ما تعيين age = -100 وتعطيل برنامجك

استخدم طرق الوصول (accessor methods) للتفاعل مع السمات الخاصة:

person = Person("Bob", 25)
print(person.get_name())  # Bob

person.set_age(30)        # صالح: يصبح العمر 30
person.set_age(-5)        # غير صالح: يجب أن يكون العمر موجباً! (يبقى العمر 30)

هل ترى الفائدة؟ تمنع طريقة set_age() البيانات غير الصالحة. بدونها، قد تنشئ بالخطأ شخصاً بعمر -5، وهو أمر غير منطقي!

سمات الشرطة السفلية المزدوجة تخضع لعملية "تشويه الأسماء" (name mangling) ولكن لا يزال من الممكن الوصول إليها:

person = Person("Charlie", 35)
# هذا لا يعمل:
# print(person.__name)  # AttributeError

# لكن هذا يعمل (غير مستحسن):
print(person._Person__name)  # Charlie

مثال من الواقع - لماذا كل هذا الكود؟

class BankAccount:
    def __init__(self, balance):
        self.__balance = balance  # خاص: لا يمكن تغييره مباشرة
    
    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
            return True
        return False
    
    def get_balance(self):
        return self.__balance

# بدون السمات الخاصة:
# account.__balance = -1000000  # كارثة! رصيد سلبي مسموح به

# مع السمات الخاصة:
account = BankAccount(100)
account.deposit(50)           # آمن: تم التحقق من صحته
print(account.get_balance())  # 150

يحمي "الكود الإضافي" (الأساليب/methods) بياناتك من التغييرات غير الصالحة. الأمر يشبه وجود حراس أمن بدلاً من ترك مقتنياتك الثمينة دون حماية.

المخرجات:

Alice
Bob
Age must be positive!
Charlie
150

تعني الشرطة السفلية المفردة _attribute "للاستخدام الداخلي فقط" حسب العرف. أما الشرطة السفلية المزدوجة __attribute فتؤدي إلى تشويه الاسم (name mangling) لخصوصية أقوى. استخدم دوال الجلب والتعيين (getter/setter) (دوال الوصول) للتفاعل بشكل صحيح مع البيانات الخاصة وإضافة التحقق من الصحة. يمنع "الكود الإضافي" الأخطاء البرمجية من خلال ضمان صحة البيانات دائمًا.

challenge icon

التحدي

سهل

أكمل فئة User في user.py مع وظيفة كلمة المرور الخاصة، ثم استخدمها في driver.py. اتبع تعليقات TODO لتنفيذ طرق التحقق من كلمة المرور وتغييرها.

ورقة مرجعية

تستخدم السمات الخاصة الشرطات السفلية للإشارة إلى أنه لا ينبغي الوصول إلى البيانات مباشرة من خارج الفئة.

الشرطة السفلية الواحدة (_attribute) - اصطلاح لـ "الاستخدام الداخلي":

class Person:
    def __init__(self, name, age):
        self._name = name    # "محمي" - استخدام داخلي
        self._age = age      # "محمي" - استخدام داخلي

الشرطة السفلية المزدوجة (__attribute) - تؤدي إلى تشويه الاسم (name mangling) لخصوصية أقوى:

class Person:
    def __init__(self, name, age):
        self.__name = name   # "خاص" - يتم تشويه الاسم
        self.__age = age     # "خاص" - يتم تشويه الاسم
    
    def get_name(self):
        return self.__name
    
    def set_age(self, age):
        if age >= 0:
            self.__age = age
        else:
            print("Age must be positive!")

استخدم طرق الوصول (accessor methods) للتفاعل مع السمات الخاصة:

person = Person("Bob", 25)
print(person.get_name())  # Bob
person.set_age(30)
person.set_age(-5)        # Age must be positive!

السمات ذات الشرطة السفلية المزدوجة يتم تشويه أسمائها ولكن لا يزال من الممكن الوصول إليها (غير مستحسن):

# هذا لا يعمل:
# print(person.__name)  # AttributeError

# لكن هذا يعمل (غير مستحسن):
print(person._Person__name)  # Charlie

جرّب بنفسك

# TODO: استيراد فئة User من وحدة user

# TODO: الحصول على حالة الاختبار من المدخلات
test_case = input()

if test_case == "constructor_test":
    # TODO: إنشاء كائن User بكلمة مرور أولية "secure123"
    user = User("secure123")
    print("User created successfully")
    print("Initial password set")

elif test_case == "correct_password_test":
    # TODO: إنشاء كائن User بكلمة مرور أولية "secure123"
    user = User("secure123")
    # TODO: اختبار طريقة check_password بكلمة مرور صحيحة
    #       اطبع "Password check successful!" إذا أعادت True
    #       اطبع "Password check failed!" إذا أعادت False
    result = user.check_password("secure123")
    if result:
        print("Password check successful!")
    else:
        print("Password check failed!")

elif test_case == "incorrect_password_test":
    # TODO: إنشاء كائن User بكلمة مرور أولية "secure123"
    user = User("secure123")
    # TODO: اختبار طريقة check_password بكلمة مرور غير صحيحة "wrong"
    #       اطبع "Incorrect password rejected!" إذا أعادت False
    #       اطبع "Security issue: incorrect password accepted!" إذا أعادت True
    result = user.check_password("wrong")
    if not result:
        print("Incorrect password rejected!")
    else:
        print("Security issue: incorrect password accepted!")

elif test_case == "change_password_test":
    # TODO: إنشاء كائن User بكلمة مرور أولية "secure123"
    user = User("secure123")
    # TODO: اختبار تغيير كلمة المرور من "secure123" إلى "newpass456"
    #       اطبع "Password changed successfully!" إذا أعادت True
    #       اطبع "Password change failed!" إذا أعادت False
    result = user.change_password("secure123", "newpass456")
    if result:
        print("Password changed successfully!")
    else:
        print("Password change failed!")
    
    # TODO: التحقق من أن كلمة المرور الجديدة تعمل عن طريق فحص "newpass456"
    #       اطبع "New password works!" إذا أعادت True
    #       اطبع "New password doesn't work!" إذا أعادت False
    if user.check_password("newpass456"):
        print("New password works!")
    else:
        print("New password doesn't work!")

elif test_case == "change_password_wrong_old_test":
    # TODO: إنشاء كائن User بكلمة مرور أولية "secure123"
    user = User("secure123")
    # TODO: محاولة تغيير كلمة المرور بكلمة مرور قديمة غير صحيحة
    #       اطبع "Security working: incorrect old password rejected!" إذا أعادت False
    #       اطبع "Security issue: password changed with wrong old password!" إذا أعادت True
    result = user.change_password("wrong", "hackerpw")
    if not result:
        print("Security working: incorrect old password rejected!")
    else:
        print("Security issue: password changed with wrong old password!")

elif test_case == "comprehensive_test":
    # TODO: إنشاء كائن User بكلمة مرور أولية "secure123"
    user = User("secure123")
    print("User created with initial password")
    
    # TODO: اختبار طريقة check_password بكلمة مرور صحيحة
    if user.check_password("secure123"):
        print("Initial password verification successful")
    
    # TODO: اختبار طريقة check_password بكلمة مرور غير صحيحة
    if not user.check_password("wrongpass"):
        print("Incorrect password properly rejected")
    
    # TODO: اختبار تغيير كلمة المرور من "secure123" إلى "newpass456"
    if user.change_password("secure123", "newpass456"):
        print("Password successfully changed")
    
    # TODO: التحقق من أن كلمة المرور القديمة لم تعد تعمل
    if not user.check_password("secure123"):
        print("Old password no longer works")
    
    # TODO: التحقق من أن كلمة المرور الجديدة تعمل
    if user.check_password("newpass456"):
        print("New password works correctly")
    
    # TODO: محاولة تغيير كلمة المرور بكلمة مرور قديمة غير صحيحة
    if not user.change_password("wrongold", "hackerpw"):
        print("Security maintained: wrong old password rejected")
    
    # TODO: التحقق من أن كلمة المرور لا تزال آمنة بعد محاولة تغيير فاشلة
    if user.check_password("newpass456"):
        print("Password remains secure after failed change attempt")

else:
    print("Unknown test case")
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming