جدول المحتويات

 

  • طرق المثيل والفئة والثابت – نظرة عامة

طرق المثيل

طرق الفصل

طرق ثابتة

  • دعونا نراهم في العمل!
  • مصانع البيتزا اللذيذة مع classmethod
  • متى يجب استخدام الأساليب الثابتة
  • الماخذ الرئيسية


في هذا البرنامج التعليمي ، سأساعد في إزالة الغموض الذي يقف وراء أساليب الفصل والطرق الثابتة وطرق المثيل العادية.

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

طرق المثيل والفئة والثابت – نظرة عامة

لنبدأ بكتابة فئة (Python 3) تحتوي على أمثلة بسيطة لجميع أنواع الطرق الثلاثة:

class MyClass:
    def method(self):
        return 'instance method called', self

    @classmethod
    def classmethod(cls):
        return 'class method called', cls

    @staticmethod
    def staticmethod():
        return 'static method called'

ملاحظة: بالنسبة لمستخدمي Python 2: يتوفر الديكورانstaticmethod و @ classmethod اعتبارًا من Python 2.4 وسيعمل هذا المثال كما هو. بدلاً من استخدام فئة بسيطة MyClass: تصريح قد تختار إعلان فئة نمط جديد موروث من كائن باستخدام الفئة MyClass (كائن): بناء الجملة. بخلاف ذلك أنت على ما يرام.

طرق المثيل

الطريقة الأولى في MyClass ، تسمى طريقة ، هي طريقة مثيل عادية. هذا هو نوع الطريقة الأساسية الخالية من الرتوش التي ستستخدمها معظم الوقت. يمكنك أن ترى أن الطريقة تأخذ معلمة واحدة ، self ، والتي تشير إلى مثيل MyClass عندما يتم استدعاء الطريقة (ولكن بالطبع يمكن أن تقبل طرق المثيل أكثر من معامل واحد فقط).

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

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

طرق الفصل

دعنا نقارن ذلك بالطريقة الثانية ، MyClass.classmethod. لقد قمت بتمييز هذه الطريقة باستخدام مصممclassmethod لوضع علامة عليها كطريقة للفصل الدراسي.

بدلاً من قبول معلمة ذاتية ، تأخذ طرق الفئة معلمة cls تشير إلى الفئة – وليس مثيل الكائن – عند استدعاء الطريقة.

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

طرق ثابتة

الطريقة الثالثة ، MyClass.staticmethod تم تمييزها باستخدامstaticmethod decorator للإشارة إليها كطريقة ثابتة.

لا يأخذ هذا النوع من الطرق أي معلمة ذاتية أو معلمة cls (ولكن بالطبع يمكنك قبول عدد عشوائي من المعلمات الأخرى).

لذلك لا يمكن للطريقة الثابتة تعديل حالة الكائن أو حالة الفئة. الطرق الثابتة مقيدة بالبيانات التي يمكنها الوصول إليها – وهي في الأساس وسيلة لمساحة الأسماء لأساليبك.

دعونا نراهم في العمل!

أعلم أن هذه المناقشة كانت نظرية إلى حد ما حتى هذه النقطة. وأعتقد أنه من المهم أن تطور فهمًا بديهيًا لكيفية اختلاف أنواع الأساليب هذه في الممارسة. سنستعرض بعض الأمثلة الملموسة الآن.

دعنا نلقي نظرة على كيفية تصرف هذه الأساليب في العمل عندما نسميها. سنبدأ بإنشاء مثيل للفصل ثم استدعاء الطرق الثلاث المختلفة عليه.

تم إعداد MyClass بطريقة تجعل تنفيذ كل طريقة يعرض مجموعة تحتوي على معلومات لنا لتتبع ما يحدث – وأي أجزاء من الفئة أو الكائن يمكن للطريقة الوصول إليها.

إليك ما يحدث عندما نطلق على طريقة المثيل:

>>> obj = MyClass()
>>> obj.method()
('instance method called', <MyClass instance at 0x10205d190>)


أكد هذا أن الطريقة (طريقة المثيل) لديها حق الوصول إلى مثيل الكائن (تتم طباعته كـ <مثيل MyClass>) عبر الوسيطة self.

عندما يتم استدعاء الطريقة ، تستبدل Python الوسيطة self مع كائن المثيل obj. يمكننا تجاهل السكر النحوي لبناء جملة dot-call (obj.method ()) وتمرير كائن المثيل يدويًا للحصول على نفس النتيجة:

>>> MyClass.method(obj)
('instance method called', <MyClass instance at 0x10205d190>)


هل يمكنك تخمين ما سيحدث إذا حاولت استدعاء الطريقة دون إنشاء مثيل أولاً؟

بالمناسبة ، يمكن أيضًا لعمليات المثال الوصول إلى الفئة نفسها من خلال سمة self .__ class__. هذا يجعل طرق المثيل قوية من حيث قيود الوصول – يمكنها تعديل الحالة على مثيل الكائن والفئة نفسها.

دعونا نجرب طريقة الفصل بعد ذلك:

>>> obj.classmethod()
('class method called', <class MyClass at 0x101a2f4c8>)


أظهر استدعاء classmethod () أنه لا يمتلك حق الوصول إلى الكائن <MyClass example> ، ولكن فقط إلى الكائن <class MyClass> ، الذي يمثل الفئة نفسها (كل شيء في Python هو كائن ، حتى الفئات نفسها).

لاحظ كيف تمرر Python الفئة تلقائيًا كأول وسيط إلى الوظيفة عندما نستدعي MyClass.classmethod (). استدعاء طريقة في بايثون من خلال بناء الجملة النقطي يؤدي إلى هذا السلوك. تعمل المعلمة الذاتية في طرق المثيل بنفس الطريقة.

يرجى ملاحظة أن تسمية هذه المعلمات self و cls هي مجرد اصطلاح. يمكنك تسميتها بنفس السهولة the_object و the_class والحصول على نفس النتيجة. كل ما يهم هو أنه يتم وضعهم أولاً في قائمة المعلمات للطريقة.

حان الوقت لاستدعاء الطريقة الثابتة الآن:

>>> obj.staticmethod()
'static method called'


هل رأيت كيف أطلقنا على الطريقة الثابتة () على الكائن وتمكنا من القيام بذلك بنجاح؟ يتفاجأ بعض المطورين عندما يعلمون أنه من الممكن استدعاء طريقة ثابتة في مثيل كائن.

وراء الكواليس ، تقوم Python ببساطة بفرض قيود الوصول من خلال عدم المرور في النفس أو وسيطة cls عندما يتم استدعاء طريقة ثابتة باستخدام بناء الجملة.

هذا يؤكد أن الأساليب الثابتة لا يمكنها الوصول إلى حالة مثيل الكائن ولا حالة الفئة. إنها تعمل مثل الوظائف العادية ولكنها تنتمي إلى مساحة اسم الفصل (وكل مثيل).

الآن ، دعنا نلقي نظرة على ما يحدث عندما نحاول استدعاء هذه العمليات على الفئة نفسها – دون إنشاء مثيل كائن مسبقًا:

>>> MyClass.classmethod()
('class method called', <class MyClass at 0x101a2f4c8>)

>>> MyClass.staticmethod()
'static method called'

>>> MyClass.method()
TypeError: unbound method method() must
    be called with MyClass instance as first
    argument (got nothing instead)


تمكنا من استدعاء classmethod () و staticmethod () على ما يرام ، لكن محاولة استدعاء طريقة المثيل () فشلت مع خطأ TypeError.

