اذهب إلى المحتوى

تلميحات النوع Type Hints في بايثون


Naser Dakhel

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

ما هي تلميحات النوع؟

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

على الجانب الآخر، هنالك لغات برمجة -ومنها لغة بايثون- متساهلة في تحديد أنواع البيانات وتُسمى متغيرة أو ديناميكية الأنواع dynamic typing أي يُمكن أن تكون المتغيرات والمُعاملات والقيم المُعادة من أي نوع من البيانات ويمكن أن تغير نوعها عند تنفيذ البرنامج. تكون اللغات الديناميكية غالبًا أكثر سهولة للبرمجة عليها لأنها لا تحتاج كثيرًا إلى تحديد صريح لأنواع البيانات، إلا أنها تفتقد ميزات منع الأخطاء التي تكون موجودة في اللغات ثابتة الأنواع. عندما تكتب سطرًا من شيفرة بايثون مثل round('forty two')‎ لا تلاحظ أنك تمرر سلسلة نصية إلى دالة تقبل نوع البيانات عدد صحيح int أو عدد عشري float فقط حتى تُنفذ الشيفرة وتسبب خطأ، بينما تعطي اللغات ثابتة الأنواع تحذيرًا مسبقًا إذا أردت تعيين قيمة أو تمرير وسيط من النوع الخاطئ.

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

def describeNumber(number: int) -> str:
    if number % 2 == 1:
        return 'An odd number. '
    elif number == 42:
        return 'The answer. '
    else:
        return 'Yes, that is a number. '

myLuckyNumber: int = 42
print(describeNumber(myLuckyNumber))

تستخدم تلميحات الأنواع النقطتين في المُعاملات والمتغيرات لفصل الاسم عن النوع ولكن بالنسبة للقيم المُرجعة يستخدم تلميح النوع السهم (‎->‎) للفصل بين أقواس تغليف تعبير def عن النوع. يلمّح نوع الدلة describeNumber()‎ أنها تأخذ قيمة عدد صحيح من أجل مُعامل number وتعيد قيمة سلسلة نصية.

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

لاحظ في المثال السابق أن أسماء الأنواع المحددة تطابق أسماء الدوال البانية int()‎ و str()‎، للصنف والنوع ونوع البيانات المعنى ذاته في بايثون، إذ تجد في أي نسخة مكونة من أصناف أن اسم الصنف مماثلٌ لاسم النوع:

import datetime
1 noon: datetime.time = datetime.time(12, 0, 0)

class CatTail:
    def __init__(self, length: int, color: str) -> None:
        self.length = length
        self.color = color

2 zophieTail: CatTail = CatTail(29, 'grey')

لدى المتغير noon تلميح النوع datetime.time (سطر 1) لأنه كائن time (المعرف في وحدة datetime)، وللكائن zophieTail أيضًا نوع التلميح CatTail (سطر 2)، وذلك لأنه كائن لصنف CatTail الذي أنشأناه بتعليمة class. تُطبَّق تلميحات الأنواع تلقائيًا لكل الأصناف الفرعية للنوع المحدد، فمثلًا يمكن ضبط متغير تلميح النوع dict لأي قيمة مسار وأيضًا لأي قيم collection.OrderedDict و collection.defaultdict لأن هذه الأصناف هي أصناف فرعية للصنف dict. سنتكلم لاحقًا عن الأصناف الفرعية بتفصيل أكبر.

لا تحتاج عادةً أدوات تحقق النوع الساكنة إلى تلميحات أنواع للمتغيرات، لأن أدوات التحقق من أنواع البيانات تستدلّ على النوع type inference من تعبير الإسناد الأول للمتغير. على سبيل المثال، يمكن لمتحقق النوع الاستدلال بأن spam في السطر spam = 42 يجب أن يحتوي تلميح نوع int، إلا أنه يُفضل ضبط تلميح نوع بكل الأحوال. أي تغيير مستقبلي للنوع float كما في spam = 42.0 سيسبب بتغيير في النوع المُستدلّ مما قد لا يكون متوافقًا مع رغبتك، إذ يُفضّل إجبار المبرمج ليغير تلميح النوع عند تغيير القيمة للتأكد أنه غيّر النوع متقصدًا بدلًا من تغيير عَرَضي خاطئ.

