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

القضية 3: الحالة العالمية قابلة للتغيير
ألقِ نظرة على فئة أخرى من الأخطاء الشائعة.
def myview(request): SomeClass.id = request.GET.get("id")
نحن هنا في وظيفة العرض التقديمي ونعلق السمة على فئة معينة بناءً على البيانات الواردة من الطلب. ربما تكون قد فهمت بالفعل جوهر المشكلة. والحقيقة هي أن الطبقات هي المفردات العالمية. وهنا نضع الدولة ، بناءً على الطلب ، في كائن طويل العمر. في عملية خادم الويب التي تستغرق وقتًا طويلاً لإكمالها ، يمكن أن يؤدي ذلك إلى تلوث كل طلب مستقبلي مقدم كجزء من هذه العملية.
نفس الشيء يمكن أن يحدث بسهولة في الاختبارات. على وجه الخصوص ، في الحالات التي يحاول فيها المبرمجون استخدام
تصحيحات القردة وعدم استخدام
مدير السياق ، مثل
mock.patch
. قد لا يؤدي هذا إلى تلوث الطلبات ، بل يؤدي إلى تلوث جميع الاختبارات التي سيتم إجراؤها في نفس العملية. هذا سبب وجيه للسلوك غير الموثوق به لنظام الاختبار الخاص بنا. هذه مشكلة كبيرة ، ومن الصعب للغاية منع ذلك. نتيجة لذلك ، تخلينا عن نظام الاختبار الموحد وانتقلنا إلى مخطط عزل الاختبار ، والذي يمكن وصفه بأنه "اختبار واحد لكل عملية".
في الواقع ، هذه هي مشكلتنا الثالثة. الدولة العالمية القابلة للتغيير هي ظاهرة لا تنفرد بها بيثون. يمكنك العثور عليه في أي مكان. نحن نتحدث عن الطبقات أو الوحدات النمطية أو القوائم أو القواميس المرتبطة بالوحدات النمطية أو الطبقات ، حول الكائنات المفردة التي تم إنشاؤها على مستوى الوحدة النمطية. العمل في مثل هذه البيئة يتطلب الانضباط. من أجل منع تلوث الحالة العالمية أثناء تشغيل البرنامج ، فأنت بحاجة إلى معرفة جيدة ببيثون.
تقديم وحدات صارمة
قد يكون أحد الأسباب الجذرية لمشكلاتنا هو استخدام Python لحل هذه المشكلات التي لم يتم تصميمها لهذه اللغة. في الفرق الصغيرة والمشاريع الصغيرة ، إذا اتبعت القواعد عند استخدام Python ، فإن هذه اللغة تعمل بشكل جيد. ويجب أن نذهب إلى لغة أكثر صرامة.
لكن قاعدة الشفرات لدينا تجاوزت بالفعل الحجم الذي يسمح لنا بالتحدث على الأقل عن كيفية إعادة كتابتها بلغة أخرى. والأهم من ذلك أنه على الرغم من كل المشكلات التي نواجهها ، فإن لبايثون علاقة كبيرة بها. انه يعطينا أكثر جيدة من سيئة. المطورين لدينا حقا مثل هذه اللغة. نتيجة لذلك ، يعتمد الأمر فقط على كيفية جعل Python تعمل على نطاقنا ، وكيفية التأكد من أنه يمكننا مواصلة العمل في المشروع أثناء تطوره.
أدى إيجاد حلول لمشاكلنا إلى فكرة واحدة. وهو يتكون في استخدام وحدات صارمة.
الوحدات النمطية الصارمة هي وحدات بيثون من نوع جديد ، يوجد في البداية بناء
__strict__ = True
. يتم تنفيذها باستخدام العديد من آليات القابلية للتوسعة ذات المستوى المنخفض التي تمتلكها بيثون بالفعل. يقوم
مُحمل وحدة نمطية خاص بتوزيع الرمز باستخدام الوحدة النمطية
ast
، ويقوم بتفسير مجردة من الكود المحمّل لتحليله ، ويطبق تحويلات متنوعة على AST ، ثم يقوم بجمع AST مرة أخرى إلى Python bytecode باستخدام وظيفة
compile
المدمجة.
أي آثار جانبية الاستيراد
تفرض الوحدات الصارمة بعض القيود على ما يمكن أن يحدث على مستوى الوحدة النمطية. لذلك ، يجب أن تكون جميع التعليمات البرمجية على مستوى الوحدة النمطية (بما في ذلك الديكورات والوظائف / المُهيئات المدعومة على مستوى الوحدة النمطية) نظيفة ، أي الكود الخالي من الآثار الجانبية ولا يستخدم آليات الإدخال / الإخراج. يتم التحقق من هذه الشروط بواسطة مترجم مجردة باستخدام وسائل تحليل رمز ثابت في وقت الترجمة.
هذا يعني أن استخدام وحدات صارمة لا يسبب آثارًا جانبية عند استيرادها. الرمز الذي تم تنفيذه أثناء استيراد الوحدة النمطية لم يعد يسبب مشاكل غير متوقعة. نظرًا لحقيقة أننا نتحقق من ذلك على مستوى التفسير التجريدي ، باستخدام الأدوات التي تفهم مجموعة فرعية كبيرة من بيثون ، فإننا نلغي الحاجة إلى الحد بشكل مفرط من تعبير بيثون. يمكن استخدام العديد من أنواع الأكواد الديناميكية ، الخالية من الآثار الجانبية ، بأمان على مستوى الوحدة. يتضمن ذلك العديد من الديكورات وتعريف ثوابت مستوى الوحدة النمطية باستخدام القوائم أو مولدات القاموس.
لنجعل الأمر أكثر وضوحًا ، ففكر في مثال. هنا هو وحدة صارمة مكتوبة بشكل صحيح:
"""Module docstring.""" __strict__ = True from utils import log_to_network MY_LIST = [1, 2, 3] MY_DICT = {x: x+1 for x in MY_LIST} def log_calls(func): def _wrapped(*args, **kwargs): log_to_network(f"{func.__name__} called!") return func(*args, **kwargs) return _wrapped @log_calls def hello_world(): log_to_network("Hello World!")
في هذه الوحدة ، يمكننا استخدام تصميمات Python المعتادة ، بما في ذلك الشفرة الديناميكية ، والتي تُستخدم لإنشاء القاموس ، وواحد يصف الديكور على مستوى الوحدة. في الوقت نفسه ، يعد الوصول إلى موارد الشبكة في وظائف
_wrapped
أو
hello_world
_wrapped
طبيعيًا تمامًا. والحقيقة هي أنها لا تسمى على مستوى الوحدة النمطية.
ولكن إذا قمنا بنقل استدعاء
log_to_network
إلى وظيفة
log_calls
الخارجية ، أو إذا حاولنا استخدام أداة الديكور التي تسببت في آثار جانبية (مثل
@route
من المثال السابق) ، أو إذا استخدمنا
hello_world()
المكالمة على مستوى الوحدة النمطية ،
@route
ذلك صارمًا وحدة.
كيفية معرفة أنه ليس من الآمن استدعاء
log_to_network
أو وظائف
log_to_network
على مستوى الوحدة النمطية؟ ننطلق من افتراض أن كل ما يتم استيراده من الوحدات النمطية التي ليست وحدات نمطية صارمة غير آمن ، باستثناء بعض الوظائف من المكتبة القياسية المعروفة بأنها آمنة. إذا كانت الوحدة النمطية
utils
وحدة نمطية صارمة ، فيمكننا الاعتماد على تحليل وحدتنا لإعلامنا إذا كانت وظيفة
log_to_network
.
بالإضافة إلى تحسين موثوقية الكود ، فإن الواردات التي ليس لها آثار جانبية تلغي حاجزًا خطيرًا لتأمين تنزيلات الكود التزايدية. هذا يفتح إمكانيات أخرى لاستكشاف طرق لتسريع فرق الاستيراد. إذا كان رمز مستوى الوحدة النمطية خاليًا من الآثار الجانبية ، فهذا يعني أنه يمكننا تنفيذ تعليمات الوحدة الفردية بأمان في وضع "كسول" ، عند الطلب ، عند الوصول إلى سمات الوحدة النمطية. هذا أفضل بكثير من اتباع خوارزمية "الجشع" ، حيث يتم تنفيذ كل رمز الوحدة النمطية مسبقًا. ومع الأخذ في الاعتبار حقيقة أن شكل جميع الفئات في الوحدة النمطية الصارمة معروف تمامًا في وقت الترجمة ، فقد نحاول في المستقبل تنظيم تخزين دائم لبيانات تعريف الوحدة النمطية (الفئات ، الوظائف ، الثوابت) الناتجة عن تنفيذ التعليمات البرمجية. سيسمح لنا ذلك بتنظيم عملية الاستيراد السريع للوحدات النمطية التي لم تتغير ، والتي لا تتطلب التنفيذ المتكرر للرمز الثانوي لمستوى الوحدة النمطية.
سمة المناعة و __slots__
الوحدات النمطية والفئات الصارمة المعلنة فيها غير قابلة للتغيير بعد إنشائها. يتم جعل الوحدات غير قابلة للتغيير بمساعدة التحويل الداخلي لجسم الوحدة إلى وظيفة يتم فيها تنظيم الوصول إلى جميع المتغيرات العامة من خلال متغيرات الإغلاق. قللت هذه التغييرات بشكل كبير من احتمالات التغيير العشوائي في الحالة العالمية ، على الرغم من أنه لا يزال من الممكن حل الحالة العالمية القابلة للتغيير إذا تقرر استخدامها من خلال حاويات قابلة للتغيير على مستوى الوحدة النمطية.
يجب أيضًا إعلان أعضاء الفئات المعلنة في وحدات صارمة في
__init__
. تتم كتابتها تلقائيًا إلى سمة
__slots__
أثناء تحويل AST الذي يتم تنفيذه بواسطة أداة تحميل الوحدة النمطية. نتيجةً لذلك ، لم يعد بإمكانك فيما بعد إرفاق سمات إضافية بمثيل الفئة. هنا فئة مماثلة:
class Person: def __init__(self, name, age): self.name = name self.age = age
أثناء تحويل AST ، الذي يتم تنفيذه أثناء معالجة الوحدات النمطية الصارمة ، سيتم الكشف عن عمليات تخصيص القيم للسمات والسمات التي تم إجراؤها في
__init__
وسيتم
__slots__ = ('name', 'age')
سمة النموذج
__slots__ = ('name', 'age')
بالصف. سيمنع هذا أي سمات أخرى من إضافتها إلى مثيل الفئة. (إذا تم استخدام التعليقات التوضيحية للكتابة ، فإننا نأخذ بالحسبان معلومات حول الأنواع المتاحة على مستوى الفصل ، مثل
name: str
،
name: str
أيضًا إلى قائمة فتحات).
القيود الموضحة لا تجعل الكود أكثر موثوقية فقط. أنها تساعد على تسريع تنفيذ التعليمات البرمجية. يزيد التحويل التلقائي للفئات مع إضافة سمة
__slots__
من كفاءة استخدام الذاكرة عند العمل مع هذه الفئات. يتيح لك ذلك التخلص من عمليات البحث في القاموس عند العمل مع المثيلات الفردية للفئات ، مما يزيد من سرعة الوصول إلى السمات. بالإضافة إلى ذلك ، يمكننا الاستمرار في تحسين هذه الأنماط أثناء تنفيذ كود Python ، والذي سيتيح لنا تحسين نظامنا.
النتائج
وحدات صارمة لا تزال التكنولوجيا التجريبية. لدينا نموذج أولي عملي ، نحن في المراحل الأولى من نشر هذه القدرات في الإنتاج. نأمل أنه بعد اكتساب الخبرة الكافية في استخدام وحدات صارمة ، سنتمكن من التحدث عنها.
أعزائي القراء! هل تعتقد أن الميزات التي توفرها وحدات صارمة تأتي في متناول اليد في مشروع بيثون الخاص بك؟
