طريقة كتابة 4 ملايين سطر من كود بايثون. الجزء 1

نلفت انتباهكم اليوم إلى الجزء الأول من ترجمة المواد المتعلقة بكيفية مشاركة Dropbox في التحكم في نوع كود Python.



دروببوإكس يكتب الكثير في بيثون. هذه لغة نستخدمها على نطاق واسع للغاية - سواء بالنسبة للخدمات الخلفية أو تطبيقات عميل سطح المكتب. نستخدم أيضًا Go و TypeScript و Rust بأحجام كبيرة ، لكن Python هي لغتنا الرئيسية. نظرًا لنطاقنا ، ونتحدث عن ملايين أسطر شفرة Python ، اتضح أن الكتابة الديناميكية لمثل هذه الشفرة قد أدت إلى تعقيد فهمها بشكل غير ضروري وبدأت في التأثير بشكل خطير على الإنتاجية. لتخفيف هذه المشكلة ، بدأنا في ترجمة التعليمات البرمجية الخاصة بنا تدريجياً إلى التحقق من النوع الثابت باستخدام mypy. من المحتمل أن يكون هذا هو نظام التحقق من النوع المستقل الأكثر شعبية في بيثون. Mypy هو مشروع مفتوح المصدر ؛ يعمل مطوروه الرئيسيون في Dropbox.

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

اقرأ الجزء الثاني

لماذا هو التحقق من النوع ضروري؟


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

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

  • هل يمكن لهذه الوظيفة العودة بلا؟
  • ماذا يجب أن تكون هذه الحجة؟
  • ما هو id : نوع السمة int ، هل هو str ، أو ربما نوع مخصص؟
  • يجب أن تكون هذه الحجة قائمة؟ هل من الممكن تمرير tuple في ذلك؟

إذا نظرت إلى مقتطف الشفرة التالي ، المجهز بملاحظات الكتابة التوضيحية ، وحاول الإجابة على هذه الأسئلة ، اتضح أن هذه هي أبسط مهمة:

 class Resource:    id: bytes    ...    def read_metadata(self,                      items: Sequence[str]) -> Dict[str, MetadataItem]:        ... 

  • لا read_metadata ، لأن نوع الإرجاع غير Optional[…] .
  • وسيطة items هي سلسلة من السلاسل. لا يمكن تكرارها بأي ترتيب.
  • سمة id هي سلسلة من البايتات.

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

على الرغم من أن أداء Python جيدًا في المراحل المبكرة أو المتوسطة من المشروعات ، فقد تواجه المشاريع الناجحة والشركات التي تستخدم Python في وقت ما سؤالًا حيويًا: "هل نحتاج إلى إعادة كتابة كل شيء بلغة مطبوعة بشكل ثابت؟"

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

استخدام مثل هذه الأنظمة له مزايا أخرى ، وهي بالفعل غير بديهية تمامًا:

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

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

خلفية Mypy


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

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

 def Fib(n as Int) as Int  if n <= 1    return n  else    return Fib(n - 1) + Fib(n - 2)  end end 

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

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

في الواقع ، لم يكن من الممكن تسمية اللغة المدعومة من قبل نظام الكتابة الخاص بي في ذلك الوقت Python: لقد كان متغير Python بسبب بعض القيود في بناء جملة جملة Python 3 type.

بدا الأمر وكأنه مزيج من Java و Python:

 int fib(int n):    if n <= 1:        return n    else:        return fib(n - 1) + fib(n - 2) 

كان أحد أفكاري في ذلك الوقت هو استخدام التعليقات التوضيحية للنوع لتحسين الأداء من خلال تجميع هذا النوع من بيثون في C ، أو ربما في JVM bytecode. لقد تقدمت إلى مرحلة كتابة النموذج الأولي للمترجم ، لكنني تركت هذه الفكرة ، لأن التحقق من النوع نفسه بدا مفيدًا للغاية.

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

 def fib(n: int) -> int:    if n <= 1:        return n    else:        return fib(n - 1) + fib(n - 2) 

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

 products = [] # type: List[str] # Eww 

أصبحت التعليقات المكتوبة أيضًا مفيدة لدعم Python 2 ، الذي يفتقر إلى الدعم المدمج للتعليقات التوضيحية للنوع:

 f fib(n):    # type: (int) -> int    if n <= 1:        return n    else:        return fib(n - 1) + fib(n - 2) 

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

أقنعني جيدو أيضًا بالانضمام إلى Dropbox بعد أن دافعت عن تخرجي. هذا هو المكان الذي يبدأ فيه الجزء الممتع في تاريخ mypy.

أن تستمر ...

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


Source: https://habr.com/ru/post/ar468233/


All Articles