استخدام محللات صارمة مع أنواع البيانات

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

نسمي هذه الأدوات بأدوات التحليل الثابتة static analyzers tools، لأنها تحلل الشيفرة المصدرية قبل تنفيذ البرنامج، بينما تحلّل أدوات التحليل وقت التنفيذ runtime analysis tools أو أدوات التحليل الديناميكي dynamic analysis tools البرامج وقت التنفيذ. قد تبدو لك الأمور مثيرة للحيرة الآن، إذ تشير الكلمتين ساكن وديناميكي إلى تنفيذ البرنامج بينما تشير الكتابة ثابتة الأنواع static typing ومتغيرة الأنواع dynamic typing إلى كيفية التصريح عن أنواع البيانات للمتغيرات والدوال. بايثون لغةٌ تُكتب ديناميكيًا ولديها أدوات تحليل ساكنة مثل Mypy مكتوبةٌ لأجلها.

تثبيت وتشغيل Mypy

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

python -m pip install -user mypy 

شغل python3 بدلًا من python على ماك أو إس macOS ولينكس Linux. تتضمن أدوات التحقق من النوع الأُخرى المعروفة Pyright الخاص بمايكروسوفت و Pyre الخاص بفيسبوك و Pytype الخاص بجوجل.

لتشغيل متحقق النوع، افتح سطر الأوامر أو نافذة طرفية ونفّذ أمر python -m mypy لتنفيذ الوحدة مثل تطبيق، ومرّر اسم ملف شيفرة بايثون ليتحقق منه. نتحقق في هذا المثال من الشيفرة لبرنامج مثال أُنشئ في ملف اسمه example.py:

C:\Users\Al\Desktop>python –m mypy example.py
Incompatible types in assignment (expression has type "float", variable has type "int")
Found 1 error in 1 file (checked 1 source file)

لا يخرِج متحقق النوع شيئًا إذا لم توجد أي مشكلة ويطبع رسائل خطأ عدا ذلك، ففي هذا المثال هناك مشكلة في ملف example.py في السطر 171 لأن المتغير المُسمى spam لديه تلميح نوع int ولكن تُسند إليه قيمة float، وقد يسبب هذا فشلًا ويجب التحقق منه. قد تكون بعض رسائل الخطأ صعبة الفهم للوهلة الأولى، ويمكن أن يعطي Mypy عددًا كبيرًا من الأخطاء المحتملة أكثر مما نستطيع ذكره هنا. أسهل طريقة لمعرفة ماذا يعني كل خطأ هو البحث عنه على الويب، ويمكنك البحث عن شيء شبيه بما يلي: " أنواع الإسناد غير المتوافقة في Mypy" أو Mypy incompatible types in assignment.

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

sublime-text-errors.png

[الشكل 1: إظهار محرر النصوص Sublime Text للأخطاء من Mypy]

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

إعلام Mypy بتجاهل الشيفرة

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

def removeThreesAndFives(number: int) -> int:
    number = str(number)  # type: ignore
    number = number.replace('3', '').replace('5', '')  # type: ignore
    return int(number)

يمكننا ضبط متغير العدد الصحيح إلى سلسلة نصية مؤقتًا لإزالة كل أرقام 3 و5 من العدد الصحيح المُمرر إلى removeThreesAndFives()‎، يتسبب ذلك بتحذير متحقق النوع من أول سطرين من الدالة لذا سنضيف تلميح النوع ‎# ‎type: ignore لهذه الأسطر لتجاهل تحذيرات متحقق النوع.

استخدم ‎# type: ignore باعتدال، إذ يفتح تجاهل التحذيرات من متحقق النوع المجال للأخطاء بأن تتسلل إلى الشيفرة الخاصة بك، ويمكنك دائمًا إعادة كتابة الشيفرة الخاصة بك حتى لا تظهر هذه التحذيرات، فمثلًا إذا أنشأنا متغيرًا باستخدام numberAsStr = str(number)‎ أو بدلنا الأسطر الثلاثة باستخدام سطر شيفرة واحد كما يلي:

 return int(str(number.replace('3', '').replace('5', '')))‎

