الخصائص (Attributes) في Python: فهم عميق

الخصائص (Attributes) هي البيانات التي يحملها الكائن. إنها ما يميز كائناً عن آخر. في هذا الدرس، سنتعمق في فهم نوعين مختلفين تماماً من الخصائص: خصائص الصنف (Class Attributes) التي تُشارك بين جميع الكائنات، وخصائص الكائن (Instance Attributes) التي تكون فريدة لكل كائن على حدة. فهم الفرق بينهما ضروري جداً لكتابة كود صحيح وفعال.

1. ما هي الخصائص (Attributes)؟

التعريف الأساسي

الخصائص (Attributes) هي المتغيرات التي تنتمي إلى صنف أو كائن. إنها تمثل "الحالة" (State) أو "البيانات" (Data) التي يحملها الكائن. مثلاً:

  • طالب له خصائص: الاسم، العمر، الدرجات، الصف الدراسي
  • سيارة لها خصائص: اللون، الموديل، السرعة الحالية، كمية الوقود
  • حساب بنكي له خصائص: اسم المالك، الرصيد، رقم الحساب، تاريخ الفتح

في بايثون، هناك نوعان رئيسيان من الخصائص، ولكل منهما استخدام وسلوك مختلف تماماً:

خصائص الصنف (Class Attributes) خصائص الكائن (Instance Attributes)
مشتركة بين جميع الكائنات فريدة لكل كائن على حدة
تُعرّف مباشرة داخل الصنف تُعرّف داخل __init__ باستخدام self
تتغير لجميع الكائنات معاً تتغير لكائن واحد فقط
مثل "قواعد اللعبة" (نفسها للجميع) مثل "نقاط اللاعب" (مختلفة لكل لاعب)

2. خصائص الصنف (Class Attributes)

ما هي خصائص الصنف؟

خصائص الصنف هي متغيرات تُعرّف مباشرة داخل الصنف (وليس داخل أي دالة). هذه الخصائص:

  • تُشارك بين جميع الكائنات المنشأة من الصنف
  • تُخزّن في الصنف نفسه، وليس في كل كائن على حدة
  • يمكن الوصول إليها من الصنف مباشرة أو من أي كائن
  • عند تغييرها، تتغير لجميع الكائنات
مثال بسيط جداً
class_attributes_basic.py
class Dog:
    # خصائص صنف (مشتركة بين كل الكلاب)
    species = "Canis familiaris"  # الفصيلة
    legs = 4                       # عدد الأرجل
    is_mammal = True               # هل هو من الثدييات

# إنشاء كلاب مختلفة
dog1 = Dog()
dog2 = Dog()
dog3 = Dog()

# كل الكلاب لها نفس الفصيلة
print(f"كلب 1 - الفصيلة: {dog1.species}")
print(f"كلب 2 - الفصيلة: {dog2.species}")
print(f"كلب 3 - الفصيلة: {dog3.species}")

# يمكن الوصول من الصنف مباشرة
print(f"\nمن الصنف: {Dog.species}")

# كل الكلاب لها 4 أرجل
print(f"\nعدد أرجل الكلب: {Dog.legs}")
النتيجة
كلب 1 - الفصيلة: Canis familiaris كلب 2 - الفصيلة: Canis familiaris كلب 3 - الفصيلة: Canis familiaris من الصنف: Canis familiaris عدد أرجل الكلب: 4
متى نستخدم خصائص الصنف؟

استخدم خصائص الصنف في الحالات التالية:

1. القيم الثابتة (Constants)

قيم لا تتغير أبداً وتنطبق على جميع الكائنات:

class Circle:
    PI = 3.14159  # ثابت رياضي

class Game:
    MAX_PLAYERS = 4      # الحد الأقصى للاعبين
    MIN_PLAYERS = 2      # الحد الأدنى للاعبين
    GAME_NAME = "شطرنج"  # اسم اللعبة
2. العدادات (Counters)

لحساب عدد الكائنات المنشأة:

counter_example.py
class Student:
    total_students = 0  # عداد مشترك
    
    def __init__(self, name):
        self.name = name
        # زيادة العداد عند إنشاء طالب جديد
        Student.total_students += 1
        print(f"تم تسجيل {self.name}. العدد الكلي: {Student.total_students}")

