الفرق بين Class وInstance Attributes في بايثون | أساسيات 35

الفرق بين class attributes وinstance attributes في Python للمبتدئين

الفرق بين Class Attribute وInstance Attribute في بايثون هو أن الأولى تنتمي إلى class وتكون متاحة لجميع الكائنات، بينما الثانية تنتمي إلى كائن instance محدد وتخزن بياناته الخاصة. عادةً تُكتب Class Attribute مباشرة داخل جسم الكلاس مثل school_name، بينما تُنشأ Instance Attribute غالبًا داخل __init__ باستخدام self مثل self.name. وإذا عدلت الخاصية المشتركة عبر اسم الكلاس يتغير ما تراه الكائنات التي لم تُخفِها بقيمة خاصة، أما الإسناد عبر كائن مثل student.school_name = ... فينشئ غالبًا Instance Attribute بالاسم نفسه فتُخفي قيمة الكلاس لهذا الكائن فقط.

ويُستخدم أيضًا مصطلحا Class Variable وInstance Variable كثيرًا للإشارة إلى الفكرة نفسها في هذا السياق. في هذا الدرس ستفهم المشاركة، وself، وShadowing، و__dict__، ولماذا قد تسبب القوائم المشتركة أخطاء غير متوقعة.

إذا كنت تحتاج مراجعة معنى الكلاس والكائن أولًا، ابدأ من شرح class وobject في بايثون ثم عد إلى هذا الدرس.

{alertInfo} القاعدة السريعة: القيمة المشتركة بين جميع الكائنات مكانها غالبًا على مستوى class، أما البيانات التي تختلف من كائن إلى آخر فأنشئها كـ Instance Attribute باستخدام self.

{getToc} $title={محتوى المقال}

الفرق بين Class Attributes وInstance Attributes باختصار

المقارنة Class Attribute Instance Attribute
لمن تنتمي؟ للكلاس نفسه، وتكون متاحة للكائنات. لكائن محدد فقط.
مكان التعريف المعتاد داخل جسم class وخارج methods. غالبًا داخل __init__ باستخدام self.
مثال school_name = "..." self.name = name
هل تختلف بين الكائنات؟ القيمة الأصلية مشتركة ما لم ينشئ كائن Attribute بالاسم نفسه. نعم، لكل كائن قيمته الخاصة.
التعديل استخدم ClassName.attribute لتعديل قيمة الكلاس. استخدم object.attribute لتعديل كائن واحد.

ما هي Attributes في Python؟

كلمة attribute تعني خاصية أو معلومة مرتبطة بكائن أو class. عندما تكتب student.name فإن name هي خاصية للكائن student. وقد تكون هذه الخاصية مخزنة داخل الكائن نفسه، أو موجودة على مستوى class ويستطيع الكائن الوصول إليها.

في هذا الدرس سنقسم الخصائص إلى نوعين رئيسيين:

  • Instance Attributes / Instance Variables: بيانات تخص كائنًا محددًا، مثل الاسم والرصيد والدرجة.
  • Class Attributes / Class Variables: بيانات موجودة على مستوى الكلاس وتستخدم عندما تكون القيمة عامة أو مشتركة، مثل إعداد افتراضي أو عداد للكائنات.

المهم ليس الاسم فقط، بل مكان تخزين القيمة وطريقة البحث عنها. عند قراءة object.attribute تبحث بايثون أولًا عن الخاصية على الكائن نفسه، وإذا لم تجدها تنتقل إلى الكلاس. سنرى أثر ذلك عمليًا في قسم Shadowing.

مثال سريع: بيانات طالب

تخيل أننا نبني class تمثل طالبًا. الاسم والدرجة يختلفان من طالب إلى آخر، لكن اسم المدرسة واحد لجميع الطلاب.

class Student:
    school_name = "مدرسة بايثون العرب"

    def __init__(self, name, score):
        self.name = name
        self.score = score

student1 = Student("علي", 90)
student2 = Student("سارة", 95)

print(student1.name)
print(student2.name)
print(student1.school_name)
print(student2.school_name)

في هذا المثال:

  • name وscore خصائص خاصة بكل طالب.
  • school_name خاصية مشتركة بين كل الطلاب.
مقارنة بين class attributes وinstance attributes في Python باستخدام مثال الطالب

ما هي Instance Attributes؟

