بايثون / أنظمة قابلة للصيانة / الدرس 13
البروتوكولات والطرائق الخاصة والعقود النوعية
صمم الواجهات بحسب السلوك المطلوب، وميز الوعود الساكنة عن التحقق وقت التشغيل، ونفذ الطرائق الخاصة بمعان متسقة.
في هذه الصفحة
أهداف الدرس
- السلوك يحدد الواجهة المفيدة
- الحفاظ على قابلية الاستبدال
السلوك يحدد الواجهة المفيدة
تصف بروتوكولات بايثون العمليات التي يدعمها الكائن. تحتاج for إلى التكرار وتحتاج len إلى الطول. تتعاون الكائنات المعرفة من المستخدم مع تراكيب اللغة دون وراثة أصل تطبيق شامل. يكتشف المفسر طرائق مثل __len__ و__iter__ وفق نموذج البيانات. نفذ مجموعة صغيرة مترابطة، لا كل طريقة تبدو مفيدة. يجب أن يكون الطول عددا صحيحا غير سالب، وأن يتفق مع التكرار عند تمثيل مجموعة محدودة. لا يكفي تشابه الاسم إذا خالف السلوك افتراضات المستدعي.
العلامات توضح ولا تفرض
توثق العلامات النوعية البنى المتوقعة وتساعد التحليل الساكن والمحرر والمراجعة، ولا يفرضها التشغيل عموما. قد تستقبل دالة معلّمة بعدد صحيح نصا حتى تفشل عملية أو يرفضه تحقق صريح. تكمل الأنواع التحقق عند الحدود ولا تستبدله. استخدم list[str] وint | None المتاحتين في 3.11. نحافظ على هذا الحد الأدنى؛ أما صياغات تعريف المعاملات النوعية الأحدث فتحتاج إلى تحديد إصدار صريح. ولا يفيد العقد المفحوص دون تشغيل أداة فحص مناسبة فعليا.
تنفيذ مجموعة محدودة
يحفظ المثال المدخل في tuple، فتحصل كل دورة لاحقة على مكرر مستقل لمحتوى خارجي ثابت. يختلف إرجاع iter(self._items) عن حفظ مكرر واحد دائم، إذ يستنفد الأخير الكائن بعد المرور الأول. تعيد __len__ حجم النسخة ويستهلك sum بروتوكول التكرار. لا يدعي الكائن دعم تعديل القوائم أو فهرستها؛ فهو يكشف المطلوب فقط. تقلل الواجهة الضيقة الاعتماد على خصائص عرضية، وتجعل ملكية النسخة المرور المتكرر متوقعا.
class Scores:
def __init__(self, values):
self._items = tuple(values)
def __len__(self):
return len(self._items)
def __iter__(self):
return iter(self._items)
scores = Scores([4, 6, 8])
print(len(scores), sum(scores))
print(list(scores))
3 18
[4, 6, 8]
يتفق الطول والتكرار، ويحصل المستهلك الثاني على مكرر جديد بدلا من مكرر محفوظ مستنفد.
الأنواع البنيوية باستخدام Protocol
تصف typing.Protocol الأعضاء المطلوبة للفحص البنيوي، ويمكن لصنف استيفاؤها دون وراثة البروتوكول صراحة. يفيد ذلك في حقن الاعتماديات؛ فقد يحتاج المنسق إلى read لا إلى تنفيذ تخزين معين. يقدم المثال مصدرا صغيرا ويستهلك واجهته المعلنة. ينفذ دون أداة خارجية، ولذلك يوضح السلوك ولا يثبت إجراء تحليل ساكن. تقدم runtime_checkable فحوص أعضاء محدودة، لا فحصا كاملا للتواقيع أو المعنى؛ فلا تعاملها كتحقق من بيانات غير موثوقة أو صحة السلوك.
from typing import Protocol
class TextSource(Protocol):
def read(self) -> str:
...
class MemorySource:
def read(self) -> str:
return "ready"
def headline(source: TextSource) -> str:
return source.read().upper()
print(headline(MemorySource()))
READY
توفر MemorySource سلوك read دون وراثة TextSource. يعلن جسم البروتوكول العقد، ويقدم المصدر الملموس التنفيذ الفعلي.
الحفاظ على قابلية الاستبدال
يقبل البديل المدخلات الموعودة ويعيد نتائج متوافقة ويحفظ الآثار وحالات الفشل الموثقة. لا يثبت التوافق البنيوي الساكن هذه الخصائص المعنوية. استخدم اختبارات عقد مشتركة للتنفيذات المتعددة. فضل أصغر بروتوكول مفيد حتى يستطيع بديل اختباري بسيط تنفيذه. اتبع قواعد النموذج في المساواة والتجزئة؛ تحتاج الكائنات المتساوية القابلة للتجزئة إلى التجزئة نفسها، وقد يفسد المفتاح القيمي المتغير افتراضات البحث. وعند عدم دعم نوع معامل، قد تحتاج الطريقة الخاصة إلى NotImplemented لا نتيجة مختلقة.
الأخطاء الشائعة وإصلاحها
وصول مدخل غير صالح رغم العلامة النوعية
- لماذا يحدث؟
- فُهمت العلامات بوصفها حراسا تلقائيين.
- كيف تصلحه؟
- تحقق من الخارج صراحة وشغّل الفحص الساكن منفصلا.
- كيف تتجنب تكراره؟
- وثق موضع فرض كل ضمان.
عمل المجموعة في المرور الأول فقط
- لماذا يحدث؟
- تعيد __iter__ مكررا محفوظا واحدا.
- كيف تصلحه؟
- أعد مكررا جديدا على البيانات المحفوظة.
- كيف تتجنب تكراره؟
- اختبر مرورين مستقلين على كائن المجموعة.
تعذر العثور على مفتاح متغير
- لماذا يحدث؟
- تغيرت حالته المؤثرة في التجزئة بعد الإدراج.
- كيف تصلحه؟
- استخدم مفاتيح ثابتة بمساواة وتجزئة متسقتين.
- كيف تتجنب تكراره؟
- تجنب تجزئة مبنية على قيم كائن مجال متغير.
اختبر فهمك
1. صمم أصغر واجهة لدالة ترسل إشعارا نصيا فقط.
اعرض الحل المشروح
عرف Protocol بطريقة send(message: str) -> None. يقبلها المستهلك وينفذها موائم حقيقي وبديل يسجل الرسائل. أضف اختبارات للمعنى والفشل؛ اشتراط خدمة بريد كاملة يضيف اعتماديات غير مستخدمة.
2. لماذا لا يثبت isinstance مع بروتوكول وقت التشغيل أمان الموائم؟
اعرض الحل المشروح
الفحص محدود ولا يثبت كل أنواع المعاملات أو معنى النتائج أو التفويض أو سياسة الاستثناء. قد يوجد العضو ويعمل خطأ. تحقق من حدود الثقة منفصلة واستخدم اختبارات سلوكية لوعود الموائم.
أهم ما تعلمته
- تصف البروتوكولات سلوك المستهلك المطلوب.
- تدعم العلامات الأدوات ولا تستبدل التحقق.
- تحتاج العقود المعنوية اختبارات سلوكية.