لماذا يوجد الكثير من الثعابين؟ مقارنة تنفيذ بايثون

نشرت: 2022-03-11

بايثون مذهلة.

من المدهش أن هذا بيان غامض إلى حد ما. ماذا أعني ب "بايثون"؟ هل أقصد بايثون الواجهة المجردة؟ هل أعني CPython ، تطبيق Python الشائع (ولا يجب الخلط بينه وبين Cython المسمى بالمثل)؟ أم أقصد شيئًا آخر تمامًا؟ ربما أشير بشكل غير مباشر إلى Jython أو IronPython أو PyPy. أو ربما خرجت حقًا من النهاية العميقة وأنا أتحدث عن RPython أو RubyPython (وهما أشياء مختلفة جدًا جدًا).

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

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

في هذا البرنامج التعليمي ، سأبدأ من الصفر وأنتقل عبر تطبيقات Python المختلفة ، وأختتم بمقدمة شاملة لـ PyPy ، والتي أعتقد أنها مستقبل اللغة.

يبدأ كل شيء بفهم ماهية "Python" في الواقع.

إذا كان لديك فهم جيد لرمز الآلة ، والآلات الافتراضية ، وما شابه ذلك ، فلا تتردد في التخطي إلى الأمام.

"هل لغة بايثون مفسرة أم مجمعة؟"

هذه نقطة ارتباك شائعة للمبتدئين في بايثون.

أول شيء يجب إدراكه عند إجراء مقارنة هو أن "Python" هي واجهة . هناك تحديد لما يجب أن تفعله Python وكيف يجب أن تتصرف (كما هو الحال مع أي واجهة). وهناك تطبيقات متعددة (كما هو الحال مع أي واجهة).

الشيء الثاني الذي يجب إدراكه هو أن "المترجمة" و "المترجمة" هي خصائص تنفيذ وليست واجهة .

لذا فإن السؤال نفسه ليس جيدًا حقًا.

هل لغة بايثون مفسرة أم مجمعة؟ السؤال ليس جيدًا حقًا.

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

* ملاحظة: هذا ليس "تجميعًا" بالمعنى التقليدي للكلمة. عادةً ما نقول إن "التجميع" يأخذ لغة عالية المستوى ويحولها إلى رمز آلي. لكنها "تجميع" من نوع ما.

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

بايت كود مقابل كود الجهاز

من المهم جدًا فهم الفرق بين رمز بايت مقابل رمز الآلة (المعروف أيضًا باسم الكود الأصلي) ، ربما يكون أفضل توضيح له من خلال المثال:

  • يُجمّع C إلى رمز الجهاز ، والذي يتم تشغيله بعد ذلك مباشرةً على معالجك. كل تعليمات ترشد وحدة المعالجة المركزية الخاصة بك إلى تحريك الأشياء.
  • تقوم Java بالترجمة إلى bytecode ، والتي يتم تشغيلها بعد ذلك على Java Virtual Machine (JVM) ، وهو تجريد للكمبيوتر الذي ينفذ البرامج. ثم يتم التعامل مع كل تعليمات بواسطة JVM ، والتي تتفاعل مع جهاز الكمبيوتر الخاص بك.

باختصار شديد: كود الآلة أسرع بكثير ، لكن الرمز الثانوي أكثر قابلية للنقل وأمانًا .

يبدو رمز الجهاز مختلفًا اعتمادًا على جهازك ، ولكن يبدو الرمز الثانوي كما هو على جميع الأجهزة. قد يقول المرء أن رمز الجهاز هو الأمثل لإعدادك.

بالعودة إلى تطبيق CPython ، تكون عملية toolchain كما يلي:

  1. يقوم CPython بترجمة شفرة مصدر Python الخاصة بك إلى كود ثانوي.
  2. ثم يتم تنفيذ هذا الرمز الثانوي على CPython Virtual Machine.
غالبًا ما يفترض المبتدئون أن Python قد تم تجميعها بسبب ملفات .pyc. هناك بعض الحقيقة في ذلك: ملف .pyc هو الرمز الثانوي المترجم ، والذي يتم تفسيره بعد ذلك. لذلك إذا قمت بتشغيل كود Python الخاص بك من قبل وكان لديك ملف .pyc في متناول يديك ، فسيتم تشغيله بشكل أسرع في المرة الثانية ، حيث لا يتعين عليه إعادة تجميع الرمز الثانوي.

