طرق خاصة دون تسطير وواجهات في بيثون



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

معدلات الوصول


تقيد معدّلات الوصول الوصول إلى الكائنات - إلى أساليب الفصل ، أو إلى الفئات الفرعية - على أساليب الفصل الأصل. يساعد استخدام مُعدِّلات الوصول على إخفاء البيانات في الفصل الدراسي بحيث لا يمكن لأي شخص في الخارج التدخل في عمل هذه الفئة.

تتوفر طرق خاصة فقط داخل الفصل ، محمية (من الداخل) - داخل الفصل وفي فصول تابعة.

كيف يتم تطبيق الطرق الخاصة والمحمية في بايثون


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

class Car: def _start_engine(self): return "Engine's sound." def run(self): return self._start_engine() if __name__ == '__main__': car = Car() assert "Engine's sound." == car.run() assert "Engine's sound." == car._start_engine() 

يمكن تحديد العيوب التالية:

  • إذا قامت طريقة _start_engine بتحديث بعض متغيرات الفئة أو حالة الإبقاء عليها ، ولم تُرجع فقط "حساب غبي" ، فربما تكون قد قطعت شيئًا عن العمل في المستقبل مع الفصل. لا تسمح لنفسك بإصلاح شيء ما في محرك سيارتك ، لأنك إذاً لن تذهب إلى أي مكان ، أليس كذلك؟
  • نقطة التدفق من النقطة السابقة - للتأكد من أنه يمكنك "بأمان" (استدعاء الأسلوب لا يضر الطبقة نفسها) استخدام طريقة محمية - تحتاج إلى النظر في رمزها وقضاء الوقت.
  • يأمل مؤلفو المكتبات ألا يستخدم أي شخص الأساليب المحمية والخاصة للفصول التي تستخدمها في مشاريعك. لذلك ، يمكنهم تغيير تطبيقه في أي إصدار (لن يؤثر ذلك على الطرق العامة بسبب التوافق مع الإصدارات السابقة ، لكنك ستعاني).
  • يتوقع مؤلف الفصل ، زميلك ، ألا تزيد الدين الفني للمشروع باستخدام طريقة محمية أو خاصة خارج الفصل الذي أنشأه. بعد كل شيء ، سيتعين على الشخص الذي سيقوم بإعادة تشكيلها أو تعديلها (طريقة الفصل الخاص) أن يتأكد (على سبيل المثال ، من خلال الاختبارات) من أن تغييراته لن تخالف الكود. وإذا كسرها ، فسوف يتعين عليه قضاء بعض الوقت في محاولة لحل هذه المشكلة (مع عكاز ، لأنه يحتاج إليها بالأمس).
  • ربما تتأكد من أن المبرمجين الآخرين لا يستخدمون طرقًا محمية أو خاصة في مراجعة التعليمات البرمجية و "الإيقاع من أجلها" ، لذا يمكنك قضاء بعض الوقت.

كيفية تنفيذ الأساليب المحمية باستخدام مكتبة


 from accessify import protected class Car: @protected def start_engine(self): return "Engine's sound." def run(self): return self.start_engine() if __name__ == '__main__': car = Car() assert "Engine's sound." == car.run() car.start_engine() 

ستؤدي محاولة استدعاء طريقة start_engine خارج الفصل إلى حدوث الخطأ التالي (الطريقة غير متوفرة وفقًا لسياسة الوصول):

 Traceback (most recent call last): File "examples/access/private.py", line 24, in <module> car.start_engine() File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/main.py", line 92, in private_wrapper class_name=instance_class.__name__, method_name=method.__name__, accessify.errors.InaccessibleDueToItsProtectionLevelException: Car.start_engine() is inaccessible due to its protection level 

باستخدام المكتبة:

  • لا تحتاج إلى استخدام الشرطة السفلية القبيحة (ذاتية) أو الشرطة السفلية المزدوجة.
  • يمكنك الحصول على طريقة جميلة (ذاتية) لتطبيق معدّلات الوصول في الكود - ديكورات خاصة ومحمية .
  • نقل المسؤولية من شخص إلى مترجم.