# إنشاء طلاب
s1 = Student("أحمد")
s2 = Student("سارة")
s3 = Student("محمد")

print(f"\nإجمالي الطلاب: {Student.total_students}")
النتيجة
تم تسجيل أحمد. العدد الكلي: 1 تم تسجيل سارة. العدد الكلي: 2 تم تسجيل محمد. العدد الكلي: 3 إجمالي الطلاب: 3
3. الإعدادات الافتراضية المشتركة
class BankAccount:
    bank_name = "البنك الوطني"
    interest_rate = 0.02  # معدل فائدة 2%
    currency = "ريال"
    
    def __init__(self, owner, balance):
        self.owner = owner
        self.balance = balance
تعديل خصائص الصنف

عند تعديل خاصية صنف، يتأثر جميع الكائنات:

modifying_class_attr.py
class Product:
    tax_rate = 0.15  # ضريبة 15%
    
    def __init__(self, name, price):
        self.name = name
        self.price = price
    
    def get_total_price(self):
        return self.price * (1 + Product.tax_rate)

# إنشاء منتجات
p1 = Product("كتاب", 100)
p2 = Product("قلم", 10)

print(f"سعر {p1.name} مع الضريبة: {p1.get_total_price()}")
print(f"سعر {p2.name} مع الضريبة: {p2.get_total_price()}")

# تغيير معدل الضريبة للجميع
print("\n--- تم تغيير الضريبة إلى 20% ---")
Product.tax_rate = 0.20

print(f"سعر {p1.name} مع الضريبة: {p1.get_total_price()}")
print(f"سعر {p2.name} مع الضريبة: {p2.get_total_price()}")
النتيجة
سعر كتاب مع الضريبة: 115.0 سعر قلم مع الضريبة: 11.5 --- تم تغيير الضريبة إلى 20% --- سعر كتاب مع الضريبة: 120.0 سعر قلم مع الضريبة: 12.0

3. خصائص الكائن (Instance Attributes)

ما هي خصائص الكائن؟

خصائص الكائن (Instance Attributes) هي متغيرات تُعرّف باستخدام self داخل دالة __init__ (أو أي دالة أخرى). هذه الخصائص:

  • فريدة لكل كائن - كل كائن له نسخته الخاصة
  • تُخزّن في الكائن نفسه، وليس في الصنف
  • يمكن أن تكون مختلفة تماماً بين كائن وآخر
  • عند تغييرها، يتأثر كائن واحد فقط
مثال بسيط
instance_attributes_basic.py
class Dog:
    def __init__(self, name, age, color):
        # خصائص كائن (فريدة لكل كلب)
        self.name = name
        self.age = age
        self.color = color
    
    def bark(self):
        print(f"{self.name} ينبح: واف واف!")

# إنشاء كلاب بخصائص مختلفة
dog1 = Dog("ماكس", 3, "بني")
dog2 = Dog("بيلا", 5, "أبيض")
dog3 = Dog("تشارلي", 2, "أسود")

# كل كلب له خصائصه الفريدة
print(f"الكلب 1: {dog1.name}, {dog1.age} سنوات, {dog1.color}")
print(f"الكلب 2: {dog2.name}, {dog2.age} سنوات, {dog2.color}")
print(f"الكلب 3: {dog3.name}, {dog3.age} سنوات, {dog3.color}")

# كل كلب ينبح باسمه الخاص
dog1.bark()
dog2.bark()
النتيجة
الكلب 1: ماكس, 3 سنوات, بني الكلب 2: بيلا, 5 سنوات, أبيض الكلب 3: تشارلي, 2 سنوات, أسود ماكس ينبح: واف واف! بيلا ينبح: واف واف!
تعديل خصائص الكائن

عند تعديل خاصية كائن، يتأثر هذا الكائن فقط:

modifying_instance_attr.py
class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self.balance = balance
    
    def deposit(self, amount):
        self.balance += amount
        print(f"تم إيداع {amount}. الرصيد الجديد: {self.balance}")

# إنشاء حسابين
account1 = BankAccount("أحمد", 1000)
account2 = BankAccount("سارة", 2000)

print(f"رصيد {account1.owner}: {account1.balance}")
print(f"رصيد {account2.owner}: {account2.balance}")

# إيداع في حساب واحد فقط
print("\n--- إيداع 500 في حساب أحمد ---")
account1.deposit(500)