الأجهزة الافتراضية البديلة: Jython و IronPython والمزيد

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

ولكن ماذا عن تطبيقات Python البديلة؟ أحد أكثرها بروزًا هو Jython ، وهو تطبيق Python مكتوب بلغة Java يستخدم JVM. بينما ينتج CPython رمز بايت للتشغيل على CPython VM ، ينتج Jython Java bytecode لتشغيله على JVM (هذه هي نفس الأشياء التي يتم إنتاجها عند تجميع برنامج Java).

تم توضيح استخدام Jython لـ Java bytecode في مخطط تطبيق Python هذا.

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

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

كمثال ، هذا كود جايثون صالح:

 [Java HotSpot(TM) 64-Bit Server VM (Apple Inc.)] on java1.6.0_51 >>> from java.util import HashSet >>> s = HashSet(5) >>> s.add("Foo") >>> s.add("Bar") >>> s [Foo, Bar]

IronPython هو تطبيق شائع آخر لبايثون ، مكتوب بالكامل بلغة C # ويستهدف حزمة .NET. على وجه الخصوص ، يتم تشغيله على ما قد تسميه .NET Virtual Machine ، Microsoft Common Language Runtime (CLR) ، مقارنة بـ JVM.

قد تقول أن جايثون: Java :: IronPython: C # . تعمل على نفس الأجهزة الافتراضية الخاصة ، يمكنك استيراد فئات C # من كود IronPython وفئات Java من كود Jython الخاص بك ، إلخ.

من الممكن تمامًا البقاء على قيد الحياة دون الحاجة إلى لمس تطبيق غير تابع لـ CPython Python. ولكن هناك مزايا يمكن الاستفادة منها من التبديل ، ويعتمد معظمها على مجموعة التكنولوجيا الخاصة بك. هل تستخدم الكثير من اللغات القائمة على JVM؟ قد يكون Jython لك. كل شيء عن المكدس. NET؟ ربما يجب أن تجرب IronPython (وربما لديك بالفعل).

يوضح مخطط مقارنة Python الاختلافات بين تطبيقات Python.

بالمناسبة: في حين أن هذا لن يكون سببًا لاستخدام تطبيق مختلف ، لاحظ أن هذه التطبيقات تختلف في الواقع في السلوك فيما يتجاوز كيفية تعاملها مع شفرة مصدر Python الخاصة بك. ومع ذلك ، عادة ما تكون هذه الاختلافات طفيفة ، وتختفي أو تظهر بمرور الوقت حيث تكون هذه التطبيقات قيد التطوير النشط. على سبيل المثال ، يستخدم IronPython سلاسل Unicode افتراضيًا ؛ ومع ذلك ، يقوم CPython بالتعيين الافتراضي لـ ASCII للإصدارات 2.x (فشل مع UnicodeEncodeError للأحرف غير ASCII) ، ولكنه يدعم سلاسل Unicode افتراضيًا لـ 3.x.

التجميع في الوقت المناسب: PyPy والمستقبل

لذلك لدينا تطبيق Python مكتوب بلغة C وواحد في Java وواحد في C #. الخطوة المنطقية التالية: تطبيق Python مكتوب بـ… Python. (سيلاحظ القارئ المتعلم أن هذا مضلل بعض الشيء).

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

جيت: لماذا وكيف

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

على سبيل المثال ، نهج مشترك تتبعه JITs:

  1. تحديد رمز بايت يتم تنفيذه بشكل متكرر.
  2. قم بتجميعها إلى رمز الجهاز الأصلي.
  3. تخزين النتيجة مؤقتًا.
  4. عندما يتم تعيين نفس الرمز الثانوي ليتم تشغيله ، بدلاً من ذلك ، احصل على رمز الجهاز المجمع مسبقًا وجني الفوائد (على سبيل المثال ، زيادة السرعة).

هذا ما يدور حوله تطبيق PyPy: إحضار JIT إلى Python (انظر الملحق لمعرفة الجهود السابقة). هناك ، بالطبع ، أهداف أخرى: تهدف PyPy إلى أن تكون عبر النظام الأساسي ، وخفيفة الذاكرة ، وداعمة بدون تكديس. لكن JIT هي حقًا نقطة بيعها. كمتوسط ​​على مدى مجموعة من الاختبارات الزمنية ، يُقال إنه يحسن الأداء بعامل 6.27. للحصول على تفصيل ، راجع هذا المخطط من PyPy Speed ​​Center:

إن إحضار JIT إلى واجهة Python باستخدام تطبيق PyPy يؤتي ثماره في تحسينات الأداء.

يصعب فهم PyPy

تتمتع PyPy بإمكانيات هائلة ، وفي هذه المرحلة تتوافق بشكل كبير مع CPython (لذا يمكنها تشغيل Flask و Django وما إلى ذلك).

ولكن هناك الكثير من الالتباس حول PyPy (انظر ، على سبيل المثال ، هذا الاقتراح غير المنطقي لإنشاء PyPyPy ...). في رأيي ، هذا في الأساس لأن PyPy هي في الواقع شيئين:

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

     x = random.choice([1, "foo"])

    سيكون كود Python صالحًا (الائتمان إلى Ademan). ما هو نوع x ؟ كيف يمكننا التفكير في أنواع المتغيرات عندما لا يتم تطبيق الأنواع بشكل صارم؟). مع RPython ، أنت تضحي ببعض المرونة ، ولكن بدلاً من ذلك تجعل التفكير في إدارة الذاكرة وغير ذلك أسهل بكثير ، مما يسمح بالتحسينات.

  2. مترجم يقوم بتجميع كود RPython لأهداف مختلفة ويضيف في JIT. النظام الأساسي الافتراضي هو C ، أي مترجم RPython-to-C ، ولكن يمكنك أيضًا استهداف JVM وغيرها.

من أجل الوضوح فقط في دليل مقارنة بايثون هذا ، سأشير إليها باسم PyPy (1) و PyPy (2).

لماذا تحتاج هذين الشيئين ، ولماذا تحت سقف واحد؟ فكر في الأمر بهذه الطريقة: PyPy (1) مترجم مكتوب بلغة RPython. لذلك يأخذ في كود Python للمستخدم ويجمعها إلى الرمز الثانوي. لكن المترجم نفسه (المكتوب بلغة RPython) يجب أن يفسر من خلال تطبيق Python آخر لكي يعمل ، أليس كذلك؟

حسنًا ، يمكننا فقط استخدام CPython لتشغيل المترجم. لكن هذا لن يكون سريعًا جدًا.

بدلاً من ذلك ، الفكرة هي أننا نستخدم PyPy (2) (يشار إليها باسم RPython Toolchain) لتجميع مترجم PyPy وصولاً إلى رمز لمنصة أخرى (على سبيل المثال ، C أو JVM أو CLI) للتشغيل على أجهزتنا ، مع إضافة JIT كـ نحن سوف. إنه أمر سحري: يضيف PyPy JIT ديناميكيًا إلى مترجم فوريًا ، وينشئ مترجمًا خاصًا به! ( مرة أخرى ، هذا هراء: نحن نجمع مترجمًا فوريًا ، ونضيف مترجمًا منفصلاً ومستقلًا. )

في النهاية ، تكون النتيجة ملف تنفيذي مستقل يفسر كود مصدر Python ويستغل تحسينات JIT. وهو بالضبط ما أردناه! إنها عبارة عن جرعة كبيرة ، ولكن ربما يساعد هذا الرسم التخطيطي في:

يوضح هذا الرسم البياني جمال تطبيق PyPy ، بما في ذلك المترجم الفوري والمترجم والملف التنفيذي باستخدام JIT.

للتكرار ، فإن الجمال الحقيقي لـ PyPy هو أننا يمكن أن نكتب لأنفسنا مجموعة من مترجمي Python المختلفين في RPython دون القلق بشأن JIT. ستقوم PyPy بعد ذلك بتنفيذ JIT لنا باستخدام RPython Toolchain / PyPy (2).

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

يمكنك نظريًا كتابة مترجم فوري لأي لغة ، وإطعامه إلى PyPy ، والحصول على JIT لتلك اللغة.

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

  1. قم بتشغيل المترجم الفوري وتفسير كل شيء (مع عدم إضافة JIT).
  2. قم ببعض التنميط الخفيف للشفرة المفسرة.
  3. حدد العمليات التي قمت بها من قبل.
  4. قم بتجميع أجزاء التعليمات البرمجية هذه وصولاً إلى رمز الجهاز.

لمزيد من المعلومات ، هذه الورقة سهلة الوصول للغاية ومثيرة للاهتمام للغاية.

