مرحبا بالجميع!
لدينا بالفعل مقال واحد حول تطوير الكتابة في Ostrovok.ru . وهذا ما يفسر سبب تحولنا من pyContracts إلى typeguard ، ولماذا نتحول إلى typeguard وما ينتهي بنا إليه الأمر. واليوم سوف أخبركم أكثر عن كيفية حدوث هذا الانتقال.

يبدو إعلان الوظيفة مع pyContracts بشكل عام كما يلي:
from contracts import new_contract import datetime @new_contract def User (x): from models import User return isinstance(x, User) @new_contract def dt_datetime (x): return isinstance(x, datetime.datetime) @contract def func(user_list, amount, dt=None): """ :type user_list: list(User) :type amount: int|float :type dt: dt_datetime|None :rtype: bool """ …
هذا مثال تجريدي ، لأنني لم أجد في مشروعنا تعريفًا لدالة قصيرة وذات مغزى من حيث عدد الحالات لفحص النوع. عادة ، يتم تخزين تعريفات pyContracts في الملفات التي لا تحتوي على أي منطق آخر. لاحظ أن المستخدم هنا هو فئة مستخدم معين ، ولا يتم استيراده مباشرةً.
وهذه هي النتيجة المرجوة مع typeguard:
from typechecked import typechecked from typing import List, Optional, Union from models import User import datetime @typechecked def func (user_list: List[User], amount: Union[int, float], dt: Optional[datetime.datetime]=None) -> bool: ...
بشكل عام ، هناك العديد من الوظائف والطرق مع التحقق من النوع في المشروع بحيث إذا قمت بتجميعها في مكدس ، يمكنك الوصول إلى القمر. حتى الترجمة يدويا لهم من pyContracts إلى typeguard هو ببساطة غير ممكن (حاولت!). لذلك قررت أن أكتب السيناريو.
ينقسم البرنامج النصي إلى جزأين: أحدهما يخبئ واردات العقود الجديدة ، والثاني يتعامل مع إعادة هيكلة الكود.
أريد أن أشير إلى أنه لا أحد أو السيناريو الآخر يدعي أنه عالمي. لم نهدف إلى كتابة أداة لحل جميع الحالات المطلوبة. لذلك ، غالبًا ما أغفلت المعالجة التلقائية لبعض الحالات الخاصة ، إذا كان من النادر العثور عليها في المشروع ، فمن الأسرع إصلاحها يدويًا. على سبيل المثال ، قام البرنامج النصي لإنشاء عقود تعيينات وواردات بتجميع 90٪ من القيم ، أما الـ 10٪ الباقية فهي خرائط صياغة يدوية.
منطق البرنامج النصي لإنشاء التعيين:
الخطوة 1. انتقل من خلال جميع ملفات المشروع ، وقراءتها. لكل ملف:
- إذا كانت السلسلة الفرعية "new_contract" غير موجودة ، فتخط هذا الملف ،
- إذا كان هناك ، فاقسم الملف بسطر "@ new_contract". لكل عنصر:
- تحليل التعريف والاستيراد ،
- إذا نجحت ، فاكتب إلى ملف النجاح ،
- إذا لم يكن كذلك ، فاكتب إلى ملف الخطأ.
الخطوة 2. معالجة الأخطاء يدويا
والآن بعد أن أصبح لدينا أسماء جميع الأنواع التي تستخدمها pyContracts (تم تعريفها باستخدام مُصمم new_contract decorator) ، ولدينا جميع عمليات الاستيراد الضرورية ، يمكننا كتابة التعليمات البرمجية لإعادة التجهيز. بينما كنت أترجم من pyContracts إلى typeguard يدويًا ، أدركت ما أحتاجه من البرنامج النصي:
- هذا أمر يأخذ اسم وحدة نمطية كوسيطة (يمكن استخدام العديد منها) ، حيث يجب استبدال بناء جملة التعليقات التوضيحية للوظائف.
- انتقل من خلال جميع ملفات الوحدة النمطية ، وقراءتها. لكل ملف:
- إذا لم تكن هناك سلسلة فرعية "contract" ، فتخط هذا الملف ؛
- إذا كان الأمر كذلك ، فقم بتحويل الكود إلى ast (شجرة بناء الجملة) ؛
- العثور على جميع الوظائف التي هي تحت الديكور العقد لكل:
- الحصول على dockstring ، تحليل ، ثم حذف ،
- إنشاء قاموس للنموذج {arg_name: arg_type} ، استخدمه لاستبدال التعليق التوضيحي للوظيفة ،
- تذكر واردات جديدة ،
- كتابة الشجرة المعدلة إلى ملف من خلال astunparse ؛
- إضافة واردات جديدة إلى أعلى الملف ؛
- استبدل السطور "contract" بـ "typechecked" لأنها أسهل من خلال ast.
حل السؤال "هل تم استيراد هذا الاسم بالفعل في هذا الملف؟" لم أكن أنوي من البداية: مع هذه المشكلة ، سنتعامل مع مجموعة إضافية من مكتبة الجزر.
ولكن بعد تشغيل الإصدار الأول من البرنامج النصي ، نشأت أسئلة لا يزال يتعين حلها. اتضح أن 1) ast ليس كلي القدرة ، 2) astunparse هو أكثر قدرة كليًا مما نود. وقد تجلى ذلك في ما يلي:
- في لحظة الانتقال إلى شجرة بناء الجملة ، تختفي جميع التعليقات ذات سطر واحد من الكود ؛
- خطوط فارغة تختفي أيضا ؛
- ast لا يميز بين وظائف وطرق الصف ، كان علينا أن نضيف المنطق ؛
- على العكس من ذلك ، عند الانتقال من شجرة إلى رمز ، تتم كتابة التعليقات متعددة الأسطر في علامات اقتباس ثلاثية في تعليقات مفردة وتحتل سطرًا واحدًا ، ويتم استبدال فواصل الأسطر الجديدة بـ \ n؛
- تظهر الأقواس غير الضرورية ، على سبيل المثال إذا أصبحت A و B و C أو D إذا ((A و B و C) أو D).
لا تزال الشفرة التي تم تمريرها عبر ast و astunparse تعمل ، لكن قابلية قراءتها منخفضة.
أخطر عيوب ما سبق هو اختفاء التعليقات ذات سطر واحد (في حالات أخرى ، لا نخسر شيئًا ، ولكن الربح فقط - بين قوسين ، على سبيل المثال). مكتبة horast على أساس ast ، astunparse ، و الرمز المميز وعود لمعرفة هذا. الوعود والفعل.
الآن الخطوط الفارغة. كان هناك حلان ممكنان:
- يعرف الرمز المميز كيفية تحديد "جزء الكلام" من الثعبان ، ويستفيد منه الرعب عندما يحصل على الرموز المميزة لنوع التعليق. لكن الرمز المميز له أيضًا رموز مثل NewLine و NL. لذا ، فأنت بحاجة إلى معرفة كيفية استعادة horast للتعليقات ، ونسخها ، واستبدال نوع الرمز المميز.
- اقترح أنيا ، خبرة في تطوير شهرين - نظرًا لأن horast يمكنه استعادة التعليقات ، فإننا نستبدل أولاً جميع الخطوط الفارغة بتعليق معين ، ثم نتخطى horast ونستبدل تعليقنا بخط فارغ.
- ابتكر يوجين ، خبرة في تطوير 8 سنوات
سأقول أقل قليلاً عن علامات الاقتباس الثلاثية في التعليقات ، وكان من السهل جدًا وضع أقواس إضافية ، خاصة وأن بعضها قد تمت إزالته عن طريق التنسيق التلقائي.
في horast نستخدم وظيفتين: تحليل و unparse ، لكن كلاهما ليس مثاليًا - يحتوي parse على أخطاء داخلية غريبة ، وفي حالات نادرة لا يمكن تحليل الكود المصدر ، ولا يمكن unparse كتابة شيء له نوع كتابة (مثل هذا النوع الذي اتضح إذا قمت بكتابة (any_other_type)).
قررت عدم التعامل مع التحليل ، لأن منطق العمل مربك إلى حد ما ، والاستثناءات نادرة - مبدأ اللامبدأية يعمل هنا.
ولكن unparse يعمل بشكل واضح جدا وأنيق جدا. تقوم الدالة unparse بإنشاء مثيل لفئة Unparser ، والتي تقوم في init بمعالجة الشجرة ثم كتابتها في ملف. يتم توريث Horast.Unparser على التوالي من العديد من Unparsers ، حيث تكون الطبقة الأساسية هي astunparse.Unparser. تعمل كل الطبقات المنحدرة ببساطة على توسيع وظائف الفئة الأساسية ، ولكن يظل منطق العمل كما هو ، لذلك خذ بعين الاعتبار astunparse.Unparser. لديها خمس طرق مهمة:
- الكتابة - فقط يكتب شيئا إلى ملف.
- fill - يستخدم الكتابة بناءً على عدد المسافات البادئة (يتم تخزين عدد المسافات البادئة كحقل فئة).
- أدخل - يزيد المسافة البادئة.
- إجازة - يقلل من المسافة البادئة.
- إرسال - يحدد نوع عقدة الشجرة (قل T) ، يستدعي الأسلوب المقابل باسم نوع العقدة ، ولكن باستخدام الشرطة السفلية (على سبيل المثال _T). هذه هي طريقة الفوقية.
جميع الطرق الأخرى هي طرق النموذج _T ، على سبيل المثال ، _Module أو _Str. في كل طريقة ، يمكن أن: 1) الإرسال بشكل متكرر للعقد الشجرة الفرعية ، أو 2) استخدام الكتابة لكتابة محتويات العقدة أو إضافة أحرف وكلمات رئيسية بحيث تكون النتيجة تعبير صحيح في بيثون.
على سبيل المثال ، صادفنا عقدة من نوع arg ، حيث يخزن ast اسم الوسيطة وعقدة التعليق التوضيحي. بعد ذلك ، سيتم استدعاء الإرسال بالطريقة _arg ، والتي ستقوم أولاً بكتابة اسم الوسيطة ، ثم اكتب النقطتين وتشغيل الإرسال لعقدة التعليق التوضيحي ، حيث سيتم تحليل الشجرة الفرعية للتعليق ، وسيظل الإرسال والكتابة مستمرين لهذه الشجرة الفرعية.
دعنا نعود إلى مشكلتنا المتمثلة في استحالة معالجة نوع النوع. الآن بعد أن أدركت كيف تعمل unparse ، أصبح إنشاء نوعك أمرًا سهلاً. لنقم بإنشاء نوع:
class NewType(object): def __init__ (self, t): self.s = ts
إنه يخزن سلسلة في حد ذاته ، وليس هكذا: نحتاج إلى كتابة وسيطات دالة ، ونحصل على أنواع الوسائط في شكل سلاسل من الإرساء. لذلك ، دعونا نستبدل التعليقات التوضيحية للوسائط ليس بالأنواع التي نطلبها ، ولكن بكائن NewType الذي يخزن اسم النوع المطلوب فقط بداخله.
للقيام بذلك ، قم بتوسيع horast.Unparser - اكتب UnparserWithType الخاص بك ، ورثًا من horast.Unparser ، وأضف معالجة النوع الجديد لدينا.
class UnparserWithType(horast.Unparser): def _NewType (self, t): self.write(ts)
يجمع هذا مع روح المكتبة. مصنوعة أسماء المتغيرات في أسلوب ast ، وهذا هو السبب أنها تتكون من حرف واحد ، وليس لأنني لا أستطيع التفكير في الأسماء. أعتقد أن هذا t قصير بالنسبة للشجرة و s للخيط. بالمناسبة ، NewType ليس سلسلة. إذا أردنا أن يتم تفسيرها كنوع سلسلة ، فسنضطر إلى كتابة علامات الاقتباس قبل وبعد مكالمة الكتابة.
و الان السحر قرد التصحيح: استبدال horast.Unparser مع UnparserWithType لدينا.
كيف تعمل الآن: لدينا شجرة بناء جملة ، لديها بعض الوظائف ، والوظائف لها حجج ، والحجج لها تعليقات توضيحية ، والإبرة مخفية في كتابة تعليق توضيحي ، وموت Koshcheev مخفيًا فيها. في السابق ، لم تكن هناك أي تعليقات توضيحية على الإطلاق ، وقمنا بإنشائها ، وأي عقدة من هذا القبيل هي مثيل لـ NewType. نحن ندعو وظيفة unparse لشجرتنا ، ولكل عقدة يطلق عليها الإرسال ، الذي يصنف تلك العقدة ويدعو الوظيفة المقابلة لها. بمجرد أن تتلقى وظيفة الإرسال عقدة الوسيطة ، فإنها تكتب اسم الوسيطة ، ثم تتطلع إلى معرفة ما إذا كان هناك تعليق توضيحي (كان يستخدم بلا ، لكننا وضعنا NewType هناك) ، إذا كان الأمر كذلك ، فإنه يكتب نقطتين ويدعو الإرسال للتعليق التوضيحي ، والذي يستدعي _NewType ، فقط يكتب السلسلة التي يخزنها - وهذا هو اسم النوع. نتيجة لذلك ، حصلنا على الوسيطة المكتوبة: type.
في الواقع ، هذا ليس قانونيا تماما. من وجهة نظر المترجم ، قمنا بتدوين التعليقات التوضيحية للوسيطات مع بعض الكلمات التي لم يتم تعريفها في أي مكان ، لذلك عندما ينتهي unparse من عمله ، نحصل على الكود الخطأ: نحن بحاجة إلى عمليات الاستيراد. أنا ببساطة أقوم بتكوين سطر من التنسيق الصحيح وإضافته إلى بداية الملف ، ثم قم بإلحاق النتيجة بفصل ، على الرغم من أنه يمكنني إضافة عمليات استيراد كعقد إلى شجرة بناء الجملة ، حيث أن ast تدعم عقدتي Import و ImportFrom.
حل مشكلة علامة اقتباس ثلاثية ليس أكثر صعوبة من إضافة نوع جديد. سنقوم بإنشاء فئة StrType وطريقة _StrType. لا تختلف الطريقة عن طريقة _NewType المستخدمة في إضافة تعليقات توضيحية إلى الأنواع ، ولكن تعريف الفئة قد تغير: لن نقوم بتخزين السلسلة نفسها فقط ، ولكن أيضًا مستوى علامة التبويب التي يجب أن تكتب بها. يتم تعريف عدد المسافة البادئة على النحو التالي: إذا تمت مصادفة هذا السطر في دالة ، فحينئذٍ واحد ، إذا كان في طريقة ما ، ثم اثنين ، ولا توجد حالات عندما يتم تعريف الوظيفة في نص وظيفة أخرى ويتم تزيينها في نفس الوقت ، في مشروعنا.
class StrType(object): def __init__ (self, s, indent): self.s = s self.indent = indent def __repr__ (self): return '"""\n' + self.s + '\n' + ' ' * 4 * self.indent + '"""\n'
في repr نحدد كيف ينبغي أن يبدو خطنا. أعتقد أن هذا أبعد ما يكون عن الحل الوحيد ، لكنه يعمل. يمكن للمرء تجربة مع astunparse.fill و astunparse.Unparser.indent ، بعد ذلك سيكون أكثر عالمية ، ولكن هذه الفكرة جاءت إلى ذهني بالفعل في وقت كتابة هذا المقال.
هذا حل الصعوبات تنتهي. بعد تشغيل البرنامج النصي الخاص بي ، تنشأ أحيانًا مشكلة الاستيراد الدوري ، ولكن هذه مسألة تتعلق بالبنية. لم أجد حلًا جاهزًا لجهة خارجية ، ويبدو أن التعامل مع مثل هذه الحالات في إطار النص البرمجي يمثل تعقيدًا خطيرًا في المهمة. ربما ، بمساعدة ast ، من الممكن الكشف عن الواردات الدورية وحلها ، ولكن يجب التفكير في هذه الفكرة بشكل منفصل. بشكل عام ، سمح لي عدد قليل من هذه الحوادث في مشروعنا تمامًا بعدم معالجتها تلقائيًا.
صعوبة أخرى واجهتها هي عدم وجود معالجة تعبيرية في ast من astro الاستيراد ، حيث يعرف القارئ المتأني بالفعل أن التصحيح القرد هو العلاج لجميع الأمراض. فليكن هذا واجبه المنزلي بالنسبة له ، لكنني قررت القيام بذلك: ما عليك سوى إضافة هذه الواردات إلى ملف التعيين ، لأن هذا البناء يستخدم عادة لتجاوز تعارض الأسماء ، ولدينا القليل منها.
على الرغم من العيوب التي تم العثور عليها ، فإن النص يفعل ما كان الغرض منه القيام به. ما هي النتيجة:
- تم تقليل وقت إطلاق المشروع من 10 إلى 3 ثوان ؛
- انخفض عدد الملفات بسبب إزالة تعريفات العقد الجديد. تم تخفيض الملفات نفسها: لم أكن أقيسها ، ولكن في المتوسط بلغ مجموع الخطوط المضافة n و 2n المحذوفة ؛
- بدأت IDE الذكية في صنع تلميحات مختلفة ، لأنها الآن ليست تعليقات ، بل واردات صادقة ؛
- تحسنت قابلية القراءة ؛
- ظهرت في مكان ما بين قوسين.
شكرا لك
روابط مفيدة:
- است
- Horast
- جميع أنواع العقد ast وما يتم تخزينها فيها
- يظهر جميل شجرة بناء الجملة
- Isort