# الحساب الآخر لم يتأثر
print(f"\nرصيد {account1.owner}: {account1.balance}")
print(f"رصيد {account2.owner}: {account2.balance}")
النتيجة
رصيد أحمد: 1000 رصيد سارة: 2000 --- إيداع 500 في حساب أحمد --- تم إيداع 500. الرصيد الجديد: 1500 رصيد أحمد: 1500 رصيد سارة: 2000

4. الفرق الدقيق بين النوعين (مهم جداً!)

مثال يوضح الفرق بشكل عملي

لنرى مثالاً يجمع النوعين معاً لفهم الفرق بوضوح:

class_vs_instance.py
class Employee:
    # خاصية صنف (مشتركة)
    company_name = "شركة التقنية المتقدمة"
    min_salary = 3000
    
    def __init__(self, name, salary, department):
        # خصائص كائن (فريدة)
        self.name = name
        self.salary = salary
        self.department = department
    
    def display_info(self):
        print(f"الموظف: {self.name}")
        print(f"الشركة: {self.company_name}")  # من الصنف
        print(f"القسم: {self.department}")      # من الكائن
        print(f"الراتب: {self.salary}")         # من الكائن
        print("-" * 30)

# إنشاء موظفين
emp1 = Employee("أحمد", 5000, "التطوير")
emp2 = Employee("سارة", 6000, "التسويق")

print("=== قبل التغيير ===")
emp1.display_info()
emp2.display_info()

# تغيير اسم الشركة (خاصية صنف)
print("\n=== تغيير اسم الشركة ===")
Employee.company_name = "شركة الابتكار"

# تغيير راتب موظف واحد (خاصية كائن)
emp1.salary = 7000

print("\n=== بعد التغيير ===")
emp1.display_info()
emp2.display_info()

# ملاحظة: اسم الشركة تغير للجميع، لكن الراتب تغير لأحمد فقط
النتيجة
=== قبل التغيير === الموظف: أحمد الشركة: شركة التقنية المتقدمة القسم: التطوير الراتب: 5000 ------------------------------ الموظف: سارة الشركة: شركة التقنية المتقدمة القسم: التسويق الراتب: 6000 ------------------------------ === تغيير اسم الشركة === === بعد التغيير === الموظف: أحمد الشركة: شركة الابتكار القسم: التطوير الراتب: 7000 ------------------------------ الموظف: سارة الشركة: شركة الابتكار القسم: التسويق الراتب: 6000 ------------------------------
تشبيه لفهم الفرق
تشبيه المدرسة والطلاب

تخيل مدرسة بها طلاب:

  • خصائص الصنف مثل: اسم المدرسة، الزي المدرسي، مواعيد الدوام - هذه نفسها لجميع الطلاب
  • خصائص الكائن مثل: اسم الطالب، عمره، درجاته - هذه مختلفة لكل طالب

إذا غيّرت اسم المدرسة، يتأثر الجميع. لكن إذا غيّرت درجة طالب واحد، لا يتأثر الآخرون.

5. الوصول إلى الخصائص وتعديلها

طرق الوصول المختلفة

يمكن الوصول إلى الخصائص بطرق مختلفة:

accessing_attributes.py
class Car:
    wheels = 4  # خاصية صنف
    
    def __init__(self, brand, color):
        self.brand = brand  # خاصية كائن
        self.color = color  # خاصية كائن

car = Car("تويوتا", "أحمر")

# 1. الوصول لخاصية كائن من الكائن
print(f"الماركة: {car.brand}")

# 2. الوصول لخاصية صنف من الكائن
print(f"العجلات: {car.wheels}")

# 3. الوصول لخاصية صنف من الصنف مباشرة
print(f"العجلات: {Car.wheels}")

# 4. تعديل خاصية كائن
car.color = "أزرق"
print(f"اللون الجديد: {car.color}")

# 5. تعديل خاصية صنف
Car.wheels = 6  # تخيل سيارة مستقبلية!
print(f"العجلات الجديدة: {car.wheels}")
إضافة خصائص جديدة ديناميكياً

في بايثون، يمكنك إضافة خصائص جديدة لكائن حتى بعد إنشائه:

class Person:
    def __init__(self, name):
        self.name = name

