REPL عديم الجدوى. تقرير ياندكس

REPL (حلقة قراءة - قراءة - طباعة) غير مجدية في Python ، حتى لو كانت IPython سحرية. اليوم سأقدم أحد الحلول الممكنة لهذه المشكلة. أولاً وقبل كل شيء ، سيكون التقرير وإضافتي TheREPL مفيدًا لأولئك الذين يرغبون في تطوير أسرع وأكثر كفاءة ، وكذلك أولئك الذين يكتبون أنظمة ذات حالة.


- اسمي ألكسندر ، أعمل كمبرمج في ياندكس. نحن نكتب في فريقي في Python ، ولم ننتقل بعد إلى Go. ولكن في وقت فراغي ، أنا أيضًا ، الغريب ، أبرمجها وأقوم بها بلغة ديناميكية للغاية - Common Lisp. ربما تكون أكثر ديناميكية من Python. تكمن خصوصيتها في حقيقة أن عملية التنمية نفسها مرتبة بشكل مختلف إلى حد ما. إنه أكثر تفاعلية وتكرارية ، لأنه في REPL على Lisp يمكنك القيام بكل شيء: إنشاء وحدات جديدة وحذف الوحدات القديمة وإضافة الأساليب والفصول وحذفها وإعادة تعريف الفئات وما إلى ذلك.



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

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

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

لذلك ، كتجربة ، قمت بعمل ملحق بسيط لـ IPython وأطلق عليه اسم TheREPL.

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

ما هو TheREPL؟ هذا هو الامتداد الذي تقوم بتنزيله ، وبعد ذلك يظهر مفهوم مثل مساحة الاسم في IPython ، ويمكنك أخذ أي وحدة Python والتحويل إليها ، ومعرفة المتغيرات والوظائف وما إلى ذلك. والأهم من ذلك ، يمكنك كتابة def مباشرة ، واسم الوظيفة ، وإعادة تعريف الوظيفة أو الفئة ، وسوف تتغير في جميع الوحدات حيث تم استيرادها. ولكن في نفس الوقت ، لا يتم إعادة تشغيل الوحدة النمطية نفسها ، لذلك يتم حفظ الحالة. بالإضافة إلى ذلك ، يتيح لك TheREPL تجنب المزيد من القطع الأثرية التي يتم تحميلها تلقائيًا والتي سننظر فيها الآن.



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

في حالة الامتداد الخاص بي ، تضغط على الاختصار الأيمن في المحرر ، ويتم تطبيق الوظيفة الموجودة تحت المؤشر على الفور وتبدأ في العمل. أي أنه باستخدام TheREPL ، يمكنك تغيير الكود بشكل أكثر دقة. يمكنك أيضًا كتابة def في IPython.



التبديل بين الوحدات ، كما قلت ، لا يدعم التحميل التلقائي بأي شكل من الأشكال. يمكنك فقط العثور على الملف في نظام الملفات وتغييره ونأمل أن يحل التحميل التلقائي كل شيء هناك.



أبعد. يفقد التحميل التلقائي المتغيرات العالمية ، يحفظ TheREPL ويسمح لك بمواصلة البحث في تشغيل تطبيقك ، وتغيير الكود الداخلي الخاص به وبالتالي تطويره بسرعة.



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

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

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



كيف يتم استبدال الوظيفة التي يقوم بها التحميل التلقائي؟ لدينا وظيفتان ، واحدة واثنتان. تحتوي كل وظيفة على مجموعة من السمات: التوثيق ، والتعليمات البرمجية ، والحجج ، وما إلى ذلك. هنا على الشريحة ، يوجد مثال لاستبدال السمات التي يتم فيها تخزين الرمز الثانوي.

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



هنا مثال على الإغلاق. في السطر الثاني ، نقوم بإنشاء إغلاق نلتقط فيه الدالة foo. يتوقع الإغلاق نفسه أن هذه الوظيفة التي مررناها ترجع خطًا ، وترميزه في utf-8 ويعمل كل شيء.



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



