وحدات الماكرو لثعباني. تقرير ياندكس

كيف يمكنني توسيع بناء جملة Python وإضافة الميزات اللازمة إليه؟ حاولت الصيف الماضي في PyCon توضيح هذا الموضوع. من التقرير ، يمكنك معرفة كيفية ترتيب مكتبات الأنماط ، الماكروبي ، والأنماط وكيف تحقق مثل هذه النتائج المثيرة للاهتمام. في النهاية ، هناك مثال على إنشاء التعليمات البرمجية باستخدام وحدات الماكرو في HyLang ، وهي لغة تشبه Lisp تعمل فوق Python.


- اهلا ياجماعة. بادئ ذي بدء ، أود أن أشكر منظمي PyCon. أنا مطور في ياندكس. لن يكون التقرير عن العمل على الإطلاق ، ولكن عن الأشياء التجريبية. ربما سيقودون أحدكم إلى فكرة أنه في Python يمكنك القيام بأشياء رائعة لم تكن تعرفها من قبل ، ولم تفكر في هذا الاتجاه.

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

pytest


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



على سبيل المثال ، لديك مثل هذا الاختبار البسيط. إذا قمت بتشغيله بدون pytest ، فسوف يرمي AssertionError ببساطة.



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



ومع ذلك ، إذا تم تشغيل هذا تحت pytest ، فسيعرض معلومات تصحيح إضافية. كيف يفعل ذلك في الداخل؟



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

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



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

عندما يحول pytest هذا ويولد عامًا جديدًا ، تبدأ الشجرة في الظهور على هذا النحو.



هناك حوالي مائة سطر من التعليمات البرمجية التي أنشأتها لك pytest.



إذا قمت بتحويل شجرة AST هذه إلى Python ، فستبدو شيئًا مثل هذا. المناطق المميزة باللون الأحمر هنا هي المكان الذي يحسب فيه pytest الأجزاء اليمنى واليسرى من التعبير ، ويولد رسالة خطأ ، ويثير AssertionError إذا حدث خطأ ما في رسالة الخطأ هذه.

نمط مطابقة


ماذا يمكنك أن تفعل مع شيء من هذا القبيل؟ يمكنك تحويل أي رمز Python. وهناك مكتبة رائعة وجدتها عن طريق الصدفة على PyPI ، من المثير للاهتمام أن أحفر هناك. تفعل مطابقة النمط.



ربما هذا الرمز مألوف لشخص ما. يعتبر عاملي بشكل متكرر. دعونا نرى كيف يمكن تسجيله باستخدام مطابقة الأنماط.



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


الوظيفة السابقة كما لو كانت مكتوبة في ثلاثة أسطر.





وتضيف بقية الأسطر وظائف إضافية تسمح ، على سبيل المثال ، بقراءة عاملي من قائمة القيم أو تمريرها من خلال وظيفة عشوائية.

كيف تكتب التحويلات بنفسك؟ macropy!


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

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

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



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



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



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



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


هنا مثال آخر مألوف. تعتبر هذه الوظيفة عامليًا ، ويتم تمييز الرمز باللون الأحمر. ماذا سيحدث عندما يتم استدعاؤها؟



سيؤدي ذلك إلى حدوث خطأ في Python ، لأنه سيصل إلى حد المكدس وسيكون هناك مثل RecursionError القبيح.



كيف يمكن إصلاح ذلك؟ باستخدام macropy ، إصلاح المشكلة بسيط للغاية.



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



وستعود الوظيفة إلى نفسها نتيجة طبيعية تمامًا ، وتذهب بعيدًا إلى تحت الأرض.


كيف يفعل ذلك macropy؟



يستبدل جميع المكالمات إلى الوظيفة نفسها بكائن TailCall خاص ، والذي يتم استدعاؤه بعد ذلك في حلقة من قبل مصمم TCO.



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

يتضمن Macropy أيضًا أمثلة أخرى. آمل أن يذهب أولئك الذين لديهم فضول منك لرؤيتهم بأنفسهم. لنفترض أن هناك أشياء مفيدة لتصحيح الأخطاء.



سأخبرك عن شيء رائع آخر. أحد الأمثلة على ماكرو الاستعلام هذا. ماذا يفعل؟ بداخله ، تكتب رمز Python عادي ، والذي يمكنك استخدامه بعد ذلك كنتيجة منتظمة لتنفيذ هذا التعبير. ولكن في الداخل ، يحول macropy هذا الرمز ويحوله إلى رمز لغة استعلام Alchemy SQL.



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