person = Person("أحمد")
print(f"الاسم: {person.name}")

# إضافة خاصية جديدة ديناميكياً
person.age = 25
person.city = "الرياض"

print(f"العمر: {person.age}")
print(f"المدينة: {person.city}")

# لكن هذه الخصائص خاصة بهذا الكائن فقط!
person2 = Person("سارة")
# print(person2.age)  # AttributeError! لا توجد خاصية age
تحذير: إضافة خصائص ديناميكياً ليست ممارسة جيدة عادةً. من الأفضل تعريف جميع الخصائص في __init__ لتكون واضحة ومنظمة.

6. خطأ شائع: التظليل (Shadowing)

ما هو التظليل؟

التظليل (Shadowing) يحدث عندما تنشئ خاصية كائن بنفس اسم خاصية صنف. هذا يمكن أن يسبب سلوكاً غير متوقع:

shadowing_problem.py
class Counter:
    count = 0  # خاصية صنف
    
    def increment_wrong(self):
        # خطأ: هذا ينشئ خاصية كائن جديدة!
        self.count = self.count + 1
    
    def increment_correct(self):
        # صحيح: تعديل خاصية الصنف
        Counter.count += 1

# مثال على المشكلة
c1 = Counter()
c2 = Counter()

print(f"العداد الأولي: {Counter.count}")

# استخدام الطريقة الخاطئة
c1.increment_wrong()
print(f"c1.count: {c1.count}")        # 1 (خاصية كائن جديدة!)
print(f"Counter.count: {Counter.count}")  # 0 (لم يتغير!)

# استخدام الطريقة الصحيحة
c2.increment_correct()
print(f"Counter.count: {Counter.count}")  # 1 (تغير!)
النتيجة
العداد الأولي: 0 c1.count: 1 Counter.count: 0 Counter.count: 1

الدرس المستفاد: إذا أردت تعديل خاصية صنف، استخدم اسم الصنف (ClassName.attribute) وليس self.attribute.

7. مثال عملي شامل: نظام مكتبة

تطبيق كل المفاهيم

لنبني مثالاً واقعياً يجمع خصائص الصنف وخصائص الكائن:

library_system.py
class Book:
    # خصائص صنف (مشتركة)
    library_name = "مكتبة المعرفة"
    total_books = 0
    late_fee_per_day = 2  # غرامة التأخير (ريال/يوم)
    
    def __init__(self, title, author, isbn, copies):
        # خصائص كائن (فريدة لكل كتاب)
        self.title = title
        self.author = author
        self.isbn = isbn
        self.total_copies = copies
        self.available_copies = copies
        self.borrowed_count = 0
        
        # زيادة عداد الكتب
        Book.total_books += 1
        self.book_id = Book.total_books
        
        print(f"تم إضافة كتاب: {self.title} (ID: {self.book_id})")
    
    def borrow(self):
        """استعارة نسخة من الكتاب"""
        if self.available_copies > 0:
            self.available_copies -= 1
            self.borrowed_count += 1
            print(f"تم استعارة '{self.title}'")
            print(f"النسخ المتبقية: {self.available_copies}/{self.total_copies}")
            return True
        else:
            print(f"عذراً، '{self.title}' غير متاح حالياً")
            return False
    
    def return_book(self):
        """إرجاع نسخة من الكتاب"""
        if self.available_copies < self.total_copies:
            self.available_copies += 1
            print(f"تم إرجاع '{self.title}'")
            print(f"النسخ المتاحة: {self.available_copies}/{self.total_copies}")
        else:
            print(f"خطأ: جميع نسخ '{self.title}' موجودة بالفعل")
    
    def display_info(self):
        """عرض معلومات الكتاب"""
        print(f"\n{'='*50}")
        print(f"معلومات الكتاب")
        print(f"{'='*50}")
        print(f"المكتبة: {Book.library_name}")  # خاصية صنف
        print(f"رقم الكتاب: {self.book_id}")
        print(f"العنوان: {self.title}")
        print(f"المؤلف: {self.author}")
        print(f"ISBN: {self.isbn}")
        print(f"النسخ المتاحة: {self.available_copies}/{self.total_copies}")
        print(f"عدد مرات الاستعارة: {self.borrowed_count}")
        print(f"غرامة التأخير: {Book.late_fee_per_day} ريال/يوم")
        print(f"{'='*50}")
    
    @staticmethod
    def display_library_stats():
        """عرض إحصائيات المكتبة"""
        print(f"\n{'='*50}")
        print(f"إحصائيات {Book.library_name}")
        print(f"{'='*50}")
        print(f"إجمالي الكتب المسجلة: {Book.total_books}")
        print(f"غرامة التأخير: {Book.late_fee_per_day} ريال/يوم")
        print(f"{'='*50}")