كيف يعمل:

  1. الديكور الخاص أو المحمي - وهو الديكور "الأكثر ارتفاعًا" ، ينطلق قبل طريقة الفصل التي تم إعلانها كمعدل وصول خاص أو محمي.


  2. باستخدام مكتبة الفحص المضمّنة ، يسترجع الديكور الكائن الحالي من مكدس الاستدعاءات - inspect.currentframe () . يحتوي هذا الكائن على السمات التالية المفيدة لنا: مساحة الاسم (السكان المحليين) والرابط الخاص بالكائن السابق من مكدس الاستدعاءات (الكائن الذي يستدعي الأسلوب باستخدام معدّل الوصول).


    (توضيح مبسط للغاية)
  3. inspect.currentframe (). f_back - استخدم هذه السمة للتحقق مما إذا كان الكائن السابق من مكدس الاستدعاءات موجودًا في نص الفصل الدراسي أم لا. للقيام بذلك ، انظر إلى مساحة الاسم - f_locals . إذا كانت هناك سمة ذاتية في مساحة الاسم ، يتم استدعاء الأسلوب داخل الفصل الدراسي ، إن لم يكن ، خارج الفصل. إذا اتصلت بطريقة باستخدام معدّل وصول خاص أو محمي خارج الفصل ، فسيحدث خطأ في سياسة الوصول.

واجهات


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

مثال


لدينا فئة مستخدم تستخدم كائن التخزين لإنشاء مستخدم جديد.

 class User: def __init__(self, storage): self.storage = storage def create(self, name): return storage.create_with_name(name=name) 

يمكنك حفظ المستخدم في قاعدة البيانات باستخدام DatabaseStorage.create_with_name .

 class DatabaseStorage: def create_with_name(self, name): ... 

يمكنك حفظ المستخدم في الملفات باستخدام FileStorage.create_with_name .

 class FileStorage: def create_with_name(self, name): ... 

نظرًا لحقيقة أن تواقيع أساليب create_with_name (الاسم ، الوسائط) هي نفسها للفئات - فئة المستخدم لا داعي للقلق بشأن الكائن الذي تم استبداله إذا كان كلاهما لهما نفس الطرق. يمكن تحقيق ذلك في حالة قيام فئتي FileStorage و DatabaseStorage بتطبيق نفس الواجهة (أي ، أنهما ملزمان بالعقد لتحديد بعض الأسلوب مع المنطق الداخلي).

 if __name__ == '__main__': if settings.config.storage = FileStorage: storage = FileStorage() if settings.config.storage = DatabaseStorage: storage = DatabaseStorage() user = User(storage=storage) user.create_with_name(name=...) 

كيفية العمل مع واجهات باستخدام المكتبة


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

 from accessify import implements class HumanInterface: @staticmethod def eat(food, *args, allergy=None, **kwargs): pass if __name__ == '__main__': @implements(HumanInterface) class Human: pass 

