ال

الأصناف في Python

49 تمرينًا

نبذة عن الأصناف

تجمع Classes بين البيانات والسلوك. تُستخدم الأصناف لإنشاء نسخ أو instances من البيانات والسلوك المجمّعين معًا، وتُعرف هذه النسخ باسم objects. يمكن للكائنات أن تمثّل كيانات من العالم الواقعي (مثل السيارات أو القطط) أو مفاهيم أكثر تجريدًا (مثل الأعداد الصحيحة أو المركبات أو الثدييات). تُعدّ الأصناف جوهرية في منهج البرمجة كائنية التوجه (OOP)، الذي يطلب من المبرمج أن يفكر في نمذجة المشكلة على هيئة كائن واحد أو أكثر تتفاعل فيما بينها، وتحفظ حالتها، وتؤثر في البيانات.

الأصناف

الأصناف هي تعريفات لأنواع كائنات جديدة، ومنها تُنشأ نسخ instances جديدة من الكائنات. وغالبًا ما تجمع هذه الأصناف البيانات (المعروفة باسم fields أو attributes أو properties أو data members أو variables) مع الكود أو الدوال (المعروفة باسم methods) التي تعمل على تلك البيانات. بهذا المعنى، يكون الصنف مخططًا أو مجموعة تعليمات يمكن من خلالها بناء واستخدام العديد من الكائنات من نوع مماثل. يمكن لبرنامج معقّد أن يحتوي على أصناف كثيرة، يبني كل منها أنواعًا مختلفة متعددة من الكائنات. تُعرف عملية بناء كائن من صنف وتخصيصه باسم instantiation (أو إنشاء نسخة من الصنف).

تعريف الصنف في Python مباشر:

class MyClass:
    # Class body goes here

سمات الصنف

يمكن إضافة حقول الصنف (المعروفة أيضًا بالخصائص أو السمات أو أعضاء البيانات أو المتغيرات) إلى جسم الصنف.

class MyClass:
    number = 5
    string = "Hello!"

يمكن إنشاء نسخة (كائن) من MyClass وربطها باسم:

>>> new_object = MyClass()

# Class is instantiated and resulting object is bound to the "new_object" name (variable).
# Note: the object address 'at 0x15adc55b0' will vary by computer.
>>> new_object
<__main__.MyClass at 0x15adc55b0>

تُشارَك Class attributes بين جميع الكائنات (أو النسخ) المنشأة من صنف، ويمكن الوصول إليها عبر dot notation، أي نقطة . توضع بعد اسم الكائن وقبل اسم السمة:

>>> new_object = MyClass()

# Accessing the class attribute "number" via dot-notation.
>>> new_object.number
5

# Accessing the class attribute "string" via dot-notation.
>>> new_object.string
'Hello!'

# Instantiating an additional object and binding it to the "second_new_object" name.
>>> second_new_object = MyClass()

>>> second_new_object
<__main__.MyClass at 0x15ad99970>

# Second_new_object shares the same class attributes as new_object.
>>> new_object.number == second_new_object.number
True

تُعرَّف سمات الصنف في جسم الصنف نفسه، قبل أي طرق أخرى. وهي مملوكة للصنف، ما يسمح بمشاركتها بين النسخ. ولأن هذه السمات مشتركة بين جميع نسخ الصنف، يمكن الوصول إلى قيمتها والتلاعب بها من الصنف مباشرةً. تغيير قيمة سمات الصنف يغيّر القيمة لجميع الكائنات المنشأة من الصنف:

>>> obj_one = MyClass()
>>> obj_two = MyClass()

# Accessing a class attribute from an object.
>>> obj_two.number
5

# Accessing the class attribute from the class itself.
>>> MyClass.number
5

# Modifying the value of the "number" class attribute.
>>> MyClass.number = 27

# Modifying the "number" class attribute changes the "number" attribute for all objects.
>>> obj_one.number
27

>>> obj_two.number
27

ليس من المفيد كثيرًا أن يكون لديك مجموعة من الكائنات ببيانات متزامنة في كل الأوقات. لحسن الحظ، يمكن تخصيص الكائنات المنشأة من صنف بسماتها الخاصة instance attributes (أو خصائص النسخة أو متغيراتها أو حقولها). وكما يوحي اسمها، فإن سمات النسخة فريدة لكل كائن، ويمكن تعديلها بشكل مستقل.

