بايثون بعد الأساسيات 8: شرح Type Hints في Python للمبتدئين

شرح Type Hints في Python للمبتدئين وتحديد أنواع المتغيرات والدوال

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

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

في هذا الدرس الثامن من سلسلة بايثون بعد الأساسيات سنتعلم Type Hints خطوة بخطوة، من str وint إلى القوائم والقواميس والقيم الاختيارية وAny وCallable، ثم سنفحص مشروعًا صغيرًا باستخدام mypy.

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

{getToc} $title={محتوى المقال}

ما هي Type Hints في Python؟

Type Hints هي annotations نضيفها إلى الكود لتوضيح أنواع البيانات المتوقعة.

بدون Type Hints:

name = "Ali"
age = 25

مع Type Hints:

name: str = "Ali"
age: int = 25

الجزء : str يعني أن المتغير name متوقع أن يحتوي على نص، و: int يعني أن age متوقع أن يكون عددًا صحيحًا.

هل Type Hints إجبارية في Python؟

لا. Python لغة ديناميكية الأنواع، ويمكنك كتابة البرامج بدون Type Hints.

بل إن هذا الكود يمكن أن يعمل أثناء التشغيل العادي:

age: int = "twenty five"

print(age)

الناتج:

twenty five

كتابة int لم تمنع Python من تخزين النص. لكن أداة فحص ثابت مثل mypy تستطيع تنبيهك إلى أن القيمة لا تطابق النوع المتوقع.

{alertWarning} مهم: لا تعتمد على Type Hints كتحقق من بيانات المستخدم وقت التشغيل. إذا كان البرنامج يستقبل مدخلات خارجية، تحتاج إلى validation حقيقي بجانب annotations.

أشهر الأنواع الأساسية

username: str = "Sara"
age: int = 24
price: float = 19.99
is_active: bool = True
النوع ماذا يمثل؟ مثال
strالنصوص"Ali"
intالأعداد الصحيحة25
floatالأعداد العشرية3.14
boolالقيم المنطقيةTrue

Type Hints في معاملات الدوال

من أهم استخدامات Type Hints توضيح أنواع معاملات الدوال.

بدون الأنواع:

def add(a, b):
    return a + b

نسخة أوضح:

def add(a: int, b: int):
    return a + b

تحديد نوع القيمة المرجعة

def add(a: int, b: int) -> int:
    return a + b

دالة تعيد نصًا

def greet(name: str) -> str:
    return f"مرحبًا {name}"


message: str = greet("Ali")

print(message)

دالة لا تعيد قيمة

def show_message(message: str) -> None:
    print(message)
تحديد أنواع المتغيرات ومعاملات الدوال والقيمة المرجعة باستخدام Type Hints

Type Hints للقوائم باستخدام list

names: list[str] = [
    "Ali",
    "Sara",
    "Omar"
]
scores: list[int] = [
    90,
    85,
    78
]

دالة تستقبل قائمة

def average(numbers: list[float]) -> float:
    return sum(numbers) / len(numbers)


result: float = average(
    [10.0, 20.0, 30.0]
)

print(result)

Type Hints للقواميس باستخدام dict

scores: dict[str, int] = {
    "Ali": 90,
    "Sara": 95,
    "Omar": 82
}

tuple و set

coordinates: tuple[float, float] = (
    12.5,
    44.2
)

tags: set[str] = {
    "python",
    "typing"
}

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

في Python 3.10+ تستطيع استخدام العامل | لكتابة Union بشكل مختصر.

user_id: int | str

user_id = 1001
user_id = "U-1001"

الصيغة باستخدام Union

from typing import Union

user_id: Union[int, str] = 1001

القيم الاختيارية: str أو None

def find_username(user_id: int) -> str | None:
    if user_id == 1:
        return "Ali"

    return None

Optional في المشاريع الأقدم

from typing import Optional


def find_username(user_id: int) -> Optional[str]:
    if user_id == 1:
        return "Ali"

    return None
شرح list و dict و Union و Optional في Type Hints ببايثون

فحص None قبل استخدام النتيجة

username = find_username(99)

if username is not None:
    print(username.upper())