# إنشاء كتب
book1 = Book("تعلم Python", "جون سميث", "978-1234567890", 3)
book2 = Book("البرمجة الكائنية", "جين دو", "978-0987654321", 2)
book3 = Book("هياكل البيانات", "بوب جونسون", "978-1122334455", 1)

# عمليات الاستعارة
print("\n--- عمليات الاستعارة ---")
book1.borrow()
book1.borrow()
book2.borrow()
book3.borrow()

# محاولة استعارة كتاب غير متاح
book3.borrow()

# عرض معلومات الكتب
book1.display_info()
book2.display_info()

# إرجاع كتاب
print("\n--- عمليات الإرجاع ---")
book1.return_book()

# عرض إحصائيات المكتبة
Book.display_library_stats()

# تغيير غرامة التأخير (تؤثر على جميع الكتب)
print("\n--- تحديث غرامة التأخير ---")
Book.late_fee_per_day = 3
print(f"الغرامة الجديدة: {Book.late_fee_per_day} ريال/يوم")
print(f"غرامة كتاب 1: {book1.late_fee_per_day} ريال/يوم")
print(f"غرامة كتاب 2: {book2.late_fee_per_day} ريال/يوم")

8. أدوات فحص الخصائص

دوال مفيدة للعمل مع الخصائص

بايثون توفر عدة دوال مدمجة للعمل مع الخصائص:

attribute_tools.py
class Person:
    species = "Human"
    
    def __init__(self, name, age):
        self.name = name
        self.age = age

person = Person("أحمد", 25)

# 1. hasattr() - التحقق من وجود خاصية
print(f"هل يوجد name؟ {hasattr(person, 'name')}")      # True
print(f"هل يوجد salary؟ {hasattr(person, 'salary')}")  # False

# 2. getattr() - الحصول على قيمة خاصية
name = getattr(person, 'name')
print(f"الاسم: {name}")

# مع قيمة افتراضية إذا لم توجد الخاصية
salary = getattr(person, 'salary', 0)
print(f"الراتب: {salary}")  # 0 (القيمة الافتراضية)

# 3. setattr() - تعيين قيمة خاصية
setattr(person, 'city', 'الرياض')
print(f"المدينة: {person.city}")

# 4. delattr() - حذف خاصية
delattr(person, 'city')
print(f"هل يوجد city؟ {hasattr(person, 'city')}")  # False

# 5. dir() - عرض جميع الخصائص والأساليب
print(f"\nجميع الخصائص: {[x for x in dir(person) if not x.startswith('_')]}")

# 6. vars() أو __dict__ - عرض خصائص الكائن فقط
print(f"\nخصائص الكائن: {vars(person)}")
print(f"خصائص الصنف: {vars(Person)}")
ملخص الدرس
  • خصائص الصنف (Class Attributes): مشتركة بين جميع الكائنات، تُعرّف مباشرة في الصنف
  • خصائص الكائن (Instance Attributes): فريدة لكل كائن، تُعرّف في __init__ باستخدام self
  • استخدم خصائص الصنف للقيم الثابتة والعدادات والإعدادات المشتركة
  • استخدم خصائص الكائن للبيانات الفريدة لكل كائن
  • تعديل خاصية صنف يؤثر على جميع الكائنات
  • تعديل خاصية كائن يؤثر على كائن واحد فقط
  • احذر من التظليل (Shadowing) عند استخدام نفس الاسم
  • استخدم ClassName.attribute لتعديل خصائص الصنف
  • استخدم self.attribute لتعديل خصائص الكائن

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

الآن بعد أن فهمنا الخصائص (البيانات)، لننتقل إلى الأساليب (السلوكيات) - الدوال التي تحدد ما يمكن للكائن فعله.

الدرس التالي: الأساليب (Methods) في Python
المحرر الذكي

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

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

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

انضم الآن