خصائص الكائن أو Instance Attributes هي البيانات التي تخص نسخة واحدة فقط من class. عادة ننشئها داخل __init__ باستخدام self.

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

employee1 = Employee("أحمد", 800)
employee2 = Employee("مريم", 950)

employee1.salary = 1000

print(employee1.salary)
print(employee2.salary)

سيظهر:

1000
950

تغيير راتب employee1 لم يغير راتب employee2، لأن الراتب قيمة تخص كل كائن منفصل.

إنشاء instance attributes باستخدام self داخل init في Python

ما هي Class Attributes؟

خصائص class أو Class Attributes تكتب مباشرة داخل class وخارج الدوال. تكون متاحة لجميع الكائنات التي تنشئها من class نفسه.

مثال: كل منتجات متجر معين تستخدم نسبة ضريبة واحدة:

class Product:
    tax_rate = 0.15

    def __init__(self, name, price):
        self.name = name
        self.price = price

    def price_after_tax(self):
        return self.price * (1 + self.tax_rate)

book = Product("كتاب Python", 20)
course = Product("دورة Python", 50)

print(book.price_after_tax())
print(course.price_after_tax())

لا نكتب self.tax_rate = 0.15 هنا لأننا لا نحتاج نسخة مستقلة من الضريبة لكل منتج. نحتاج قيمة واحدة مشتركة بين كل المنتجات.

استخدام شائع: عدّاد مشترك لكل الكائنات

من الاستخدامات المناسبة لـClass Attribute تتبع عدد الكائنات التي أُنشئت من الكلاس:

class Employee:
    total_employees = 0

    def __init__(self, name):
        self.name = name
        Employee.total_employees += 1

employee1 = Employee("علي")
employee2 = Employee("سارة")

print(Employee.total_employees)

الناتج هو 2 لأن العداد تابع للكلاس كله، وليس لكل موظف نسخة مستقلة منه.

كيف يصل الكائن إلى Class Attribute؟

يمكنك الوصول إلى class attribute بطريقتين:

print(Product.tax_rate)
print(book.tax_rate)

كلتا الطريقتين تعملان، لكن الأفضل عند القراءة والتعديل هو استخدام ClassName.attribute إذا كنت تريد توضيح أن القيمة تخص class كله. أما داخل methods فيمكنك الوصول إليها عبر self.attribute.

عندما لا تجد Python الخاصية داخل الكائن نفسه، تبحث عنها في class. لهذا يستطيع book.tax_rate الوصول إلى Product.tax_rate.

خاصية class مشتركة بين عدة objects في Python

ماذا يحدث عند تعديل Class Attribute؟

إذا عدلت الخاصية من خلال class نفسه، ستتغير القيمة التي يصل إليها كل كائن لا يملك نسخة خاصة من الاسم نفسه.

class Student:
    school_name = "مدرسة بايثون العرب"

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

student1 = Student("علي")
student2 = Student("سارة")

Student.school_name = "أكاديمية بايثون العرب"

print(student1.school_name)
print(student2.school_name)

الناتج:

أكاديمية بايثون العرب
أكاديمية بايثون العرب

هذا مفيد للقيم العامة مثل اسم المؤسسة، نسبة ضريبة، حد أدنى للنجاح، أو إعداد عام داخل المشروع.

ماذا يحدث إذا كان Class Attribute وInstance Attribute بالاسم نفسه؟

هذه النقطة تسبب ارتباكًا عند المبتدئين. انظر إلى المثال التالي:

class Student:
    school_name = "مدرسة بايثون العرب"

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

student1 = Student("علي")
student2 = Student("سارة")

student1.school_name = "مدرسة خاصة بعلي"

print(student1.school_name)
print(student2.school_name)
print(Student.school_name)

الناتج:

مدرسة خاصة بعلي
مدرسة بايثون العرب
مدرسة بايثون العرب

لم تتغير الخاصية المشتركة الأصلية. عند كتابة student1.school_name = ... أنشأت بايثون Instance Attribute جديدة داخل student1 بالاسم نفسه، فأصبحت تخفي Class Attribute عند القراءة من هذا الكائن. يسمى هذا السلوك عادة Attribute Shadowing.

بمعنى آخر: الإسناد باستخدام = عبر الكائن لا يذهب تلقائيًا إلى الكلاس. إذا أردت تغيير القيمة المشتركة نفسها، عدّلها بوضوح عبر Student.school_name.