يمكننا تجنب استخدام المتغير number في عدة أنواع. لا نريد هنا تجاهل التحذيرات عن طريق تغيير تلميح النوع للمُعامل إلى union[int, str]‎ لأن هدف المعامل هو السماح بالأعداد الصحيحة فقط.

ضبط تلميحات النوع لأنواع متعددة

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

from typing import Union
spam: Union[int, str, float] = 42
spam = 'hello'
spam = 3.14

في هذا المثال، يحدد تلميح النوع Union[int, str, float]‎ أنه يمكن ضبط spam إلى عدد صحيح أو سلسلة نصية أو رقم عشري. لاحظ أنه من الأفضل استخدام الشكل from typing import x من تعبير import بدلًا من الشكل import typing ومن ثم استخدام الكتابة المطولة باستمرار من أجل تلميحات النوع في كل البرنامج.

يمكن تحديد أنواع بيانات متعددة في المواقف التي يمكن أن تعيد فيها المتغيرات أو القيم المُعادة قيمة None بالإضافة إلى نوع آخر. ضع None داخل أقواس معقوفة بدلًا من NoneType لإضافة NoneType الذي هو نوع القيمة None في تلميح النوع. تقنيًا، ليس NoneType معرّفًا مبنيًا مسبقًا built-in identifier كما هو الحال مع int أو str.

الأفضل من ذلك، بدلًا من استخدام Union[str, None]‎ على سبيل المثال، يمكنك استيراد Optional من الوحدة Optional[str]‎؛ يعني تلميح النوع هذا أن التابع أو الدالة قد تُعيدان None بدلًا من القيمة للنوع المتوقع، إليك مثالًا عن ذلك:

from typing import Optional
lastName: Optional[str] = None
lastName = 'Sweigart'

يمكن ضبط المتغير lastName في هذا المثال إلى قيمة None أو str، ولكن من الأفضل التقليل من استخدام Union و Optimal، إذ كلما قل عدد الأنواع التي تسمح بها المتغيرات والدوال كلما كانت الشيفرة أبسط، والشيفرة البسيطة أقل عرضةً للأخطاء من الشيفرة المعقدة. تذكر الحكمة الفضلى في بايثون أن البسيط أفضل من المعقد، فمن أجل الدوال التي تُعيد None للإشارة إلى خطأ ما، جرّب استخدام استثناء بدلًا من ذلك.

يمكنك استخدام تلميح النوع Any (أيضًا من وحدة typing) لتحديد أن المتغير أو المعامل أو القيمة المُعادة يمكن أن تكون من أي نوع بيانات:

from typing import Any
import datetime
spam: Any = 42
spam = datetime.date.today()
spam = True

يسمح تلميح النوع Any في هذا المثال بضبط المتغير spam إلى قيمة من أي نوع من البيانات، مثل int أو datetime.date أو bool، كما يمكنك أيضًا استخدام object مثل تلميح نوع لأن هذا هو الصنف الأساسي لكل أنواع البيانات في بايثون، ولكن Any هو تلميح مفهوم أكثر من object.

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

ضبط تلميحات النوع لكل من القوائم والقواميس وغيرها

يمكن للقوائم lists والقواميس dictionaries والصفوف tuples والمجموعات sets وحاويات البيانات الأخرى أن تحتفظ بقيم أخرى، فإذا حددت list على أنها تلميح النوع للمتغير، يجب على هذا المتغير أن يحتوي على قائمة، إلا أنه من الممكن أن تحتوي تلك القائمة على قيمة من أي نوع. لا تسبب الشيفرة التالية أي اعتراضات من متحقق النوع:

spam: list = [42, 'hello', 3.14, True]

يجب استخدام تلميح النوع List الخاص بالوحدة typing للتصريح بالتحديد عن أنواع البيانات داخل القائمة. لاحظ أن List مكتوبةٌ بحرف كبير "L" لتمييزها عن نوع البيانات list:

from typing import List, Union
1 catNames: List[str] = ['Zophie', 'Simon', 'Pooka', 'Theodore']
2 numbers: List[Union[int, float]] = [42, 3.14, 99.9, 86]