سينتهي البرنامج النصي بسبب الخطأ التالي:

 Traceback (most recent call last): File "examples/interfaces/single.py", line 18, in <module> @implements(HumanInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 66, in decorator interface_method_arguments=interface_method.arguments_as_string, accessify.errors.InterfaceMemberHasNotBeenImplementedException: class Human does not implement interface member HumanInterface.eat(food, args, allergy, kwargs) 

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

 from accessify import implements class HumanInterface: @staticmethod def eat(food, *args, allergy=None, **kwargs): pass if __name__ == '__main__': @implements(HumanInterface) class Human: @staticmethod def eat(food): pass 

سينتهي البرنامج النصي بسبب الخطأ التالي:

 Traceback (most recent call last): File "examples/interfaces/single_arguments.py", line 16, in <module> @implements(HumanInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 87, in decorator interface_method_arguments=interface_method.arguments_as_string, accessify.errors.InterfaceMemberHasNotBeenImplementedWithMismatchedArgumentsException: class Human implements interface member HumanInterface.eat(food, args, allergy, kwargs) with mismatched arguments 

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

 from accessify import implements, private class HumanInterface: @private @staticmethod def eat(food, *args, allergy=None, **kwargs): pass if __name__ == '__main__': @implements(HumanInterface) class Human: @staticmethod def eat(food, *args, allergy=None, **kwargs): pass 

سينتهي البرنامج النصي بسبب الخطأ التالي:

 Traceback (most recent call last): File "examples/interfaces/single_access.py", line 18, in <module> @implements(HumanInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 77, in decorator interface_method_name=interface_method.name, accessify.errors.ImplementedInterfaceMemberHasIncorrectAccessModifierException: Human.eat(food, args, allergy, kwargs) mismatches HumanInterface.eat() member access modifier. 

يمكن للفصل تنفيذ عدة واجهات (عدد غير محدود). إذا قام الفصل بتنفيذ عدة واجهات ، فيجب أن يحتوي الفصل على جميع أساليب جميع الواجهات ، بما في ذلك الوسائط الواردة ومعدلات الوصول . في المثال أدناه ، تطبق فئة Human طريقة تناول لواجهة HumanBasicsInterface ، لكنها لا تطبق طريقة الحب لواجهة HumanSoulInterface.

 from accessify import implements class HumanSoulInterface: def love(self, who, *args, **kwargs): pass class HumanBasicsInterface: @staticmethod def eat(food, *args, allergy=None, **kwargs): pass if __name__ == '__main__': @implements(HumanSoulInterface, HumanBasicsInterface) class Human: def love(self, who, *args, **kwargs): pass 

سينتهي البرنامج النصي بسبب الخطأ التالي:

 Traceback (most recent call last): File "examples/interfaces/multiple.py", line 19, in <module> @implements(HumanSoulInterface, HumanBasicsInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 66, in decorator interface_method_arguments=interface_method.arguments_as_string, accessify.errors.InterfaceMemberHasNotBeenImplementedException: class Human does not implement interface member HumanBasicsInterface.eat(food, args, allergy, kwargs) 

ميزة Killer - يمكن لطريقة الواجهة "تحديد" الأخطاء التي تحدث في طريقة الفصل التي يجب أن "ترمي". في المثال أدناه ، يتم "إعلان" أن طريقة "الحب" لواجهة "HumanInterface" يجب أن ترمي استثناء "HumanDoesNotExistError" و
"HumanAlreadyInLoveError" ، ولكن طريقة "الحب" للفئة "الإنسانية" لا "ترمي" واحدة منها.

 from accessify import implements, throws class HumanDoesNotExistError(Exception): pass class HumanAlreadyInLoveError(Exception): pass class HumanInterface: @throws(HumanDoesNotExistError, HumanAlreadyInLoveError) def love(self, who, *args, **kwargs): pass if __name__ == '__main__': @implements(HumanInterface) class Human: def love(self, who, *args, **kwargs): if who is None: raise HumanDoesNotExistError('Human whom need to love does not exist') 

سينتهي البرنامج النصي بسبب الخطأ التالي:

 Traceback (most recent call last): File "examples/interfaces/throws.py", line 21, in <module> @implements(HumanInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 103, in decorator class_method_arguments=class_member.arguments_as_string, accessify.errors.DeclaredInterfaceExceptionHasNotBeenImplementedException: Declared exception HumanAlreadyInLoveError by HumanInterface.love() member has not been implemented by Human.love(self, who, args, kwargs) 

لتلخيص ، باستخدام المكتبة:

  • يمكنك تنفيذ واحد أو أكثر من واجهات.
  • يتم الجمع بين واجهات مع معدلات الوصول.
  • ستحصل على فصل بين الواجهات والفئات التجريدية ( وحدة abc في Python ) ، والآن لن تحتاج إلى استخدام الطبقات المجردة كواجهات إذا كنت قد فعلت (فعلت).
  • بالمقارنة مع الطبقات المجردة. إذا لم تقم بتحديد جميع وسيطات الطريقة من الواجهة ، فستحصل على خطأ في استخدام فئة مجردة - لا.
  • بالمقارنة مع الطبقات المجردة. باستخدام الواجهات ، سوف تحصل على خطأ أثناء إنشاء الفصل (عندما كتبت الفصل وتسمى ملف * .py ). في الفصول التجريدية ، سوف تحصل على خطأ بالفعل في مرحلة استدعاء طريقة لكائن الفئة.

كيف يعمل:

  1. باستخدام مكتبة الفحص المضمّنة في مصمم الأدوات ، يتم الحصول على جميع أساليب الفصل وواجهة الاتصال - inspect.getmembers () . الفهرس الفريد للطريقة هو مزيج من اسمها ونوعها (staticmethod ، خاصية ، وما إلى ذلك).
  2. ومع inspect.signature () ، وسيطات الأسلوب.
  3. نحن ندور في كل أساليب الواجهة ، ونرى ما إذا كان هناك مثل هذه الطريقة (بواسطة فهرس فريد) في الفئة التي تنفذ الواجهة ، وما إذا كانت الوسائط الواردة هي نفسها ، وما إذا كانت معدّلات الوصول هي نفسها ، وما إذا كانت الطريقة تنفذ الأخطاء المعلنة في طريقة الواجهة.

شكرا لاهتمامكم بهذه المادة. رابط للمشروع على جيثب .

Source: https://habr.com/ru/post/ar443192/


All Articles