وهذا أمر متوقع – هذه المرة لم نقم بإنشاء مثيل كائن وحاولنا استدعاء دالة مثيل مباشرة في مخطط الفصل نفسه. هذا يعني أنه لا توجد طريقة لبايثون لملء حجة الذات وبالتالي تفشل المكالمة.

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

سأبني الأمثلة الخاصة بي حول فئة البيتزا العارية:

class Pizza:
    def __init__(self, ingredients):
        self.ingredients = ingredients

    def __repr__(self):
        return f'Pizza({self.ingredients!r})'
>>> Pizza(['cheese', 'tomatoes'])
Pizza(['cheese', 'tomatoes'])

ملاحظة: هذا المثال من الكود والمثال الآخر في البرنامج التعليمي يستخدم Python 3.6 f-strings لبناء السلسلة التي تم إرجاعها بواسطة __repr__. في Python 2 وإصدارات Python 3 قبل 3.6 ، يمكنك استخدام تعبير تنسيق سلسلة مختلف ، على سبيل المثال:

def __repr__(self):
    return 'Pizza(%r)' % self.ingredients

مصانع البيتزا اللذيذة مع classmethod

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

Pizza(['mozzarella', 'tomatoes'])
Pizza(['mozzarella', 'tomatoes', 'ham', 'mushrooms'])
Pizza(['mozzarella'] * 4)


اكتشف الإيطاليون تصنيفهم للبيتزا منذ قرون ، ولذا فإن هذه الأنواع اللذيذة من البيتزا لها أسماء خاصة بها. من الأفضل أن نستفيد من ذلك ونمنح مستخدمي صف البيتزا لدينا واجهة أفضل لإنشاء عناصر البيتزا التي يرغبون فيها.

هناك طريقة لطيفة ونظيفة للقيام بذلك وهي استخدام طرق الفصل كوظائف المصنع لأنواع البيتزا المختلفة التي يمكننا إنشاؤها:

class Pizza:
    def __init__(self, ingredients):
        self.ingredients = ingredients

    def __repr__(self):
        return f'Pizza({self.ingredients!r})'

    @classmethod
    def margherita(cls):
        return cls(['mozzarella', 'tomatoes'])

    @classmethod
    def prosciutto(cls):
        return cls(['mozzarella', 'tomatoes', 'ham'])


لاحظ كيف أستخدم الوسيطة cls في أساليب مصنع مارجريتا وبروسكيوتو بدلاً من الاتصال بمُنشئ البيتزا مباشرةً.

هذه خدعة يمكنك استخدامها لاتباع مبدأ لا تكرر نفسك (جاف). إذا قررنا إعادة تسمية هذه الفئة في وقت ما ، فلن نضطر إلى تذكر تحديث اسم المُنشئ في جميع وظائف مصنع أسلوب الفصل.

الآن ، ماذا يمكننا أن نفعل بأساليب المصنع هذه؟ دعونا نجربهم:

>>> Pizza.margherita()
Pizza(['mozzarella', 'tomatoes'])

>>> Pizza.prosciutto()
Pizza(['mozzarella', 'tomatoes', 'ham'])


كما ترى ، يمكننا استخدام وظائف المصنع لإنشاء كائنات بيتزا جديدة تم تكوينها بالطريقة التي نريدها. يستخدمون جميعًا نفس المُنشئ __init__ داخليًا ويوفرون اختصارًا لتذكر جميع المكونات المختلفة.

هناك طريقة أخرى للنظر إلى هذا الاستخدام لطرق الفصل وهي أنها تسمح لك بتحديد منشئات بديلة لفئاتك.

تسمح Python بطريقة __init__ واحدة فقط لكل فئة. باستخدام طرق الفصل ، من الممكن إضافة أكبر عدد ممكن من المنشئات البديلة حسب الضرورة. هذا يمكن أن يجعل واجهة فصولك ذاتية التوثيق (إلى حد معين) وتبسيط استخدامها.

متى يجب استخدام الأساليب الثابتة

