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

إذا طورت فهمًا بديهيًا لاختلافاتهم ، فستتمكن من كتابة 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'

 

طرق المثيل #

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

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

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

 

طرق الفصل #

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

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

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

 

طرق ثابتة

الطريقة الثالثة ، MyClass.staticmethodتم تمييزها @staticmethodبمصمم لتعليمها على أنها طريقة ثابتة .

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

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

 

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

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

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

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

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

>>> obj = MyClass()
>>> obj.method()
('instance method called', )

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

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

>>> MyClass.method(obj)
('instance method called', )

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

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

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

>>> obj.classmethod()
('class method called', )

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

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

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

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

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

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

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

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

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

>>> MyClass.classmethod()
('class method called', )

>>> 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()ما يرام ، لكن محاولة استدعاء طريقة المثيل method()فشلت مع TypeError.

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

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

سأبني الأمثلة الخاصة بي حول Pizzaفئة العظام المجردة:

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)

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

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

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الوسيطة في التابعين margheritaو prosciuttofactory بدلاً من استدعاء Pizzaالمُنشئ مباشرةً.

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

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

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

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

كما ترى ، يمكننا استخدام وظائف المصنع لإنشاء Pizzaكائنات جديدة يتم تكوينها بالطريقة التي نريدها. يستخدمون جميعًا نفس __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__وأقبل radiusوسيطة إضافية .

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

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

لنجربها!

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

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

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

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

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

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

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

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

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

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

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

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

الوجبات الجاهزة الرئيسية

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