{alertWarning} معلومة مهمة: عند كتابة object.attribute = value باسم يطابق class attribute، فأنت غالبًا تنشئ قيمة خاصة لهذا الكائن. لتغيير القيمة للجميع، استخدم ClassName.attribute = value.

فحص خصائص الكائن وclass باستخدام __dict__

للتعلم فقط، تستطيع رؤية أين خزنت Python الخصائص باستخدام __dict__:

class Student:
    school_name = "مدرسة بايثون العرب"

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

student = Student("علي")

print(Student.__dict__)
print(student.__dict__)

ستجد أن Student.__dict__ يحتوي على school_name والدوال، بينما student.__dict__ يحتوي على name لأنه خاص بهذا الكائن.

خطأ شائع جدًا: وضع List قابلة للتعديل كـ Class Attribute

القيم القابلة للتعديل مثل list وdict تحتاج انتباهًا. عندما تضع قائمة في Class Attribute، تشير الكائنات إلى الكائن القابل للتعديل نفسه. لذلك فإن عملية مثل append() تغيّر القائمة المشتركة نفسها، ولا تنشئ قائمة خاصة بالكائن كما يحدث عند الإسناد بـ=.

إذا احتجت مراجعة سريعة للقوائم قبل متابعة المثال، راجع شرح Lists في بايثون.

class Student:
    courses = []

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

student1 = Student("علي")
student2 = Student("سارة")

student1.courses.append("Python")

print(student1.courses)
print(student2.courses)

قد تتفاجأ أن الناتج يظهر المادة عند الطالبين، لأنهما يشتركان في القائمة نفسها:

['Python']
['Python']

الحل الصحيح هو إنشاء قائمة مستقلة لكل طالب داخل __init__:

class Student:
    def __init__(self, name):
        self.name = name
        self.courses = []

student1 = Student("علي")
student2 = Student("سارة")

student1.courses.append("Python")

print(student1.courses)
print(student2.courses)
['Python']
[]
خطأ شائع عند استخدام list كـ class attribute في Python

مثال عملي كامل: نظام طلاب بسيط

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

class Student:
    academy_name = "أكاديمية بايثون العرب"
    passing_score = 60

    def __init__(self, name, score):
        self.name = name
        self.score = score
        self.lessons = []

    def add_lesson(self, lesson_name):
        self.lessons.append(lesson_name)

    def is_passed(self):
        return self.score >= self.passing_score

    def show_info(self):
        status = "ناجح" if self.is_passed() else "يحتاج مراجعة"
        print(f"الأكاديمية: {self.academy_name}")
        print(f"الطالب: {self.name}")
        print(f"الدرجة: {self.score}")
        print(f"الحالة: {status}")
        print(f"الدروس: {', '.join(self.lessons)}")

student1 = Student("علي", 85)
student2 = Student("سارة", 55)

student1.add_lesson("أساسيات بايثون")
student1.add_lesson("القوائم")
student2.add_lesson("الشروط")

student1.show_info()
print("-" * 30)
student2.show_info()

في هذا المشروع الصغير:

  • academy_name وpassing_score هما class attributes.
  • name وscore وlessons هي instance attributes.
  • كل طالب يملك قائمة دروس مستقلة لأنها أنشئت عبر self.lessons = [].

متى تستخدم Class Attribute ومتى تستخدم Instance Attribute؟

استخدم Class Attribute عندما...استخدم Instance Attribute عندما...
القيمة يجب أن تكون مشتركة بين كل الكائنات.القيمة تختلف من كائن إلى آخر.
القيمة تمثل إعدادًا عامًا مثل نسبة ضريبة أو اسم مؤسسة.القيمة تمثل حالة أو بيانات خاصة مثل اسم أو سعر أو درجة.
تريد تغيير القيمة للجميع من مكان واحد.تريد تعديل القيمة لكائن واحد دون التأثير على الآخرين.
القيمة ثابتة أو غير قابلة للتعديل غالبًا.القيمة قائمة أو قاموس أو بيانات قابلة للتعديل لكل كائن.

أخطاء شائعة يجب تجنبها

1. وضع بيانات خاصة في Class Attribute

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

2. تعديل Class Attribute من object دون قصد

تذكر أن student.school_name = "..." ينشئ غالبًا نسخة خاصة بهذا الطالب. استخدم Student.school_name = "..." إذا كنت تريد التغيير للجميع.