هنا هو التعبير الأصلي. بعد توسيع الماكرو ، يستغرق الأمر شيئًا كهذا.



ربما يكون أحد الأشخاص مهتمًا بكتابة تعليمات برمجية أكثر تشابهًا مع Python ، وعدم إجبار مطوريهم على كتابة الاستعلامات على DSL SQL Alchemy.

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



الآن دعونا نرى كيفية إنشاء الماكرو الخاص بك. مع macropy ، الأمر بسيط للغاية.

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

هنا ، وظيفة replace_assert الداخلية هي المساعد. إنها تنحدر عودية في شجرة لك. داخل replace_assert ، يتم تمرير عنصر الشجرة الفرعية.



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







عند استخدامه ، تقوم بإرفاق مثل هذا الماكرو إلى كتلة من التعليمات البرمجية باستخدام مدير السياق ، وكل التعليمات البرمجية التي تدخل داخل مدير السياق تمر عبر هذا التحويل. يتبين أدناه أنه تمت إضافة رسالة الخطأ الخاصة بنا إلى AssertionError ، التي شكلناها من تعبير len ([1، 2، 3]).



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

HyLang


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

لحسن الحظ بالنسبة لنا ، هناك وظيفة Python الإضافية تسمى HyLang. هذا الشيء يذكرنا إلى حد ما بـ Clojure ، فقط Clojure يعمل فوق JVM ، و HyLang يعمل على قمة Python Virtual Machine. أي أنها توفر لك بنية جديدة لكتابة التعليمات البرمجية. ولكن في الوقت نفسه ، ستكون جميع التعليمات البرمجية التي تكتبها متوافقة تمامًا مع مكتبات Python الحالية ، ويمكن استخدامها من مكتبات Python.



يبدو شيء من هذا القبيل.



الجزء الموجود على اليسار مكتوب بلغة Python ، على اليمين - على HyLang. ومن الأسفل لكليهما رمز ثانوي ، وهو النتيجة. ربما لاحظت أنه هو نفسه تمامًا ، فقط يتغير بناء الجملة. HyLang s- التعبيرات التي لا يحبها الكثير. معارضو "الأقواس" لا يفهمون أن مثل هذا التركيب يعطي اللغة قوة هائلة لأنه يعطي التوحيد لبنى اللغة. ويسمح لك التوحيد باستخدام وحدات الماكرو لتنفيذ أي تصميم.

يتم تحقيق ذلك بسبب حقيقة أن العنصر الأول داخل كل تعبير هو دائمًا نوع من العمل. ثم تذهب حججه.

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



دعونا نرى كيف يعمل الماكرو البسيط على HyLang. لفعل نفس الشيء الذي فعلناه مع Assert باستخدام macropy ، ما عليك سوى هذا الرمز.

يتلقى الماكرو HyLang الإدخال ، وهو رمز. علاوة على ذلك ، يمكن لماكرو بسهولة استخدام أي جزء من هذا الرمز لإنشاء رمز جديد. الاختلاف الرئيسي بين وحدات الماكرو والوظائف: التعبيرات هي المدخلات وليست القيم. إذا نسمي الماكرو الخاص بنا على أنه (هو (= 1 2)) ، فسوف يتلقى تعبيرًا (= 1 2) بدلاً من خطأ.



لذا يمكننا إنشاء رسالة خطأ تفيد بحدوث خطأ ما.



ثم أعِد الرمز الجديد. بناء الجملة backtick و tilde يعني شيئًا مما يلي. يقول الاقتباس الخلفي: خذ هذا التعبير كما هو وأعده كما هو. وتقول التلدة: استبدل قيمة المتغير هنا.



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

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

هذا كل شيء بالنسبة لي. يمكنك رؤية الروابط:

  • الأنماط ،
  • MacroPy ،
  • HyLang ،
  • كتاب OnLisp - لدراسة متقدمة لقدرات وحدات الماكرو. هذا لمن يهتم بشكل خاص. صحيح أن الكتاب لا يعتمد بالكامل على Python ، بل على Common Lisp. ولكن بالنسبة لدراسة أعمق ، سيكون هذا مثيرًا للاهتمام.

All Articles