بايثون 3.13: ميزات جديدة رائعة يمكنك تجربتها

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

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

في هذا الدرس، ستتعرف على بعض التحسينات في الإصدار الجديد، بما في ذلك:

  • تحسينات تم إجراؤها على المترجم التفاعلي (REPL)
  • رسائل الخطأ الواضحة التي يمكن أن تساعدك في إصلاح الأخطاء الشائعة
  • تم تحقيق تقدم في إزالة قفل المترجم العالمي (GIL) وجعل بايثون حر الترابط
  • تنفيذ مُجمِّع تجريبي في الوقت المناسب (JIT)
  • مجموعة من الترقيات البسيطة لنظام النوع الثابت في بايثون

ملاحظة: كان من المقرر إصدار بايثون 3.13 في الأول من أكتوبر 2024. اكتشف المطورون بعض المشكلات أثناء الاختبار وقرروا تأجيل الإصدار إلى السابع من أكتوبر.

مترجم تفاعلي محسّن (REPL)

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

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

ابدأ بتشغيل REPL. يمكنك القيام بذلك عن طريق كتابة python في الطرفية. اعتمادًا على إعدادك، قد تضطر إلى كتابة py أو python3 أو حتى python3.13 بدلاً من ذلك. إحدى الطرق للتعرف على أنك تستخدم المترجم الجديد الذي يأتي مع بايثون 3.13 هي أن موجه الأوامر المكون من ثلاثة أشكال (>>>) ملون بشكل خفي:

أحد التحسينات هو أنه يمكنك الآن استخدام الأوامر الخاصة بـ REPL دون استدعائها باستخدام أقواس كما لو كانت دوال بايثون. فيما يلي بعض الأوامر واختصارات لوحة المفاتيح التي يمكنك استخدامها:

  • exit أو quit: الخروج من المترجم
  • clear: مسح الشاشة
  • help أو F1: الوصول إلى نظام المساعدة
  • F2: فتح متصفح التاريخ
  • F3:الدخول إلى وضع اللصق

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

لتجربة ذلك بنفسك، أدخل الكود التالي في REPL الخاص بك:

>>> numbers = range(3, 13)
>>> [
...     (number - 3)**3 for number in numbers
...     if number % 2 == 1
... ]
[0, 8, 64, 216, 512]

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

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

إن القدرة على استدعاء العبارات متعددة الأسطر وتحريرها توفر الكثير من الوقت وستجعلك أكثر كفاءة عند العمل مع REPL.

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

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

import random

num_faces = 6

print("Hit enter to roll die (q to quit, number for # of faces) ")
while True:
    roll = input()
    if roll.lower().startswith("q"):
        break
    if roll.isnumeric():
        num_faces = int(roll)

    result = random.randint(1, num_faces)
    print(f"Rolling a d{num_faces:<2d} -  {result:2d}")

إذا حاولت نسخ ولصق هذا الكود في REPL أقدم، فلن يعمل. يتسبب السطر الفارغ داخل حلقة while في حدوث مشكلات. تدخل حلقة لا نهائية لا تفعل أي شيء على ما يبدو. لإيقاف الحلقة، يمكنك كتابة q والضغط على Enter أو الضغط على Ctrl+C.

في Python 3.13، تعمل عملية اللصق دون أي مشكلة:

>>> import random
...
... num_faces = 6
...
... print("Hit enter to roll die (q to quit, number for # of faces) ")
... while True:
...     roll = input()
...     if roll.lower().startswith("q"):
...         break
...     if roll.isnumeric():
...         num_faces = int(roll)
...
...     result = random.randint(1, num_faces)
...     print(f"Rolling a d{num_faces:<2d} -  {result:2d}")
...
Hit enter to roll die (q to quit, number for # of faces)
13
Rolling a d13 -  10

Rolling a d13 -   5

Rolling a d13 -   3
q

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

رسائل خطأ أفضل

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

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

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

أولاً، ستستفز بايثون وتنشئ خطأ وقت التشغيل للحصول على لمحة عن عمليات التتبع الملونة. افتح REPL وقم بتعريف inverse() على النحو التالي:

>>> def inverse(number):
...     return 1 / number
...

تحسب دالة inverse() المعكوس الضربي لرقم ما. هناك مشكلة لا تأخذها دالة inverse() في الاعتبار، وهي أن الصفر ليس له معكوس. في دالتك، سترى هذا لأن inverse(0) تثير خطأ ZeroDivisionError:

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

ومع ذلك، إذا كنت لا تحب الألوان، فيمكنك إيقاف تشغيلها عن طريق تعيين متغير البيئة PYTHON_COLORS إلى 0.

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

>>> numbers = [2, 0, 2, 4, 1, 0, 0, 1]
>>> sorted(numbers, reversed=True)
Traceback (most recent call last):
  File "<python-input-1>", line 1, in <module>
    sorted(numbers, reversed=True)
    ~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: sort() got an unexpected keyword argument 'reversed'.
           Did you mean 'reverse'?

>>> sorted(numbers, reverse=True)
[4, 2, 2, 1, 1, 0, 0, 0]

تحاول استخدام reverse=True لفرز الأرقام بترتيب عكسي. في البداية، ترتكب خطأ مطبعيًا وتستدعي الوسيطة معكوسة بـ d في النهاية. يلاحظ بايثون هذا ويقترح بشكل مفيد أنك تقصد العكس.

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

السيناريو التالي هو شيء ربما واجهته. تذكر نص رمي النرد من قبل. قل أنك قمت باستدعاء الملف random.py بدلاً من ذلك:

import random

num_faces = 6

print("Hit enter to roll die (q to quit, number for # of faces) ")
while True:
    roll = input()
    if roll.lower().startswith("q"):
        break
    if roll.isnumeric():
        num_faces = int(roll)

    result = random.randint(1, num_faces)
    print(f"Rolling a d{num_faces:<2d} -  {result:2d}")

تنتظرك لعبتك على الطاولة وأنت متحمس لبدء رمي النرد. قم بتشغيل البرنامج النصي واترك العشوائية تتكشف:

$ python random.py
Hit enter to roll die (q to quit, number for # of faces)

Traceback (most recent call last):
  File "/home/pyarabic/random.py", line 1, in <module>
    import random
  File "/home/pyarabic/random.py", line 13, in <module>
    result = random.randint(1, num_faces)
             ^^^^^^^^^^^^^^
AttributeError: module 'random' has no attribute 'randint' (consider
                renaming '/home/pyarabic/random.py' since it has the same
                name as the standard library module named 'random' and the
                import system gives it precedence)

رسائل الخطأ مثل عدم وجود سمة “randint” في الوحدة النمطية “random” قد أربكت مطوري Python لعقود من الزمن. من المؤكد أن randint() هي إحدى الوظائف في الوحدة النمطية “random”!

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

ملاحظة: يمكنك استخدام حل بديل آخر لجعل المثال يعمل. إذا أضفت الخيار -P وقمت بتشغيل python -P random.py، فلن يقوم بايثون بتضمين الدليل الحالي في المسارات التي يبحث فيها بايثون أثناء عمليات الاستيراد، ولن يخفي البرنامج النصي الخاص بك وحدة مكتبة standard-library العشوائية.

قم بإعادة تسمية random.py إلى roll_dice.py وقم بتشغيله مرة أخرى:

$ python roll_dice.py
Hit enter to roll die (q to quit, number for # of faces)

Rolling a d6  -   3

Rolling a d6  -   1

Rolling a d6  -   3
20
Rolling a d20 -  13
q

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

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

يساعد العمل المستمر على تحسين تجربة المطور الخاص بك على مساعدة بايثون في الدفاع عن مكانتها كلغة صديقة للمبتدئين.

بايثون حر الترابط: لا يوجد GIL

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

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

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

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

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

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

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

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

$ CODE='import pyfeatures; print(pyfeatures.FreeThreading())'

$ python3.13 -c "$CODE"
Free Threading: unsupported

$ python3.13t -c "$CODE"
Free Threading: enabled ✨

$ python3.13t -X gil=1 -c "$CODE"
Free Threading: disabled

يمكنك تمكين GIL وإيقاف تشغيل الترابط الحر من خلال تعيين -X gil=1. تتوفر وحدة pyfeatures المطلوبة لتشغيل الكود أعلاه في المواد القابلة للتنزيل. وهي تحتوي على وظيفة للكشف عن حالة الترابط الحر.

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

أداء Python 3.13 على جهاز كمبيوتر الكل في واحد

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

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

مُجمِّع JIT التجريبي

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

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

يوفر مُجمِّع JIT أو Just-in-Time نوعًا من الأرضية الوسطى، حيث قد يختار المُفسِّر تجميع بعض التعليمات البرمجية أثناء تشغيل البرنامج من أجل تسريعه. في بايثون 3.13، يوجد مُجمِّع JIT تجريبي جديد. وحقيقة أنه تجريبي يعني أنه موجود في التعليمات البرمجية المصدرية لـبايثون، ولكنه غير مُمكَّن افتراضيًا.

إذا كنت تريد تجربة مُجمِّع JIT، فيتعين عليك إعداد إصدار خاص من بايثون 3.13 مع تمكين JIT. وهذا مشابه لما يتعين عليك القيام به لتجربة بايثون ذات الخيوط الحرة، على الرغم من اختلاف علامات البناء.

يعتمد مُجمِّع JIT الخاص بـبايثون على خوارزمية تسمى copy-and-patch. سيبحث المُفسِّر عن الأنماط في الكود الخاص بك التي تتطابق مع القوالب المُجمَّعة مسبقًا ويملأ كود الجهاز بمعلومات محددة مثل عناوين الذاكرة للمتغيرات.

يمكن أن يكون التجميع في الوقت المناسب فعالاً ويعطي أداءً أفضل في الكود حيث يتم تكرار نفس العملية مرارًا وتكرارًا، مما يمنح مُجمِّع JIT الوقت لتحليل الكود وترجمته. في أحد معايير القياس، الذي قام بحساب سلسلة فيبوناتشي بشكل متكرر، يمكنك أن ترى تحسنًا طفيفًا في الأداء باستخدام JIT:

أداء Python 3.13 على جهاز كمبيوتر الكل في واحد

في هذا المعيار، فإن بايثون 3.13 مع تمكين JIT أسرع من بايثون 3.12 وبايثون 3.13 العادي. ومع ذلك، فإن JIT ليس جاهزًا للاستخدام اليومي بعد، ويجب أن تقتصر على تجربته. نأمل أن يكون لـ JIT تأثير أكبر في إصدارات بايثون المستقبلية.

تحسينات على الكتابة الثابتة

على الرغم من أن بايثون هي لغة ذات نوع ديناميكي، يمكنك إضافة معلومات النوع الثابتة في تلميحات النوع الاختيارية. يمكنك تشغيل أداة فحص نوع منفصلة مثل mypy أو Pyright للتحقق من صحة الكود الخاص بك، أو يمكنك الاعتماد على IDE الخاص بك لإرشادك بشأن المشكلات.

تم تعريف أسس نظام النوع الثابت في بايثون في PEP 484 وتم تقديمها في بايثون 3.5. في نوفمبر 2023، أسس PEP 729 مجلس النوع وقام بإضفاء الطابع الرسمي على نظام النوع من خلال مواصفات النوع.

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

بالإضافة إلى توفير مزيد من الوضوح في إدارة نظام النوع في بايثون، قام المجتمع أيضًا بتنفيذ بعض التغييرات المتعلقة بالكود. تم قبول PEPs التالية لـ بايثون 3.13:

  • PEP 696: الإعدادات الافتراضية للنوع لمعلمات النوع
  • PEP 742: تضييق الأنواع باستخدام TypeIs
  • PEP 705: : عناصر TypedDict للقراءة فقط
  • PEP 702: تحديد الإهمال باستخدام نظام النوع

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

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

ملاحظة: في هذا القسم، ستستخدم Pyright كمدقق للنوع. يمكنك تثبيت Pyright في بيئتك الافتراضية باستخدام pip.

إذا كنت تستخدم Visual Studio Code، فيمكنك استخدام Pyright داخل المحرر من خلال ملحق Pylance. قد تحتاج إلى تنشيطه عن طريق تعيين خيار Python › Analysis: Type Checking Mode في إعداداتك.

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

تقليديًا، كان عليك تعريف متغيرات النوع صراحةً باستخدام TypeVar. ومنذ بايثون 3.12، يمكنك إعلان متغيرات النوع في صيغة [T] أكثر إحكامًا باستخدام الأقواس المربعة:

from collections import deque

class Queue[T]:
    def __init__(self) -> None:
        self.elements: deque[T] = deque()

    def push(self, element: T) -> None:
        self.elements.append(element)

    def pop(self) -> T:
        return self.elements.popleft()

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

>>> from generic_queue import Queue

>>> string_queue = Queue[str]()
>>> string_queue.push("three")
>>> string_queue.push("thirteen")

>>> integer_queue = Queue[int]()
>>> integer_queue.push(3)
>>> integer_queue.push(13)

يمكنك إعلان أن string_queue ستكون queue تحتوي على عناصر سلسلة عن طريق إضافة [str] بين قوسين مربعين عند إنشاء المثيل. وعلى نحو مماثل، يحدد [int] أن integer_queue ستحتوي على عناصر عدد صحيح. إذا تركت الأقواس المربعة واستدعيت Queue() مباشرة، فيمكن أن تحتوي قائمة الانتظار الخاصة بك على عناصر من أي نوع.

يمكنك الآن تحديد نوع افتراضي لهذه الأنواع من الفئات العامة. سيتم افتراض هذا النوع الافتراضي عندما لا تحدد نوعًا صراحةً:

from collections import deque

class Queue[T=str]:
    def __init__(self) -> None:
        self.elements: deque[T] = deque()

    def push(self, element: T) -> None:
        self.elements.append(element)

    def pop(self) -> T:
        return self.elements.popleft()

string_queue = Queue()
reveal_type(string_queue)

integer_queue = Queue[int]()
reveal_type(integer_queue)

يمكنك تعريف النوع الافتراضي بنفس الطريقة التي تحدد بها وسيطات افتراضية للوظائف. يعني بناء الجملة [T=str] أنه إذا لم يتم تحديد نوع T، فسيكون str. لتأكيد ذلك، يمكنك استخدام الدالة الخاصة discover_type(). يتم فهم هذه الدالة بواسطة فاحص النوع الخاص بك، ولكنها ستفشل إذا قمت بتشغيل الكود الخاص بك:

$ pyright --pythonversion 3.13 generic_queue.py

generic_queue.py
  generic_queue.py:15:13 - information: Type of "string_queue" is "Queue[str]"
  generic_queue.py:18:13 - information: Type of "integer_queue" is "Queue[int]"
0 errors, 0 warnings, 2 informations

عندما تقوم بتشغيل Pyright على الكود الخاص بك، فإن discover_type() يؤكد أن string_queue هي قائمة انتظار من السلاسل، حتى لو لم تحددها صراحةً.

تعد الإعدادات الافتراضية للنوع إضافة صغيرة يمكنها جعل العمل مع الأنواع العامة أكثر ملاءمة.

قدم بايثون 3.10 حراس النوع. وهذا يسمح لك بتضييق أنواع الاتحاد. ضع في اعتبارك المثال التالي لهيكل شجرة محدد بشكل متكرر:

from typing import TypeGuard

type Tree = list[Tree | int]

def is_tree(obj: object) -> TypeGuard[Tree]:
    return isinstance(obj, list) and all(
        is_tree(elem) or isinstance(elem, int) for elem in obj
    )

def get_left_leaf_value(tree_or_leaf: Tree | int) -> int:
    if is_tree(tree_or_leaf):
        return get_left_leaf_value(tree_or_leaf[0])
    else:
        return tree_or_leaf

هنا، تعمل is_tree() كحارس نوع يمكنه التعرف على الأشجار. في الكود الخاص بك، Tree عبارة عن قائمة متداخلة من الأعداد الصحيحة. تأخذ دالة get_left_leaf_value() المتكررة إما Tree أو عددًا صحيحًا واحدًا. ومع ذلك، بعد التحقق من is_tree()، يمكنك الوصول بأمان إلى العنصر الأول من Tree على السطر 12.

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

في المثال أعلاه، هذا يعني أنه بالنسبة لمدقق النوع، tree_or_leaf على السطر 14 لا يزال من نوع Tree | int:

$ pyright --pythonversion 3.13 tree.py

tree.py
  tree.py:14:16 - error: Type "Tree | int" isn't assignable to return type "int"
    Type "Tree | int" is not assignable to type "int"
      "list[Tree | int]" is not assignable to "int" (reportReturnType)
1 error, 0 warnings, 0 informations

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

TypeIs مشابه لـ TypeGuard ولكنه يوفر مزيدًا من معلومات تضييق النوع لمدقق النوع، ويحل المشكلة في المثال أعلاه. في العديد من الحالات، يمكنك استخدام TypeIs كبديل مباشر لـ TypeGuard:

from typing import TypeIs

type Tree = list[Tree | int]

def is_tree(obj: object) -> TypeIs[Tree]:
    return isinstance(obj, list) and all(
        is_tree(elem) or isinstance(elem, int) for elem in obj
    )

def get_left_leaf_value(tree_or_leaf: Tree | int) -> int:
    if is_tree(tree_or_leaf):
        return get_left_leaf_value(tree_or_leaf[0])
    else:
        return tree_or_leaf

لقد استبدلت TypeGuard بـ TypeIs. وهذا يوفر مزيدًا من المعلومات لمدقق النوع. على وجه الخصوص، يعرف أن tree_or_leaf لا يمكن أن تكون Tree إذا أدخلت فرع else في السطر 13، لذا يجب أن يكون tree_or_leaf عددًا صحيحًا في هذه الحالة. يمكنك التأكد من ذلك بتشغيل Pyright:

$ pyright --pythonversion 3.13 tree.py

0 errors, 0 warnings, 0 informations

يمكنك قراءة المزيد حول تضييق النوع والفرق بين TypeGuard وTypeIs في مواصفات النوع.

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

ميزات أخرى رائعة جدًا

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

سطر أوامر عشوائي

يأتي بايثون مزودًا بالعديد من الأدوات المضمنة. ومن أشهر هذه الأدوات json.tool وhttp.server. يمكنك الوصول إلى هذه الأدوات من خلال مفتاح -m في بايثون:

$ python -m json.tool dog_friend.json
{
    "name": "Frieda",
    "age": 8
}

$ python -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

يمكنك استخدام json.tool لتنسيق ملفات JSON وجعلها أكثر ملاءمة للقراءة. باستخدام http.server، يمكنك تشغيل خادم HTTP محلي بسرعة.

يضيف بايثون 3.13 واجهة سطر أوامر إلى الوحدة النمطية العشوائية:

$ python -m random
usage: random.py [-h] [-c CHOICE [CHOICE ...] | -i N | -f N] [input ...]

positional arguments:
  input                 if no options given, output depends on the input
                            string or multiple: same as --choice
                            integer: same as --integer
                            float: same as --float

options:
  -h, --help            show this help message and exit
  -c, --choice CHOICE [CHOICE ...]
                        print a random choice
  -i, --integer N       print a random integer between 1 and N inclusive
  -f, --float N         print a random floating-point number between 0 and N

يمكنك إنشاء رقم عشوائي بسرعة أو إجراء اختيار عشوائي عن طريق استدعاء python -m random. بناءً على إدخالك، ستحصل على إحدى النتائج الثلاث التالية:

  • إذا قمت بتمرير قائمة من العناصر، فسوف تحصل على عنصر عشوائي واحد من القائمة.
  • إذا قمت بتمرير عدد صحيح N، فسوف تحصل على عدد صحيح عشوائي بين 1 وN.
  • إذا قمت بتمرير عدد عشري x، فسوف تحصل على عدد عشري عشوائي بين 0 وx.

جربها بنفسك:

$ python -m random Oslo Berlin London Krakow Belgrade Graz
Krakow

$ python -m random 6
3

$ python -m random 6.0
3.132801224231027

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

$ python -m random --float 6
1.4284991504686064

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

copy.replace() جديدة لتعديل الكائنات غير القابلة للتغيير

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

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

فكر في المثال التالي، الذي يقوم بنمذجة شخص وإصدار بايثون الحالي الخاص به:

>>> from typing import NamedTuple

>>> class Person(NamedTuple):
...     name: str
...     place: str
...     version: str
...

>>> person = Person(name="Geir Arne", place="Oslo", version="3.12")

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

>>> person = Person(name=person.name, place=person.place, version="3.13")
>>> person
Person(name='Geir Arne', place='Oslo', version='3.13')

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

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

>>> from datetime import date

>>> today = date.today()
>>> today
datetime.date(2024, 9, 30)

>>> today.replace(day=1)
datetime.date(2024, 9, 1)

>>> today.replace(month=12, day=24)
datetime.date(2024, 12, 24)

تستخدم .replace() لحساب اليوم الأول من الشهر الحالي، أو عشية عيد الميلاد هذا العام.

في بايثون 3.13، تمت إضافة دالة replace() جديدة إلى وحدة copy لتوفير نفس الوظيفة باستمرار للعديد من هياكل البيانات غير القابلة للتغيير. يمكنك استخدام copy.replace() لإعادة إنشاء الأمثلة أعلاه:

>>> import copy

>>> person = Person(name="Geir Arne", place="Oslo", version="3.12")
>>> copy.replace(person, version="3.13")
Person(name='Geir Arne', place='Oslo', version='3.13')

>>> copy.replace(today, day=1)
datetime.date(2024, 9, 1)

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

>>> person
Person(name='Geir Arne', place='Oslo', version='3.12')

يمكنك استخدام replace() لتعديل فئات البيانات بالإضافة إلى المجموعات المسماة. يمكنك أيضًا إضافة تابع خاص  .__replace__() إذا كنت تريد أن تعمل replace() مع فئتك المخصصة. ستجد مثالاً لكيفية عمل ذلك في المواد القابلة للتنزيل.

تحسين تجميع الملفات والدلائل

تحتوي وحدة pathlib في بايثون على العديد من الأدوات للعمل مع المسارات والملفات بشكل متسق عبر أنظمة التشغيل. على سبيل المثال، يمكنك سرد الملفات والدلائل باستخدام .glob():

>>> from pathlib import Path

>>> Path("music")
PosixPath('music')

>>> for path in Path("music").glob("*"):
...     print(path)
...
music/opera
music/rap

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

يتستخدم التابع .glob() أنماط glob خاصة تم استخدامها لتمثيل مجموعات من أسماء الملفات منذ سبعينيات القرن العشرين. يمكنك استخدام أحرف بديلة خاصة مثل * و؟ حيث يتطابق * مع أي عدد من أي أحرف، بينما يتطابق؟ مع أي حرف واحد.

على سبيل المثال، يتطابق *.py مع أي اسم ملف ينتهي بـ .py. وبالمثل، يتطابق python3.1? مع أسماء مثل python3.12 وpython3.13 وpython3.1X، ولكن ليس python3.1 أو python3.100.

هناك نمط خاص وهو globstar (**) والذي يتوفر في العديد من واجهات الطرفية. وهو يطابق جميع الملفات والدلائل بشكل متكرر. يمكنك استكشاف music/ في Bash باستخدام ls:

$ ls music/**
music/opera:
flower_duet.txt  habanera.txt  nabucco.txt

music/rap:
bedlam_13-13.txt  fight_the_power.txt

باستخدام ls music/**، يمكنك سرد جميع الدلائل والملفات الموجودة داخل music/. ورغم أن Python يدعم **، إلا أن سلوكه غير متسق مع الأدوات الأخرى. في Python 3.12، يمكنك ملاحظة ما يلي:

>>> for path in Path("music").glob("**"):
...     print(path)
...
music
music/opera
music/rap

>>> for path in Path("music").glob("**/*"):
...     print(path)
...
music/opera
music/rap
music/opera/nabucco.txt
music/opera/flower_duet.txt
music/opera/habanera.txt
music/rap/bedlam_13-13.txt
music/rap/fight_the_power.txt

يؤدي استخدام globstar إلى عرض الدلائل فقط. إذا كنت تريد عرض الدلائل والملفات، فيتعين عليك استخدام النمط الممتد  **/*.

في بايثون 3.13، تم تغيير سلوك ** ليصبح متوافقًا مع الأدوات التقليدية:

>>> for path in Path("music").glob("**"):
...     print(path)
...
music
music/opera
music/rap
music/rap/bedlam_13-13.txt
music/rap/fight_the_power.txt
music/opera/nabucco.txt
music/opera/flower_duet.txt
music/opera/habanera.txt

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

>>> for path in Path("music").glob("**/"):
...     print(path)
...
music
music/opera
music/rap

أضف شرطة مائلة (/) في نهاية النمط الخاص بك لجعل .glob() يقوم بإرجاع الدلائل فقط.

عادةً، ستستخدم .glob() من خلال pathlib. ولكن الوظيفة متاحة أيضًا في وحدة glob. يضيف بايثون 3.13 دالة جديدة، glob.translate() يمكنك استخدامها لتحويل نمط glob إلى تعبير عادي (regex).

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

>>> import glob

>>> pattern = glob.translate("music/**/*.txt")
>>> pattern
'(?s:music/(?!\\.)[^/]*/(?!\\.)[^/]*\\.txt)\\Z'

يمكنك ترجمة نمط glob الذي يتطابق مع أي ملف .txt متداخل داخل music/ إلى تعبير عادي. يمكنك بعد ذلك مطابقة نمط التعبير العادي مع أي سلسلة باستخدام re.match():

>>> import re

>>> re.match(pattern, "music/opera/flower_duet.txt")
<re.Match object; span=(0, 27), match='music/opera/flower_duet.txt'>

>>> re.match(pattern, "music/progressive_rock/")

>>> re.match(pattern, "music/progressive_rock/fandango.txt")
<re.Match object; span=(0, 35), match='music/progressive_rock/fandango.txt'>

في هذا المثال، يتطابق مسارا الملفين لديك مع النمط، بينما لا يتطابق مسار الدليل.

يستخدم Pathlib الآن glob.translate() داخليًا للعديد من العمليات. يأتي هذا مع زيادة صغيرة في الأداء حيث يمكن التنقل بين الدلائل بشكل أسرع ويمكن أن تكون كائنات Path أصغر حجمًا.

إذن، هل يجب عليك الترقية إلى بايثون 3.13؟

لقد رأيت العديد من الميزات الجديدة في بايثون 3.13. وهناك العديد من الميزات الأخرى التي يمكنك قراءتها في سجل التغييرات الخاص بـ بايثون. تتضمن الميزات المحسنة التي لم يتم تناولها في هذا الدرس ما يلي:

  • يدعم Argparse إلغاء استخدام الخيارات والحجج والأوامر الفرعية في تطبيقات سطر الأوامر.
  • تم تعريف الدلالات لـ locals() لضمان المزيد من الاتساق عند إلقاء نظرة خاطفة على مساحة الأسماء المحلية.
  • تمت إزالة البطاريات الميتة التي تم إيقاف استخدامها في Python 3.11.
  • أصبح الآن نظامي التشغيل iOS وAndroid مدعومين للغة بايثون على مستوى الطبقة 3.

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

لذا، بعد رؤية كل هذه التحسينات الجديدة، هل يجب عليك الترقية إلى Python 3.13؟ كما هو الحال مع كل إصدار، الإجابة واضحة وصريحة، الأمر يعتمد على كل حالة.

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

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

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

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


اكتشاف المزيد من بايثون العربي

اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

Scroll to Top

اكتشاف المزيد من بايثون العربي

اشترك الآن للاستمرار في القراءة والحصول على حق الوصول إلى الأرشيف الكامل.

Continue reading