الخصائص (Attributes) في Python: فهم عميق
الخصائص (Attributes) هي البيانات التي يحملها الكائن. إنها ما يميز كائناً عن آخر. في هذا الدرس، سنتعمق في فهم نوعين مختلفين تماماً من الخصائص: خصائص الصنف (Class Attributes) التي تُشارك بين جميع الكائنات، وخصائص الكائن (Instance Attributes) التي تكون فريدة لكل كائن على حدة. فهم الفرق بينهما ضروري جداً لكتابة كود صحيح وفعال.
1. ما هي الخصائص (Attributes)؟
التعريف الأساسي
الخصائص (Attributes) هي المتغيرات التي تنتمي إلى صنف أو كائن. إنها تمثل "الحالة" (State) أو "البيانات" (Data) التي يحملها الكائن. مثلاً:
- طالب له خصائص: الاسم، العمر، الدرجات، الصف الدراسي
- سيارة لها خصائص: اللون، الموديل، السرعة الحالية، كمية الوقود
- حساب بنكي له خصائص: اسم المالك، الرصيد، رقم الحساب، تاريخ الفتح
في بايثون، هناك نوعان رئيسيان من الخصائص، ولكل منهما استخدام وسلوك مختلف تماماً:
| خصائص الصنف (Class Attributes) | خصائص الكائن (Instance Attributes) |
|---|---|
| مشتركة بين جميع الكائنات | فريدة لكل كائن على حدة |
| تُعرّف مباشرة داخل الصنف | تُعرّف داخل __init__ باستخدام self |
| تتغير لجميع الكائنات معاً | تتغير لكائن واحد فقط |
| مثل "قواعد اللعبة" (نفسها للجميع) | مثل "نقاط اللاعب" (مختلفة لكل لاعب) |
2. خصائص الصنف (Class Attributes)
ما هي خصائص الصنف؟
خصائص الصنف هي متغيرات تُعرّف مباشرة داخل الصنف (وليس داخل أي دالة). هذه الخصائص:
- تُشارك بين جميع الكائنات المنشأة من الصنف
- تُخزّن في الصنف نفسه، وليس في كل كائن على حدة
- يمكن الوصول إليها من الصنف مباشرة أو من أي كائن
- عند تغييرها، تتغير لجميع الكائنات
مثال بسيط جداً
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. القيم الثابتة (Constants)
قيم لا تتغير أبداً وتنطبق على جميع الكائنات:
class Circle:
PI = 3.14159 # ثابت رياضي
class Game:
MAX_PLAYERS = 4 # الحد الأقصى للاعبين
MIN_PLAYERS = 2 # الحد الأدنى للاعبين
GAME_NAME = "شطرنج" # اسم اللعبة
2. العدادات (Counters)
لحساب عدد الكائنات المنشأة:
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}")
3. الإعدادات الافتراضية المشتركة
class BankAccount:
bank_name = "البنك الوطني"
interest_rate = 0.02 # معدل فائدة 2%
currency = "ريال"
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance
تعديل خصائص الصنف
عند تعديل خاصية صنف، يتأثر جميع الكائنات:
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()}")
3. خصائص الكائن (Instance Attributes)
ما هي خصائص الكائن؟
خصائص الكائن (Instance Attributes) هي متغيرات تُعرّف باستخدام self داخل دالة __init__ (أو أي دالة أخرى). هذه الخصائص:
- فريدة لكل كائن - كل كائن له نسخته الخاصة
- تُخزّن في الكائن نفسه، وليس في الصنف
- يمكن أن تكون مختلفة تماماً بين كائن وآخر
- عند تغييرها، يتأثر كائن واحد فقط
مثال بسيط
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()
تعديل خصائص الكائن
عند تعديل خاصية كائن، يتأثر هذا الكائن فقط:
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}")
4. الفرق الدقيق بين النوعين (مهم جداً!)
مثال يوضح الفرق بشكل عملي
لنرى مثالاً يجمع النوعين معاً لفهم الفرق بوضوح:
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()
# ملاحظة: اسم الشركة تغير للجميع، لكن الراتب تغير لأحمد فقط
تشبيه لفهم الفرق
تشبيه المدرسة والطلاب
تخيل مدرسة بها طلاب:
- خصائص الصنف مثل: اسم المدرسة، الزي المدرسي، مواعيد الدوام - هذه نفسها لجميع الطلاب
- خصائص الكائن مثل: اسم الطالب، عمره، درجاته - هذه مختلفة لكل طالب
إذا غيّرت اسم المدرسة، يتأثر الجميع. لكن إذا غيّرت درجة طالب واحد، لا يتأثر الآخرون.
5. الوصول إلى الخصائص وتعديلها
طرق الوصول المختلفة
يمكن الوصول إلى الخصائص بطرق مختلفة:
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) يحدث عندما تنشئ خاصية كائن بنفس اسم خاصية صنف. هذا يمكن أن يسبب سلوكاً غير متوقع:
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 (تغير!)
الدرس المستفاد: إذا أردت تعديل خاصية صنف، استخدم اسم الصنف (ClassName.attribute) وليس self.attribute.
7. مثال عملي شامل: نظام مكتبة
تطبيق كل المفاهيم
لنبني مثالاً واقعياً يجمع خصائص الصنف وخصائص الكائن:
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. أدوات فحص الخصائص
دوال مفيدة للعمل مع الخصائص
بايثون توفر عدة دوال مدمجة للعمل مع الخصائص:
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