كيف يتم تحديث فئات التحميل التلقائي؟ بسيط جدا. يقوم بتحديث جميع طرق الفصل بنفس طريقة الوظائف ، كما يقوم بتحديث سمة __class__ لجميع الحالات بحيث يبدأ حل الأساليب (تحديد الطريقة التي يجب استدعاؤها) في العمل بطريقة جديدة.

كل شيء أكثر تعقيدًا بعض الشيء في TheREPL ، لأنه عندما تقوم بتحديث _class_ ، قد يتبين أنه يحتوي على بعض المنحدرين من الفئات الفرعية ، والتي تحتاج أيضًا إلى التحديث ، لأن شيئًا ما قد تغير في قائمة الفئات الأساسية.

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



هنا هو مثال جيد. هناك وحدتان - أ و ب. في الوحدة (أ) ، يتم تعريف فئة الأصل ، في الوحدة (ب) فئة تابعة ، وننشئ مثيلًا لفئة الطفل. ويظهر السطر 10 أنه نعم ، هذا مثال لفئة Foo ، الأصل.



بعد ذلك ، نأخذ فقط ونغير الوحدة أ. على سبيل المثال ، قم بإضافة وثائق إلى فئة Foo. ثم يلتقط التحميل التلقائي هذه التغييرات. ما رأيك أنه في هذه الحالة سيعود من بار؟



ويعود خطأ ، لأن التحميل التلقائي قد غيّر فئة Foo ، والآن هو فئة مختلفة تمامًا ، وليس الفئة التي ورثت منها فئة Bar.



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



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



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



وهذه هي الطريقة التي يحل بها TheREPL مشكلة فئات الأطفال. بمعنى ، عندما يتم تغيير الفئة الرئيسية ، فإنها تحدد قائمة الفئات الأساسية من خلال الأمر السحري mro (ترتيب دقة الأسلوب). تحتوي هذه السمة على قائمة من الفئات بالترتيب الذي تريد البحث عن الأساليب أو السمات فيه. وفي كل مرة تستدعي فيها طريقة get_name على جسمك ، على سبيل المثال ، ستقوم Python بفحصها أولاً في فئة Bar ، ثم في فئة Foo ، ثم في فئة الكائن ، إذا لم تجدها. وهو يعمل وفقًا لإجراء ترتيب حل الطريقة.

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

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

لا يزال لدي عرضًا توضيحيًا ، فلنلقِ نظرة على كيفية عمله. أولاً ، دعونا نلقي نظرة على مثل هذا الشيء البسيط.

العرض الأول

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

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

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

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

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

العرض الثاني

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

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

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

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

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

دعونا نرى كيف سيتم تحديث البوت في حالة استخدام TheREPL. من أجل نقاء التجربة ، سأعيد تشغيل IPython وسنكررها مرة أخرى.

والآن أقوم بتنزيل TheREPL. يبدأ على الفور في الاستماع على منفذ معين حتى تتمكن من إرسال رمز بداخله. بالمناسبة ، يمكن القيام بذلك حتى إذا كان IPython يعمل في مكان ما على الخادم والمحرر يعمل محليًا ، والذي يمكن أن يساعدك أيضًا في بعض الحالات.

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

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

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

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

عندما ينفذ البوت هذا الأمر ، فإنه يستدعي الرد لعرض تمثيل البوت. يمكننا الذهاب وتصحيح ، على سبيل المثال ، هذا الرد بشيء آخر. على سبيل المثال ، قم بإجراء ذلك بحيث يتم إدخال الأسماء فقط. يمكنك فعل ذلك. نعود إلى رسولنا ، وننفذ الحالة مرة أخرى. و هذا كل شيء. يعمل الرد الآن بطريقة جديدة ، ولكن الشيء هو نفسه ، فقد حافظ على حالته ، لأنه يتذكرنا جميعًا - Oleg و Sasha و kek و "DROP TABLE Users ، Alex"!

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

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

تأكد من عمل مكون إضافي لـ PyCharm. إذا كان هناك متطوع سيساعدني في Kotlin والمكون الإضافي PyCharm ، فسوف يسعدني التحدث. اكتب لي في البريد أو البرقية .

* * *

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

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

All Articles