يحتوي المتغير catNames في هذا المثال على قائمة من السلاسل النصية، لذا نضبط بعد استيراد List من الوحدة typing تلميح النوع إلى List[str]‎ (السطر 1)، وينظر متحقق النوع إلى أي استدعاء للتابعين append()‎ أو insert()‎ أو أي شيفرة أخرى تضع قيمة غير السلسلة النصية في القائمة. يمكننا ضبط تلميح النوع باستخدام Union إذا احتوت القائمة على أنواع متعددة، إذ يمكن مثلًا للقائمة numbers أن تحتوي على قيم ذات عدد صحيح أو عدد عشري، لذا نضبط تلميح النوع إلى List[Union[int, float]]‎ (السطر 2).

لدى وحدة typing اسم بديل آخر لكل نوع حاوية، إليك قائمةً بأسماء الأنواع البديلة لأنواع الحاويات المعروفة في بايثون:

  • List هي لنوع البيانات list.
  • Tuple هي لنوع البيانات tuple.
  • Dict هي لنوع البيانات القاموس dict.
  • Set هي لنوع البيانات set.
  • FrozenSet هي لنوع البيانات frozenset.
  • Sequence هي لنوع البيانات list و tuple وأي نوع آخر من أنواع البيانات المتسلسلة.
  • Mapping هي لنوع البيانات القاموس dict و set و frozenset وأي نوع آخر من أنواع بيانات الربط.
  • ByteString هي لأنواع bytes و bytearray و memoryview.

إليك قائمة كاملة للأنواع.

النقل العكسي لتلميحات النوع باستخدام التعليقات

النقل العكسي Backporting هي عملية أخذ الميزات من إصدار جديد من البرنامج ونقلها (أي تكييفها وإضافتها) إلى نسخة أقدم. ميزة تلميحات النوع لبايثون جديدةٌ للإصدار 3.5 ولكن يمكن لشيفرة بايثون التي تُنفذ بإصدار مفسر أقدم من 3.5 استخدام تلميحات النوع بوضع معلومات النوع في التعليقات. استخدم التعليق السطري بعد تعبير الإسناد من أجل المتغيرات، ومن أجل الدوال والتوابع اكتب تلميح النوع على السطر الذي يلي تعبير def. ابدأ بالتعليق باستخدام type متبوعًا بنوع البيانات. إليك مثالًا عن شيفرة تحتوي على تلميحات النوع في التعليقات:

1 from typing import List

2 spam = 42  # type: int
def sayHello():
   3 # type: () -> None
    """The docstring comes after the type hint comment."""
    print('Hello!')

def addTwoNumbers(listOfNumbers, doubleTheSum):
   4 # type: (List[float], bool) -> float
    total = listOfNumbers[0] + listOfNumbers[1]
    if doubleTheSum:
        total *= 2
    return total

لاحظ أنه حتى مع استخدامك لتنسيق التعليق الخاص بتلميح النوه فأنت لا تزال بحاجة لاستيراد الوحدة typing (سطر 1)، إضافةً لأي نوع اسم بديل تستخدمه في التعليقات. لا تحتوي الإصدارات الأقدم من 3.5 وحدة typing في مكتبتها الافتراضية، لذا عليك تثبيت typing بصورةٍ منفصلة عن طريق تنفيذ هذا الأمر:

python -m pip install --user typing 

شغل python3 بدلًا من python في ماك أو إس ولينكس.

لضبط المتغير spam إلى عدد صحيح نضيف ‎# ‎type: int مثل تعليق بنهاية السطر (سطر 2). من أجل الدوال يجب أن يضم التعليق قوسين مع فاصلة تفصل قتئمة تلميح الأنواع بنفس ترتيب المعاملات. يجب أن يكون للدوال التي تحتوي على صفر معامل قوسين فارغين (سطر 3)، كما يجب الفصل بين المعاملات المتعددة إذا وجدت داخل القوسين بفواصل (سطر 4).

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

ترجمة -وبتصرف- لقسم من الفصل COMMENTS, DOCSTRINGS, AND TYPE HINTS من كتاب Beyond the Basic Stuff with Python.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...