else:
    print("المستخدم غير موجود")

ما هو Any؟

from typing import Any

value: Any = 10
value = "Hello"
value = [1, 2, 3]

استخدم Any عندما تكون المرونة مقصودة فعلًا، ولا تجعلها النوع الافتراضي في كل مكان لأن ذلك يقلل فائدة الفحص.

Type Hints للدوال باستخدام Callable

from typing import Callable


def double(number: int) -> int:
    return number * 2


def apply_operation(
    number: int,
    operation: Callable[[int], int]
) -> int:
    return operation(number)


result = apply_operation(
    5,
    double
)

print(result)

Alias لنوع طويل

UserRecord = dict[str, str | int]


def show_user(user: UserRecord) -> None:
    print(user)

Type Hints لا تعني Runtime Validation

def set_age(age: int) -> None:
    print(f"العمر: {age}")


set_age("twenty")

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

def set_age(age: int) -> None:
    if not isinstance(age, int):
        raise TypeError("age يجب أن يكون int")

    print(f"العمر: {age}")

ما هو mypy؟

mypy أداة static type checker تقرأ Type Hints وتحلل الكود للبحث عن استخدامات غير متوافقة مع الأنواع.

أنشئ ملفًا باسم app.py:

def add(a: int, b: int) -> int:
    return a + b


result = add("5", 3)

print(result)

تثبيت mypy

python -m pip install mypy

فحص ملف Python باستخدام mypy

mypy app.py

صحح الاستدعاء:

result = add(5, 3)
استخدام mypy لفحص Type Hints واكتشاف أخطاء الأنواع في بايثون

مثال يكشفه mypy قبل التشغيل

def get_total(prices: list[float]) -> float:
    return sum(prices)


prices: list[str] = [
    "10.5",
    "20.0"
]

total = get_total(prices)

هل يجب كتابة Type Hints لكل متغير؟

لا. أدوات التحليل تستطيع غالبًا استنتاج أنواع بسيطة مثل:

name = "Ali"
age = 25

ركز على واجهات الدوال والقيم المرجعة والأنواع المركبة والقيم التي قد تكون أكثر من نوع.

مثال مشروع صغير مع Type Hints

User = dict[str, str | int]


users: list[User] = [
    {"name": "Ali", "age": 25},
    {"name": "Sara", "age": 22}
]


def find_user(
    users: list[User],
    name: str
) -> User | None:
    for user in users:
        if user["name"] == name:
            return user

    return None

استخدام النتيجة بطريقة آمنة

user = find_user(
    users,
    "Ali"
)

if user is not None:
    print(user["age"])
else:
    print("المستخدم غير موجود")

Type Hints مع class

class User:
    def __init__(
        self,
        name: str,
        age: int
    ) -> None:
        self.name = name
        self.age = age


def show_user(user: User) -> None:
    print(user.name, user.age)

أخطاء شائعة عند استخدام Type Hints

1. الاعتقاد أن Python ستمنع النوع الخطأ

Type Hints ليست validation تلقائيًا. إذا كنت تحتاج تحققًا أثناء التشغيل، نفّذه بصورة مستقلة.

2. استخدام Any لكل شيء

هذا يفقدك جزءًا كبيرًا من فوائد static typing.

3. كتابة أنواع معقدة بلا حاجة

اكتب النوع الأبسط الذي يصف العقد بوضوح. لا تجعل annotation أصعب من الكود نفسه.

4. تجاهل احتمال None

إذا كانت الدالة قد تعيد None، اكتب ذلك في النوع وتحقق منه قبل الاستخدام.

5. إضافة Type Hints دون تشغيل checker

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

أخطاء شائعة عند استخدام Type Hints في بايثون

متى تكون Type Hints مهمة جدًا؟

  • عندما يكون المشروع موزعًا على عدة ملفات.
  • عندما تعمل مع فريق.
  • عندما توجد دوال كثيرة تتبادل بيانات مركبة.
  • عندما تعيد الدالة أكثر من نوع محتمل.
  • عندما تبني مكتبة أو API يستخدمها الآخرون.
  • عندما تريد إعادة هيكلة الكود بثقة أكبر.
  • عندما تريد أن يساعدك المحرر في autocomplete والتنبيهات.

