عندما يكون برنامج 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 للقوائم باستخدام 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
فحص 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 قبل التشغيل
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 مهمة جدًا؟
- عندما يكون المشروع موزعًا على عدة ملفات.
- عندما تعمل مع فريق.
- عندما توجد دوال كثيرة تتبادل بيانات مركبة.
- عندما تعيد الدالة أكثر من نوع محتمل.
- عندما تبني مكتبة أو 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الأكثر صرامة.
روابط داخلية مفيدة من بايثون العرب
- كورس بايثون بعد الأساسيات
- بايثون بعد الأساسيات 2: تنظيم ملفات مشروع Python بطريقة صحيحة
- بايثون بعد الأساسيات 5: شرح logging في Python
- بايثون بعد الأساسيات 6: اختبار الدوال باستخدام unittest
- بايثون بعد الأساسيات 7: شرح pytest في بايثون للمبتدئين
- شرح البيئة الافتراضية venv في Python للمبتدئين
مصادر خارجية رسمية للتوسع
- توثيق typing الرسمي في Python
- تعريف Type Hint في توثيق Python
- دليل البدء باستخدام mypy
- Type Hints Cheat Sheet في 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 مهمة في المشاريع الصغيرة؟
ليست إلزامية. فائدتها تزداد مع نمو المشروع وكثرة الدوال والملفات أو العمل ضمن فريق، لكن تعلمها مبكرًا يساعدك على كتابة واجهات دوال أوضح.