تخصيص إنشاء الكائنات باستخدام __init__()

تُستخدم "طريقة dunder" الخاصة (اختصار لـ "طريقة الشرطة السفلية المزدوجة") __init__() لتخصيص نسخ الصنف، ويمكن استخدامها لتهيئة سمات النسخة أو خصائصها للكائنات. ونظرًا لدورها في تهيئة سمات النسخة، يُشار إلى __init__() أيضًا باسم class constructor أو initializer. تأخذ __init__() معاملًا واحدًا مطلوبًا يسمى self، ويشير إلى الكائن المنشأ حديثًا. ويمكن بعد ذلك تمرير بيانات سمات النسخة أو خصائصها كوسائط لـ __init__()، بعد المعامل self.

فيما يلي، أصبح لدى MyClass سمات نسخة تسمى location_x و location_y. وكما ترى، أُسندت السمتان إلى الفهرس الأول والثاني من الوسيط location (وهو صف) الذي مُرِّر إلى __init__(). ستُهيَّأ الآن السمتان location_x و location_y لنسخة الصنف عند إنشاء نسخة من الصنف وإنشاء كائن:

class MyClass:
    # These are class attributes, variables, or fields.
    number = 5
    string = "Hello!"

    # This is the class "constructor", with a "location" parameter that is a tuple.
    def __init__(self, location):

        # This is an instance or object property, attribute, or variable.
        # Note that we are unpacking the tuple argument into two separate instance variables.
        self.location_x = location[0]
        self.location_y = location[1]

# Create a new object "new_object_one", with object property (1, 2).
>>> new_object_one = MyClass((1, 2))

# Create a second new object "new_object_two" with object property (-8, -9).
>>> new_object_two = MyClass((-8, -9))

# Note that new_object_one.location_x and new_object_two.location_x two different values.
>>> new_object_one.location_x
1

>>> new_object_two.location_x
-8

لاحظ أنك تحتاج فقط إلى تمرير وسيط واحد عند تهيئة MyClass أعلاه؛ فـ Python تتولى تمرير self عند استدعاء الصنف.

Advanced

هناك طريقة أخرى لإنشاء متغير نسخة هي الوصول ببساطة إلى متغير صنف عبر كائن، وتغييره إلى شيء آخر:

>>> obj_one = MyClass()
>>> obj_two = MyClass()
>>> MyClass.string
'Hello!'

>>> obj_two.string = "Hi!"
>>> obj_two.string
'Hi!'

>>> obj_one.string
'Hello!'

الآن، راقب ما يحدث عندما يتغير متغير الصنف:

>>> MyClass.string = "World!"
>>> obj_two.string

# obj_two.string has not changed
'Hi!'

>>> obj_one.string

# obj_one.string changed
'World!'

أصبحت السمة string الآن متغير نسخة في حالة obj_two، لكنها تبقى متغير صنف في obj_one.

يمكن فعل ذلك أيضًا من داخل الصنف:

class Demo:
    new_var = 3

    def add_two(self):
        self.new_var += 2

في اللحظة التي يُستدعى فيها <object>.add_two() ويُقرأ فيها self.new_var += 2، يتغير new_var من متغير صنف إلى متغير نسخة بالاسم نفسه.

يمكن أن يكون هذا مفيدًا أثناء التهيئة عندما تحتاج جميع نسخ الصنف إلى أن تبدأ بعض السمات بالقيمة نفسها. غير أن متغير النسخة بعد ذلك يحجب متغير الصنف، ما يجعل متغير الصنف غير قابل للوصول من النسخة التي يحجب فيها. بالنظر إلى هذا الوضع، قد يكون أكثر أمانًا ووضوحًا ضبط سمات النسخة من الطريقة __init__() بالشكل self.<attribute>.

الطرق