للختام: نستخدم مترجم PyPy RPython-to-C (أو النظام الأساسي المستهدف الآخر) لتجميع مترجم PyPy's RPython المنفذ.

تغليف

بعد مقارنة مطولة لتطبيقات Python ، يجب أن أسأل نفسي: لماذا هذا رائع جدًا؟ لماذا هذه الفكرة المجنونة تستحق المتابعة؟ أعتقد أن أليكس جاينور صاغها جيدًا في مدونته: "[PyPy هو المستقبل] لأنه [] يوفر سرعة أفضل ومزيدًا من المرونة ومنصة أفضل لنمو Python."

بالمختصر:

  • إنه سريع لأنه يجمع كود المصدر إلى كود أصلي (باستخدام JIT).
  • إنه مرن لأنه يضيف JIT إلى المترجم الفوري الخاص بك مع القليل جدًا من العمل الإضافي.
  • إنه مرن (مرة أخرى) لأنه يمكنك كتابة المترجمين الفوريين في RPython ، وهو أسهل في التمديد من ، على سبيل المثال ، C (في الواقع ، من السهل جدًا وجود برنامج تعليمي لكتابة المترجمين الفوريين الخاصين بك).

الملحق: أسماء Python الأخرى التي قد تسمعها

  • Python 3000 (Py3k): تسمية بديلة لـ Python 3.0 ، إصدار Python رئيسي غير متوافق مع الإصدارات السابقة ظهر المرحلة في عام 2008. توقع فريق Py3k أن الأمر سيستغرق حوالي خمس سنوات حتى يتم اعتماد هذا الإصدار الجديد بالكامل. وبينما يواصل معظم مطوري Python (تحذير: ادعاء قصصي) استخدام Python 2.x ، يزداد وعي الناس بـ Py3k.

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

  • Numba: "مترجم متخصص في الوقت المناسب" يضيف JIT إلى كود Python المشروح . في أبسط المصطلحات ، تقوم بإعطائها بعض التلميحات ، وتقوم بتسريع أجزاء من التعليمات البرمجية الخاصة بك. يأتي Numba كجزء من توزيع Anaconda ، وهو مجموعة من الحزم لتحليل البيانات وإدارتها.

  • IPython: يختلف تمامًا عن أي شيء آخر تمت مناقشته. بيئة حاسوبية لبايثون. تفاعلي مع دعم مجموعات أدوات واجهة المستخدم الرسومية وتجربة المتصفح ، إلخ.

  • Psyco: وحدة امتداد Python ، وأحد جهود Python JIT المبكرة. ومع ذلك ، فقد تم تصنيفها منذ ذلك الحين على أنها "ميتة وميتة". في الواقع ، المطور الرئيسي لـ Psyco ، Armin Rigo ، يعمل الآن على PyPy.

روابط لغة بايثون

  • RubyPython: جسر بين Ruby و Python VMs. يسمح لك بتضمين كود Python في كود Ruby الخاص بك. أنت تحدد مكان بدء Python وتوقفه ، وينظم RubyPython البيانات بين الأجهزة الافتراضية.

  • PyObjc: روابط اللغة بين Python و Objective-C ، تعمل كجسر بينهما. من الناحية العملية ، هذا يعني أنه يمكنك استخدام مكتبات Objective-C (بما في ذلك كل ما تحتاجه لإنشاء تطبيقات OS X) من كود Python الخاص بك ووحدات Python من كود Objective-C الخاص بك. في هذه الحالة ، من الملائم كتابة CPython بلغة C ، وهي مجموعة فرعية من Objective-C.

  • PyQt: بينما يمنحك PyObjc الربط لمكونات OS X GUI ، فإن PyQt تفعل الشيء نفسه بالنسبة لإطار عمل تطبيق Qt ، مما يتيح لك إنشاء واجهات رسومية غنية ، والوصول إلى قواعد بيانات SQL ، إلخ. أداة أخرى تهدف إلى جلب بساطة Python إلى أطر أخرى.

أطر عمل جافا سكريبت

  • pyjs (Pajamas): إطار عمل لإنشاء تطبيقات الويب وسطح المكتب في Python. يتضمن مترجم Python-to-JavaScript ومجموعة عناصر واجهة مستخدم وبعض الأدوات الأخرى.

  • Brython: Python VM مكتوب بلغة JavaScript للسماح بتنفيذ كود Py3k في المتصفح.