بيثون كحالة C ++ النهائية. الجزء 1/2

من المترجم

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


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


كما هو معتاد معي ، أطلب منك أن تبلغني في المساء عن أخطائي والأخطاء المطبعية.


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


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


الأصول غير الأساسية


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


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


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


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


بناء الجملة


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


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


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


حرفية رقمية وخيطية تتحكم في التتابعات باستخدام خطوط مائلة للخلف - كل هذا سيبدو في C.


تنفيذ التحكم في التدفق؟ الشيء نفسه if ، else ، في while ، break continue . بالطبع ، سوف نضيف القليل من المرح من خلال الاشتراك مع القديم الجيد للتكرار عبر بنيات البيانات ونطاقات القيمة. سيتم اقتراح ذلك لاحقًا في الإصدار C ++ 11 ، ولكن في Python ، قام المشغل بتغليف جميع العمليات في البداية لحساب الأحجام والارتباطات الزائدة وزيادة العداد ، وما إلى ذلك ، بمعنى آخر ، القيام بكل ما هو ضروري لتزويد المستخدم بعنصر في بنية البيانات. أي نوع من الهياكل؟ لا يهم ، فقط قم بتمريرها إلى ، سوف يتم اكتشافها.


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


اوه نعم سنقوم بإصلاح عيب التصميم الأصلي في C - إضافة فاصلة متدلية!


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


 If (n = 0) then begin writeln('N is now zero'); func := 1 end 

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


 int a[] = {4, 5, 6}; 

ولكن عندما يصبح مهيئك أطول وترتبه رأسيًا ، فإنك تحصل على نفس التباين غير المريح كما في Pascal:


 int a[] = { 4, 5, 6 }; 

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


في وقت لاحق ، صححت معايير C99 و C ++ 11 أيضًا سوء الفهم الأولي ، مما يسمح لك بإضافة فاصلة بعد الحرفي الأخير في المُهيئ.


النطاقات


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


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


قم my_package دليل my_package ، ضع ملف my_module.py ، ثم قم بتعريف الفئة في الملف:


 class C(object): READ = 1 WRITE = 2 

ثم سيكون الوصول إلى سمات الفصل كما يلي:


 import my_package.my_module my_package.my_module.C.READ 

لا تقلق ، لن نجبر المستخدم على طباعة الاسم الكامل في كل مرة. سنمنحه الفرصة لاستخدام إصدارات عديدة من عبارة import لتغيير درجة "القرب" من مساحة الاسم:


 import my_package.my_module my_package.my_module.C.READ from my_package import my_module my_module.C.READ from my_package.my_module import C C.READ 

وبالتالي ، لن تتعارض نفس الأسماء الواردة في حزم مختلفة:


 import json j = json.load(file) import pickle p = pickle.load(file) 

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


وظيفة الزائد


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


واجهات برمجة التطبيقات للنظام


سنمنح المستخدم حق الوصول الكامل إلى العديد من واجهات برمجة التطبيقات للنظام ، بما في ذلك المقابس. لا أفهم لماذا يقدم مؤلفو لغات البرمجة النصية دائمًا طرقًا مبدعة خاصة بهم لفتح مأخذ توصيل. ومع ذلك ، فهم لا يدركون أبدًا واجهة برمجة تطبيقات Unix Socket الكاملة. أنها تنفذ 5-6 الوظائف التي يفهمونها ، ورمي كل شيء آخر. بيثون ، على عكسهم ، يحتوي على وحدات قياسية للتفاعل مع نظام التشغيل الذي ينفذ كل مكالمة نظام قياسية. هذا يعني أنه يمكنك فتح كتاب ستيفنز الآن والبدء في كتابة التعليمات البرمجية. وستعمل جميع مآخذك والعمليات والشوك تمامًا كما يقول. نعم ، من المحتمل أن يكون Guido أو مساهمو Python الأوائل قد فعلوا ذلك تمامًا ، لأنهم كانوا كسولًا جدًا في كتابة تطبيقهم لمكتبات النظام ، كسول جدًا ليشرح للمستخدمين مرة أخرى كيف تعمل مآخذ التوصيل. ولكن نتيجة لذلك ، حققوا تأثيرًا رائعًا: يمكنك نقل كل ما لديك من معرفة UNIX المكتسبة في C و C ++ إلى بيئة Python.


لذلك ، قررنا ما هي الميزات التي "سنستعيرها" من C ++ لإنشاء لغة البرمجة النصية الخاصة بنا. الآن نحن بحاجة إلى تحديد ما نريد إصلاحه.


سلوك غير محدد


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


أولويات العملية


يجب أن تكون قد واجهت أخطاء مماثلة: تعبير


 oflags & 0x80 == nflags & 0x80 

إرجاع دائمًا 0 ، لأن المقارنات في C لها الأسبقية على عمليات bitwise. بمعنى آخر ، يتم تقييم هذا التعبير إلى


 oflags & (0x80 == nflags) & 0x80 

أوه ، هذا C!


سنقوم بحذف السبب المحتمل لمثل هذه الأخطاء في لغتنا النصية البسيطة ، مع وضع أولوية عمليات المقارنة وراء التلاعب الحسابي والبت ، بحيث يتم احتساب التعبير من مثالنا بشكل أكثر حدسية:


 (oflags & 0x80) == (nflags & 0x80) 

تحسينات أخرى


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


سنترك إمكانية إجراء اختصار لحساب عوامل التشغيل المنطقية لدينا ( https://en.wikipedia.org/wiki/Short-circuit_evaluation ) ، ولكن أيضًا نوفر لهم القدرة على إرجاع القيمة النهائية من أي نوع ، وليس فقط Boolean. ثم تعبيرات مثل


 s = error.message or 'Error' 

في هذا المثال ، سيتم تعيين المتغير إلى error.message إذا كان غير فارغ ، وإلا فإن السلسلة "Error".


نقوم بتوسيع فكرة C أن 0 تعادل false للكائنات الأخرى غير الأعداد الصحيحة. على سبيل المثال ، على الخطوط الفارغة والحاويات.


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


صارمة مقابل الكتابة الضعيفة


قضية أخرى مهمة في تصميم لغة البرمجة: دقة الكتابة. كثير من الجمهور على دراية جافا سكريبت؟ ماذا يحدث إذا تم طرح الرقم 3 من السلسلة "4"؟


 js> '4' - 3 1 

! ممتاز وإذا قمت بإضافة الرقم 3 إلى السلسلة "4"؟


 js> '4' + 3 "43" 

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


 js> [] + [] "" js> [] + {} "[object Object]" 

نتوقع أن تكون عملية الإضافة بديلة ، ولكن ماذا يحدث إذا قمنا بتغيير الشروط في الحالة الأخيرة؟


 js> {} + [] 0 

جافا سكريبت ليست وحدها في مشاكلها. بيرل في موقف مماثل يحاول أيضًا إرجاع شيء على الأقل:


 perl> "3" + 1 4 

و awk سوف تفعل شيئا مثل هذا:


 $ echo | awk '{print "3" + 1}' 4 

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


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


في Python ، كما هو الحال في C ++ ، ستُرجع هذه التعبيرات خطأ.


 >>> '4' - 3 TypeError >>> '4' + 3 TypeError 

لأن كتابة الصب ، إذا لزم الأمر حقًا ، من السهل الكتابة بوضوح:


 >>> int('4') + 3 7 >>> '4' + str(3) '43' 

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


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


تابع: " بيثون كحالة C ++ النهائية. الجزء 2/2 . "

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


All Articles