method هو function مرتبط إما بالصنف نفسه (المعروف باسم طريقة الصنف، وسنناقشه بعمق في تمرين لاحق) أو بـ نسخة من الصنف (كائن). الطرق التي تعمل على كائن أو نسخة يجب تعريفها بحيث يكون self هو المعامل الأول. ويمكنك بعد ذلك تعريف بقية المعاملات كما تفعل مع دالة "عادية" أو غير مرتبطة. الطرق التي تعمل على صنف يجب تعريفها باستخدام المُزخرف @classmethod و(بحسب العُرف) بحيث يكون cls هو المعامل الأول. تُستدعى طرق الصنف على الصنف مباشرةً دون إنشاء كائن من الصنف أولًا.

class MyClass:
    number = 5
    string = "Hello!"

    # Class constructor.
    def __init__(self, location):

        # Instance properties
        self.location_x = location[0]
        self.location_y = location[1]

    # Class method. Uses the @classmethod decorator, and cls as the first parameter.
    # Can be called without first making an instance of the class.
    @classmethod
    def change_string(cls, new_string):
        #Class attributes are referred to with cls.
        cls.string = new_string
        return cls.string

    # Instance method.  Note "self" as first parameter.
    def change_location(self, amount):
        self.location_x += amount
        self.location_y += amount
        return self.location_x, self.location_y

كما في الوصول إلى السمات، فإن استدعاء طريقة بسيط مثل وضع . بعد اسم الكائن وقبل اسم الطريقة. لا تحتاج الطريقة المُستدعاة إلى نسخة من الكائن كمعامل أول؛ إذ تملأ Python قيمة self تلقائيًا:

class MyClass:
    number = 5
    string = "Hello!"

    def __init__(self, location):
        self.location_x = location[0]
        self.location_y = location[1]

    def change_location(self, amount):
        self.location_x += amount
        self.location_y += amount
        return self.location_x, self.location_y

# Make a new test_object with location (3,7)
>>> test_object = MyClass((3,7))
>>> (test_object.location_x, test_object.location_y)
(3,7)

# Call change_location to increase location_x and location_y by 7.
>>> test_object.change_location(7)
(10, 14)

يمكن الوصول إلى سمات الصنف من داخل طرق النسخة بالطريقة نفسها التي يتم الوصول إليها بها من خارج الصنف:

class MyClass:
    number = 5
    string = "Hello!"

    def __init__(self, location):
        self.location_x = location[0]
        self.location_y = location[1]

    # Alter instance variable location_x and location_y
    def change_location(self, amount):
        self.location_x += amount
        self.location_y += amount
        return self.location_x, self.location_y

    # Alter class variable number for all instances from within an instance.
    def increment_number(self):
        # Increment the 'number' class variable by 1.
        MyClass.number += 1


>>> test_object_one = MyClass((0,0))
>>> test_object_one.number
5

>>> test_object_two = MyClass((13, -3))
>>> test_object_two.increment_number()
>>> test_object_one.number
6

استخدام pass كعنصر نائب أو هيكل مبدئي للتنفيذ

في تمارين المفاهيم السابقة والهياكل المبدئية لتمارين التدريب، ستكون قد رأيت الكلمة المفتاحية pass مستخدمة داخل جسم الدوال بدلًا من الكود الفعلي.

الكلمة المفتاحية pass عنصر نائب صحيح نحويًا؛ فهي تمنع Python من إطلاق خطأ نحوي بسبب تعريف دالة أو صنف فارغ. وهي في جوهرها طريقة لتقول لمفسّر Python: "لا تقلق! سأضع الكود هنا في النهاية، لكنني لم أفعل ذلك بعد."

class MyClass:
    number = 5
    string = "Hello!"

    def __init__(self, location):
        self.location_x = location[0]
        self.location_y = location[1]

    # Alter instance variable location_x and location_y.
    def change_location(self, amount):
        self.location_x += amount
        self.location_y += amount
        return  self.location_x, self.location_y

    # Alter class variable number for all instances.
    def increment_number(self):
        # Increment the 'number' class variable by 1.
        MyClass.number += 1

    # This will compile and run without error, but has no current functionality.
    def pending_functionality(self):
       # Stubbing or place-holding the body of this method.
       pass
تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة

تعلّم الأصناف

التدريب مقفل

افتح 6 تمارين إضافية لممارسة الأصناف