Kodax

Type Hints — Python المتقدم

تلميحات الأنواع (Type Hints)

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

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

أهم حقيقة

Python لا تتحقق من التلميحات أثناء التشغيل. average("abc") ستُنفَّذ وتفشل كالعادة. الفائدة تأتي من:

- المحرر (VS Code / PyCharm): إكمال تلقائي وتحذيرات قبل التشغيل.

- أدوات الفحص مثل mypy أو pyright التي تقرأ الكود وتكتشف التعارضات.

- القراءة: من يقرأ الدالة يعرف ما تتوقعه دون فتح جسمها.

الصيغة الحديثة (Python 3.9 و 3.10+)

| الحاجة | الحديث | القديم (من typing) |

|---|---|---|

| قائمة أرقام | list[int] | List[int] |

| قاموس | dict[str, int] | Dict[str, int] |

| tuple ثابتة | tuple[int, int] | Tuple[int, int] |

| قيمة أو None | str | None | Optional[str] |

| أحد نوعين | int | str | Union[int, str] |

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

None صراحة

def find_user(user_id: int) -> str | None:
    ...

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

الدوال كمعاملات

from collections.abc import Callable
def apply(func: Callable[[int], int], value: int) -> int:
    return func(value)

أنواع مفيدة أخرى

- Literal["asc", "desc"]: قيم محددة فقط.

- TypedDict: شكل قاموس بمفاتيح معروفة (مفيد لبيانات JSON).

- الأسماء المستعارة: type UserId = int (Python 3.12+) أو UserId = int.

- الأنواع العامة (Generics): class Stack[T]: في Python 3.12+، أو TypeVar في الإصدارات الأقدم.

مع الأصناف و dataclasses

dataclass تعتمد على التلميحات لتعريف الحقول، والمحررات تفهمها جيداً.

كم تكتب؟

ابدأ بالدوال العامة (المعاملات والقيم المرجعة). لا داعي لتلميح كل متغير محلي؛ أدوات الفحص تستنتج أنواعها.

تمرين

أضف تلميحات أنواع كاملة لدوال مشروع دفتر الدرجات، وشغّل mypy عليه إن استطعت تثبيته (pip install mypy)، ولاحظ ما يكتشفه.

from collections.abc import Callable, Iterable
from dataclasses import dataclass
from typing import Literal, TypedDict

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

def find_user(user_id: int, users: dict[int, str]) -> str | None:
    return users.get(user_id)

def apply(func: Callable[[int], int], value: int) -> int:
    return func(value)

def sort_names(names: Iterable[str], order: Literal["asc", "desc"] = "asc") -> list[str]:
    return sorted(names, reverse=(order == "desc"))

class UserJSON(TypedDict):
    id: int
    name: str
    active: bool

@dataclass
class Point:
    x: float
    y: float

    def distance_to(self, other: "Point") -> float:
        return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5

users = {1: "أحمد", 2: "منى"}
print(average([90.0, 80.5]))
name = find_user(3, users)
print(name if name is not None else "غير موجود")   # افحص None دائماً
print(apply(lambda n: n * 2, 21))
print(sort_names(["منى", "أحمد", "زيد"], "desc"))

data: UserJSON = {"id": 1, "name": "منى", "active": True}
print(data["name"], Point(0, 0).distance_to(Point(3, 4)))

# التلميحات لا تُفرض أثناء التشغيل:
print(average([1, 2, 3]))   # يعمل رغم أن التلميح يقول float
اختبر فهمك