من الأصعب قليلاً أن تأتي بمثال جيد هنا. لكن أخبرك بماذا ، سأستمر في تمديد تشبيه البيتزا أرق وأرق … (لذيذ!)

هذا ما توصلت إليه:

import math

class Pizza:
    def __init__(self, radius, ingredients):
        self.radius = radius
        self.ingredients = ingredients

    def __repr__(self):
        return (f'Pizza({self.radius!r}, '
                f'{self.ingredients!r})')

    def area(self):
        return self.circle_area(self.radius)

    @staticmethod
    def circle_area(r):
        return r ** 2 * math.pi


الآن ماذا تغيرت هنا؟ أولاً ، قمت بتعديل المُنشئ و __repr__ لقبول وسيطة نصف قطر إضافية.

أضفت أيضًا طريقة مثيل area () التي تحسب وتعيد منطقة البيتزا (قد يكون هذا أيضًا مرشحًا جيدًا لـproperty – ولكن مهلا ، هذا مجرد مثال على لعبة).

بدلاً من حساب المساحة داخل المنطقة () مباشرةً ، باستخدام صيغة منطقة الدائرة المعروفة جيدًا ، قمت بحساب ذلك إلى طريقة منطقة دائرة منفصلة () ثابتة.

دعونا نجربها!

>>> p = Pizza(4, ['mozzarella', 'tomatoes'])
>>> p
Pizza(4, ['mozzarella', 'tomatoes'])
>>> p.area()
50.26548245743669
>>> Pizza.circle_area(4)
50.26548245743669


بالتأكيد ، هذا مثال بسيط بعض الشيء ، لكنه سيساعد في شرح بعض الفوائد التي توفرها الأساليب الثابتة.

كما تعلمنا ، لا يمكن للطرق الثابتة الوصول إلى فئة أو حالة حالة لأنها لا تأخذ cls أو حجة ذاتية. هذا قيد كبير – ولكنه أيضًا إشارة رائعة لإظهار أن طريقة معينة مستقلة عن كل شيء آخر حولها.

في المثال أعلاه ، من الواضح أن circ_area () لا يمكنها تعديل الفصل أو مثيل الفصل بأي شكل من الأشكال. (بالتأكيد ، يمكنك دائمًا التغلب على ذلك باستخدام متغير عام ولكن هذا ليس هو الهدف هنا.)

الآن ، لماذا هذا مفيد؟

لا يعد وضع علامة على طريقة كطريقة ثابتة مجرد تلميح إلى أن الطريقة لن تعدل فئة أو حالة حالة – يتم فرض هذا التقييد أيضًا بواسطة وقت تشغيل Python.

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

بعبارة أخرى ، فإن استخدام الأساليب الثابتة وطرق الفصل هي طرق لإيصال نية المطور مع فرض هذه النية بما يكفي لتجنب معظم أخطاء العقل والأخطاء التي قد تفسد التصميم.

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

تتمتع الطرق الثابتة أيضًا بفوائد عندما يتعلق الأمر بكتابة كود الاختبار.

نظرًا لأن طريقة Circle_area () مستقلة تمامًا عن بقية الصف ، فمن الأسهل اختبارها.

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

الماخذ الرئيسية

  • تحتاج طرق المثيل إلى مثيل فئة ويمكنها الوصول إلى المثيل من خلال self.
  • لا تحتاج طرق الفصل إلى مثيل الفصل. لا يمكنهم الوصول إلى المثيل (ذاتي) لكن يمكنهم الوصول إلى الفصل نفسه عبر cls.
  • لا تملك الأساليب الثابتة إمكانية الوصول إلى cls أو self. إنها تعمل مثل الوظائف العادية ولكنها تنتمي إلى مساحة اسم الفصل.
  • تتواصل الطرق الثابتة والطبقية (إلى حد ما) وتفرض نية المطور حول تصميم الفصل. يمكن أن يكون لهذا فوائد الصيانة.