3. استخدام قائمة أو قاموس مشترك لكل الكائنات

إذا كانت القائمة يجب أن تكون مستقلة لكل كائن، أنشئها داخل __init__ باستخدام self.

4. استخدام Class Attribute لكل شيء

Class attributes مفيدة، لكنها ليست بديلًا عن instance attributes. اسأل دائمًا: هل هذه القيمة تخص كل الكائنات أم هذا الكائن فقط؟

تمرين عملي

أنشئ class باسم Book تحقق الشروط التالية:

  • لديها class attribute باسم library_name وقيمتها "مكتبة بايثون العرب".
  • لديها instance attributes باسم title وauthor.
  • أنشئ كتابين مختلفين واطبع بياناتهما.
  • غيّر library_name من خلال class ثم اطبعها لكلا الكتابين.

حل مقترح

class Book:
    library_name = "مكتبة بايثون العرب"

    def __init__(self, title, author):
        self.title = title
        self.author = author

    def show_info(self):
        print(f"المكتبة: {self.library_name}")
        print(f"العنوان: {self.title}")
        print(f"المؤلف: {self.author}")

book1 = Book("تعلم Python", "إيليا علي")
book2 = Book("مشاريع Python", "فريق بايثون العرب")
Book.library_name = "مكتبة بايثون العرب الرقمية"

book1.show_info()
print("-" * 25)
book2.show_info()

الخطوة التالية بعد Class وInstance Attributes

بعد أن فهمت الفرق بين القيم المشتركة والقيم الخاصة بكل كائن، فالخطوة المنطقية التالية هي شرح الوراثة Inheritance في بايثون، حيث سترى كيف ترث الكلاسات خصائص وسلوكًا من كلاسات أخرى. ويمكنك العودة في أي وقت إلى كورس أساسيات بايثون لمتابعة الدروس بالترتيب.

الخلاصة

  • Instance Attributes تخص كائنًا واحدًا، وتُنشأ غالبًا باستخدام self داخل __init__.
  • Class Attributes تكتب على مستوى class وتكون متاحة للكائنات ما لم يخفِها كائن بقيمة خاصة.
  • غيّر Class Attribute عبر ClassName.attribute عندما تريد تعديل القيمة الموجودة على الكلاس.
  • الإسناد عبر object.attribute ينشئ أو يعدّل Instance Attribute، وقد يخفي Class Attribute بالاسم نفسه.
  • لا تستخدم list أو dict كـ class attribute عندما تريد بيانات مستقلة لكل object.
{alertSuccess} قاعدة ذهبية: اسأل قبل إنشاء أي attribute: هل هذه القيمة مشتركة بين جميع الكائنات؟ ضعها داخل class. هل تختلف من كائن إلى آخر؟ أنشئها باستخدام self داخل __init__.

مصادر خارجية رسمية للتوسع

أسئلة شائعة

ما الفرق بين class attribute وinstance attribute في Python؟

Class Attribute توجد على مستوى الكلاس وتكون متاحة لجميع الكائنات، أما Instance Attribute فتوجد على كائن محدد وتخزن حالته الخاصة. وإذا امتلك الكائن Attribute بالاسم نفسه فإنها تُقدَّم عند البحث وتخفي قيمة الكلاس لهذا الكائن.

هل يمكن الوصول إلى class attribute من خلال object؟

نعم، مثل student.school_name. لكن من الأفضل استخدام Student.school_name عندما تريد توضيح أن الخاصية مشتركة وتابعة للكلاس كله.

لماذا لم تتغير القيمة عند تعديلها من object واحد؟

لأن كتابة student.school_name = "..." قد تنشئ instance attribute جديدة لهذا الكائن بدل تعديل class attribute الأصلية.

هل أضع list داخل class أم داخل init؟

إذا كانت كل نسخة تحتاج قائمة مستقلة، ضعها داخل __init__ كالتالي: self.items = []. وضعها مباشرة داخل class يجعل القائمة مشتركة بين جميع الكائنات.

هل class attributes مناسبة للقيم الثابتة فقط؟

هي مناسبة للقيم المشتركة عمومًا، سواء كانت ثابتة أو يمكن تغييرها للجميع مثل نسبة ضريبة أو عدد الكائنات. المهم ألا تكون بيانات خاصة بكائن واحد.

إرسال تعليق

أحدث أقدم