قائمة فحص عملية

السؤالمثال
هل معاملات الدالة موضحة؟name: str
هل القيمة المرجعة موضحة؟-> int
هل نوع عناصر القائمة معروف؟list[str]
هل القاموس يوضح المفتاح والقيمة؟dict[str, int]
هل قد تعود None؟str | None
هل القيمة قد تكون أكثر من نوع؟int | str
هل تحتاج Any فعلًا؟Any
هل شغلت type checker؟mypy app.py

تمرين عملي

لديك الكود التالي بدون Type Hints:

def calculate_discount(price, discount):
    return price - (
        price * discount / 100
    )


def format_product(name, price, tags):
    return {
        "name": name,
        "price": price,
        "tags": tags
    }

حل مقترح

Product = dict[
    str,
    str | float | list[str]
]


def calculate_discount(
    price: float,
    discount: float
) -> float:
    return price - (
        price * discount / 100
    )


def format_product(
    name: str,
    price: float,
    tags: list[str]
) -> Product:
    return {
        "name": name,
        "price": price,
        "tags": tags
    }

الخطوة التالية بعد Type Hints

  • TypedDict لوصف قواميس ذات مفاتيح محددة.
  • Literal عندما تريد قيمًا محددة.
  • Protocol لوصف واجهات تعتمد على السلوك.
  • TypeVar والـ Generics.
  • إعدادات mypy الأكثر صرامة.

روابط داخلية مفيدة من بايثون العرب

مصادر خارجية رسمية للتوسع

الخلاصة

Type Hints تضيف طبقة من الوضوح إلى كود Python بدون أن تغير طبيعة اللغة الديناميكية. باستخدامها تستطيع توضيح أنواع معاملات الدوال والقيم المرجعة والقوائم والقواميس، والتعبير عن القيم التي قد تكون أكثر من نوع أو قد تساوي None.

تعلمنا استخدام str وint وfloat وbool، ثم list[str] وdict[str, int] وint | str وstr | None، كما شرحنا Any وCallable.

والأهم أننا فصلنا بين annotations والتحقق أثناء التشغيل، واستخدمنا mypy كأداة تفحص الكود وتساعد في اكتشاف استخدام أنواع غير متوافقة.

{alertSuccess} القاعدة المهمة: اكتب Type Hints لتوضيح عقود الدوال والبيانات المهمة، ولا تحاول وضع annotation في كل سطر بلا حاجة. ابدأ بالواجهات والأنواع المركبة، ثم استخدم type checker للحصول على الفائدة الكاملة.

أسئلة شائعة

ما هي Type Hints في Python؟

هي annotations تكتب داخل الكود لتوضيح أنواع البيانات المتوقعة للمتغيرات ومعاملات الدوال والقيم المرجعة، وتستفيد منها أدوات الفحص والمحررات.

هل Type Hints تمنع تمرير نوع خاطئ؟

لا. Python لا تفرض annotations تلقائيًا أثناء التشغيل العادي. يمكنك استخدام static type checker مثل mypy لاكتشاف كثير من هذه الأخطاء.

كيف أحدد نوع القيمة المرجعة من دالة؟

استخدم ->، مثل def add(a: int, b: int) -> int:.

كيف أكتب قائمة نصوص في Type Hints؟

في Python الحديثة استخدم list[str].

كيف أكتب قاموسًا مفاتيحه نصوص وقيمه أعداد؟

استخدم dict[str, int].

ما معنى str | None؟

يعني أن القيمة قد تكون نصًا من نوع str أو قد تكون None. هذه الصيغة الحديثة متاحة في Python 3.10+.

ما الفرق بين Optional[str] و str | None؟

في هذا الاستخدام يعبران عن المعنى نفسه: قيمة قد تكون str أو None. صيغة | أبسط في Python 3.10+.

متى أستخدم Any؟

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

ما هو mypy؟

هو static type checker لبايثون يحلل Type Hints وينبهك إلى استخدام أنواع غير متوافقة.

هل Type Hints مهمة في المشاريع الصغيرة؟

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

إرسال تعليق

أحدث أقدم