كوكب بايثون
آخر تحديث: July 21, 2026 01:00 PM GMT
July 11, 2026
بايثون العرب
بايثون بالمثال 18: إنشاء برنامج قائمة مهام بسيطة للمبتدئين
في هذا الدرس من سلسلة بايثون بالمثال سنبني برنامجًا عمليًا بسيطًا: قائمة مهام تعمل من نافذة الأوامر.
فكرة البرنامج سهلة ومفيدة للمبتدئين: يستطيع المستخدم إضافة مهمة جديدة، عرض جميع المهام، تحديد مهمة كمكتملة، حذف مهمة، ثم الخروج من البرنامج. ورغم بساطة المثال، إلا أنه يجمع مفاهيم مهمة جدًا مثل list وdict وwhile وif وinput() والدوال.
هذا النوع من الأمثلة يساعدك على الانتقال من حفظ الأوامر إلى بناء برنامج صغير له وظيفة واضحة. لذلك لا تقرأ الكود فقط، بل اكتبه بيدك وشغله وعدل عليه.
{alertInfo} الفكرة ببساطة: سنخزن المهام داخل قائمةlist، وكل مهمة ستكون قاموسًاdictيحتوي على عنوان المهمة وحالتها: هل اكتملت أم لا.
{getToc} $title={محتوى المقال}
ماذا سنبني في هذا المثال؟
سننشئ برنامج قائمة مهام بسيطًا يعمل بهذا الشكل:
===== قائمة المهام =====
1. عرض المهام
2. إضافة مهمة
3. تحديد مهمة كمكتملة
4. حذف مهمة
5. خروج
اختر رقم العملية:
عندما يختار المستخدم رقمًا من القائمة، ينفذ البرنامج العملية المطلوبة. مثلًا إذا اختار 2 يستطيع إضافة مهمة جديدة، وإذا اختار 1 يعرض المهام الموجودة.
| الخيار | وظيفته | المفهوم المستخدم |
|---|---|---|
| عرض المهام | طباعة كل المهام الموجودة | for loop |
| إضافة مهمة | إضافة عنصر جديد إلى القائمة | append() |
| تحديد مهمة كمكتملة | تغيير حالة المهمة | dict |
| حذف مهمة | إزالة مهمة من القائمة | pop() |
| الخروج | إيقاف الحلقة | break |
لماذا هذا المثال مهم للمبتدئين؟
برنامج قائمة المهام من أفضل الأمثلة للمبتدئ؛ لأنه يشبه برامج حقيقية نستخدمها يوميًا، وفي نفس الوقت لا يحتاج إلى مكتبات خارجية أو واجهة رسومية.
ستتدرب في هذا المثال على:
- تخزين البيانات داخل قائمة.
- استخدام قاموس لكل مهمة.
- قراءة اختيار المستخدم باستخدام
input(). - تكرار القائمة باستخدام
while True. - تقسيم البرنامج إلى دوال صغيرة.
- التعامل مع الاختيارات الخاطئة بطريقة بسيطة.
{alertWarning} ملاحظة مهمة: هذا البرنامج لا يحفظ المهام بعد إغلاقه؛ لأننا سنخزن البيانات في الذاكرة فقط. حفظ المهام في ملف سيكون تطويرًا لاحقًا للمشروع.
الخطوة الأولى: إنشاء قائمة فارغة للمهام
سنبدأ بقائمة فارغة اسمها tasks. هذه القائمة ستكون مكان تخزين المهام أثناء تشغيل البرنامج.
tasks = []
كل مهمة داخل هذه القائمة ستكون قاموسًا بهذا الشكل:
{
"title": "تعلم أساسيات بايثون",
"done": False
}
استخدمنا المفتاح title لتخزين نص المهمة، والمفتاح done لتحديد هل المهمة مكتملة أم لا.
الخطوة الثانية: إنشاء دالة لعرض المهام
الدالة الأولى في البرنامج ستكون مسؤولة عن عرض جميع المهام الموجودة. إذا لم تكن هناك مهام، ستظهر رسالة تخبر المستخدم أن القائمة فارغة.
def show_tasks(tasks):
if not tasks:
print("لا توجد مهام حتى الآن.")
return
print("\nقائمة المهام:")
for index, task in enumerate(tasks, start=1):
status = "تمت" if task["done"] else "لم تكتمل"
print(f"{index}. {task['title']} - {status}")
استخدمنا enumerate() حتى نعرض رقمًا لكل مهمة، وهذا الرقم سيساعد المستخدم لاحقًا عند تحديد مهمة كمكتملة أو حذفها.
لاحظ أيضًا هذا السطر:
status = "تمت" if task["done"] else "لم تكتمل"
هذا السطر يحدد النص الذي سيظهر بجانب المهمة حسب قيمة done.
الخطوة الثالثة: إنشاء دالة لإضافة مهمة
الآن سننشئ دالة تطلب من المستخدم كتابة عنوان المهمة، ثم تضيفها إلى القائمة.
def add_task(tasks):
title = input("اكتب عنوان المهمة: ").strip()
if title == "":
print("لا يمكن إضافة مهمة فارغة.")
return
task = {
"title": title,
"done": False
}
tasks.append(task)
print("تمت إضافة المهمة بنجاح.")
استخدمنا strip() لحذف المسافات الزائدة من بداية ونهاية النص. فإذا ضغط المستخدم Enter دون كتابة شيء، لن نضيف مهمة فارغة.
الخطوة الرابعة: تحديد مهمة كمكتملة
عندما يريد المستخدم تحديد مهمة كمكتملة، يجب أن نعرض المهام أولًا، ثم نطلب منه إدخال رقم المهمة.
def complete_task(tasks):
show_tasks(tasks)
if not tasks:
return
try:
task_number = int(input("اكتب رقم المهمة المكتملة: "))
if 1 <= task_number <= len(tasks):
tasks[task_number - 1]["done"] = True
print("تم تحديد المهمة كمكتملة.")
else:
print("رقم المهمة غير موجود.")
except ValueError:
print("الرجاء إدخال رقم صحيح.")
استخدمنا try وexcept لأن المستخدم قد يكتب نصًا بدل رقم. في هذه الحالة لا نريد أن يتوقف البرنامج برسالة خطأ، بل نعرض رسالة واضحة.
{alertInfo}
لماذا كتبنا task_number - 1؟ لأن المستخدم يرى الترقيم يبدأ من 1، بينما فهارس القائمة في بايثون تبدأ من 0.
الخطوة الخامسة: حذف مهمة من القائمة
حذف المهمة يشبه تحديدها كمكتملة، لكن بدل تغيير قيمة done سنستخدم pop() لإزالة المهمة من القائمة.
def delete_task(tasks):
show_tasks(tasks)
if not tasks:
return
try:
task_number = int(input("اكتب رقم المهمة التي تريد حذفها: "))
if 1 <= task_number <= len(tasks):
deleted_task = tasks.pop(task_number - 1)
print(f"تم حذف المهمة: {deleted_task['title']}")
else:
print("رقم المهمة غير موجود.")
except ValueError:
print("الرجاء إدخال رقم صحيح.")
الدالة pop() تحذف العنصر من القائمة وتعيده لنا، لذلك خزنا المهمة المحذوفة في المتغير deleted_task حتى نطبع اسمها للمستخدم.
الخطوة السادسة: إنشاء قائمة الخيارات الرئيسية
الآن سننشئ دالة تعرض قائمة الخيارات للمستخدم. هذه الدالة فقط تطبع القائمة، ولا تنفذ العمليات.
def show_menu():
print("\n===== قائمة المهام =====")
print("1. عرض المهام")
print("2. إضافة مهمة")
print("3. تحديد مهمة كمكتملة")
print("4. حذف مهمة")
print("5. خروج")
وجود دالة خاصة بالقائمة يجعل الكود مرتبًا، ويسهل تعديل الخيارات لاحقًا.
الخطوة السابعة: تشغيل البرنامج باستخدام while
نحتاج الآن إلى حلقة مستمرة تعرض القائمة وتقرأ اختيار المستخدم. سنستخدم while True، وعندما يختار المستخدم الخروج نستخدم break.
tasks = []
while True:
show_menu()
choice = input("اختر رقم العملية: ")
if choice == "1":
show_tasks(tasks)
elif choice == "2":
add_task(tasks)
elif choice == "3":
complete_task(tasks)
elif choice == "4":
delete_task(tasks)
elif choice == "5":
print("تم إغلاق البرنامج.")
break
else:
print("اختيار غير صحيح. حاول مرة أخرى.")
بهذا الشكل أصبح لدينا برنامج تفاعلي بسيط. كل مرة يختار المستخدم عملية، ينفذ البرنامج الدالة المناسبة ثم يعود إلى القائمة مرة أخرى.
الكود الكامل للبرنامج
الآن اجمع كل الأجزاء السابقة داخل ملف واحد باسم todo_list.py:
def show_tasks(tasks):
if not tasks:
print("لا توجد مهام حتى الآن.")
return
print("\nقائمة المهام:")
for index, task in enumerate(tasks, start=1):
status = "تمت" if task["done"] else "لم تكتمل"
print(f"{index}. {task['title']} - {status}")
def add_task(tasks):
title = input("اكتب عنوان المهمة: ").strip()
if title == "":
print("لا يمكن إضافة مهمة فارغة.")
return
task = {
"title": title,
"done": False
}
tasks.append(task)
print("تمت إضافة المهمة بنجاح.")
def complete_task(tasks):
show_tasks(tasks)
if not tasks:
return
try:
task_number = int(input("اكتب رقم المهمة المكتملة: "))
if 1 <= task_number <= len(tasks):
tasks[task_number - 1]["done"] = True
print("تم تحديد المهمة كمكتملة.")
else:
print("رقم المهمة غير موجود.")
except ValueError:
print("الرجاء إدخال رقم صحيح.")
def delete_task(tasks):
show_tasks(tasks)
if not tasks:
return
try:
task_number = int(input("اكتب رقم المهمة التي تريد حذفها: "))
if 1 <= task_number <= len(tasks):
deleted_task = tasks.pop(task_number - 1)
print(f"تم حذف المهمة: {deleted_task['title']}")
else:
print("رقم المهمة غير موجود.")
except ValueError:
print("الرجاء إدخال رقم صحيح.")
def show_menu():
print("\n===== قائمة المهام =====")
print("1. عرض المهام")
print("2. إضافة مهمة")
print("3. تحديد مهمة كمكتملة")
print("4. حذف مهمة")
print("5. خروج")
tasks = []
while True:
show_menu()
choice = input("اختر رقم العملية: ")
if choice == "1":
show_tasks(tasks)
elif choice == "2":
add_task(tasks)
elif choice == "3":
complete_task(tasks)
elif choice == "4":
delete_task(tasks)
elif choice == "5":
print("تم إغلاق البرنامج.")
break
else:
print("اختيار غير صحيح. حاول مرة أخرى.")
تجربة تشغيل البرنامج
بعد حفظ الملف باسم todo_list.py، شغله من الطرفية:
python todo_list.py
جرّب إضافة ثلاث مهام مثل:
- مراجعة درس القوائم.
- حل تمرين بسيط.
- كتابة برنامج صغير.
ثم اعرض المهام، وحدد واحدة كمكتملة، وبعدها احذف مهمة أخرى. بهذه الطريقة ستفهم كيف تتحرك البيانات داخل القائمة أثناء تشغيل البرنامج.
أخطاء شائعة في هذا المثال
1. نسيان تحويل رقم المهمة إلى int
الدالة input() تعيد النصوص دائمًا، حتى لو كتب المستخدم رقمًا. لذلك يجب استخدام int() عند التعامل مع رقم المهمة.
task_number = int(input("اكتب رقم المهمة: "))
2. استخدام رقم المهمة مباشرة دون طرح 1
إذا كتب المستخدم رقم 1 فهو يقصد أول مهمة، لكن أول عنصر في القائمة فهرسه 0. لذلك نستخدم:
tasks[task_number - 1]
3. حذف مهمة من قائمة فارغة
قبل حذف مهمة أو تعديلها، يجب التأكد أن القائمة ليست فارغة. لذلك استخدمنا:
if not tasks:
return
أفكار لتطوير البرنامج
بعد أن تفهم هذا المثال، يمكنك تطوير البرنامج بخطوات بسيطة:
- حفظ المهام في ملف نصي أو ملف JSON.
- إضافة تاريخ لكل مهمة.
- إضافة أولوية للمهمة مثل: عالية، متوسطة، منخفضة.
- عرض المهام المكتملة فقط أو غير المكتملة فقط.
- إضافة خيار لتعديل اسم مهمة موجودة.
هذه التطويرات ستجعل المشروع أقرب إلى برنامج حقيقي، وستدربك على التعامل مع الملفات وتنظيم الكود.
ماذا تعلمت في هذا المثال؟
| المفهوم | كيف استخدمناه؟ |
|---|---|
list |
استخدمناها لتخزين جميع المهام. |
dict |
استخدمنا قاموسًا لكل مهمة يحتوي على العنوان والحالة. |
append() |
لإضافة مهمة جديدة إلى القائمة. |
pop() |
لحذف مهمة من القائمة. |
while True |
لتكرار عرض القائمة حتى يختار المستخدم الخروج. |
if / elif / else |
لتنفيذ العملية المناسبة حسب اختيار المستخدم. |
try / except |
للتعامل مع إدخال رقم غير صحيح دون إيقاف البرنامج. |
functions |
لتقسيم البرنامج إلى أجزاء واضحة ومنظمة. |
روابط داخلية مفيدة من بايثون العرب
- أساسيات بايثون 8: شرح Lists للمبتدئين
- أساسيات بايثون 10: شرح Dictionaries للمبتدئين
- أساسيات بايثون 11: شرح if و else للمبتدئين
- أساسيات بايثون 12: شرح الحلقات في بايثون
- أساسيات بايثون 13: شرح الدوال Functions للمبتدئين
- مشاريع بايثون بسيطة للمبتدئين
- تمارين بايثون للمبتدئين مع الحل
مصادر خارجية مفيدة
الخلاصة
في هذا المثال أنشأنا برنامج قائمة مهام بسيطة باستخدام بايثون. تعلمت كيف تخزن المهام داخل قائمة، وكيف تجعل كل مهمة قاموسًا يحتوي على العنوان والحالة، وكيف تعرض قائمة خيارات للمستخدم باستخدام while وinput().
هذا المثال مهم لأنه يجمع أكثر من مفهوم في برنامج واحد صغير. وكلما عدلت عليه وأضفت خيارات جديدة، ستفهم البرمجة العملية أكثر.
{alertSuccess} القاعدة المهمة: أفضل طريقة لتعلم بايثون ليست قراءة الأوامر فقط، بل بناء أمثلة صغيرة مثل برنامج قائمة المهام ثم تطويرها خطوة بعد خطوة.
أسئلة شائعة
هل برنامج قائمة المهام مناسب للمبتدئين؟
نعم، لأنه يجمع مفاهيم أساسية مثل القوائم، الشروط، الحلقات، الإدخال، والدوال داخل مثال عملي واضح.
هل يحفظ البرنامج المهام بعد إغلاقه؟
لا، هذا الإصدار يخزن المهام في الذاكرة فقط. عند إغلاق البرنامج ستختفي المهام، ويمكن تطويره لاحقًا لحفظ البيانات في ملف.
لماذا استخدمنا قائمة وقاموسًا معًا؟
استخدمنا القائمة لتخزين جميع المهام، واستخدمنا القاموس لتمثيل كل مهمة بمعلوماتها مثل العنوان وحالة الاكتمال.
ما فائدة الدوال في هذا البرنامج؟
الدوال تجعل البرنامج منظمًا وأسهل في القراءة والتعديل، بدل كتابة كل الكود داخل حلقة واحدة طويلة.
لماذا نستخدم try و except؟
نستخدمها حتى لا يتوقف البرنامج إذا كتب المستخدم نصًا بدل رقم عند اختيار مهمة للحذف أو الإكمال.
كيف أطور هذا المشروع؟
يمكنك إضافة حفظ المهام في ملف JSON، أو إضافة تاريخ وأولوية لكل مهمة، أو إنشاء خيار لتعديل اسم المهمة.
تحليل البيانات ببايثون: تنظيف البيانات والتعامل مع القيم الفارغة باستخدام pandas
بعد أن تتعلم قراءة ملف CSV وتحويله إلى DataFrame باستخدام مكتبة pandas، تأتي خطوة مهمة جدًا قبل أي تحليل حقيقي: تنظيف البيانات.
البيانات التي نحصل عليها من ملفات Excel أو CSV أو قواعد البيانات لا تكون دائمًا مرتبة وجاهزة. قد تجد قيمًا فارغة، أسماء أعمدة غير واضحة، أرقامًا ناقصة، أو صفوفًا غير مكتملة. لذلك لا يكفي أن تقرأ البيانات فقط، بل يجب أن تفحصها وتنظفها قبل أن تعتمد على نتائج التحليل.
في هذا الدرس من مسار تحليل البيانات ببايثون ستتعلم كيف تكتشف القيم الفارغة في pandas، وكيف تستخدم isnull() وdropna() وfillna()، ومتى تحذف القيم الناقصة ومتى تستبدلها بقيمة مناسبة.
{alertInfo} الفكرة ببساطة: تنظيف البيانات يعني فحص الجدول قبل التحليل، ومعالجة المشاكل مثل القيم الفارغة حتى لا تحصل على نتائج مضللة أو غير دقيقة.
{getToc} $title={محتوى المقال}
لماذا تنظيف البيانات مهم في تحليل البيانات؟
عند تحليل البيانات، قد تبدو الأرقام صحيحة من الخارج، لكنها قد تحتوي على مشاكل تؤثر على النتيجة النهائية. مثلًا إذا أردت حساب متوسط درجات الطلاب، وكان هناك طالب لا توجد له درجة، فماذا ستفعل؟ هل تحذف الصف؟ هل تضع صفرًا؟ هل تضع متوسط الدرجات؟
الإجابة تعتمد على نوع البيانات والهدف من التحليل. لذلك تنظيف البيانات ليس مجرد أوامر نكتبها، بل قرار منطقي قبل أن يكون خطوة برمجية.
| المشكلة | مثال | التأثير على التحليل |
|---|---|---|
| قيمة فارغة | درجة طالب غير موجودة | قد تؤثر على المتوسط أو الفرز أو التصفية. |
| اسم عمود غير واضح | col1 بدل score |
يصعب فهم البيانات والتعامل معها. |
| صف ناقص | اسم موجود لكن العمر غير موجود | قد يجعل المقارنات غير دقيقة. |
| نوع بيانات غير مناسب | رقم محفوظ كنص | قد يمنع الحسابات مثل المتوسط والمجموع. |
إنشاء بيانات للتجربة
حتى يكون الشرح واضحًا، سننشئ DataFrame بسيطًا يحتوي على بعض القيم الفارغة. في pandas تظهر القيم الفارغة غالبًا بالشكل NaN.
import pandas as pd
import numpy as np
data = {
"name": ["Ali", "Sara", "Omar", "Mona", "Khaled"],
"age": [22, 25, np.nan, 23, 24],
"city": ["Aden", "Sanaa", "Taiz", np.nan, "Hadramout"],
"score": [88, np.nan, 76, 95, 82]
}
df = pd.DataFrame(data)
print(df)
الناتج سيكون قريبًا من هذا الشكل:
name age city score
0 Ali 22.0 Aden 88.0
1 Sara 25.0 Sanaa NaN
2 Omar NaN Taiz 76.0
3 Mona 23.0 NaN 95.0
4 Khaled 24.0 Hadramout 82.0
لاحظ أن بعض الخانات تحتوي على NaN، وهذا يعني أن القيمة مفقودة أو غير موجودة.
ما معنى NaN في pandas؟
NaN تعني أن هناك قيمة مفقودة. قد تظهر بسبب أن الخلية كانت فارغة في ملف CSV، أو لأن البيانات لم تكن متوفرة عند جمعها، أو لأن هناك مشكلة أثناء إدخال البيانات.
وجود NaN ليس خطأ دائمًا، لكنه إشارة تقول لك: قبل أن تحلل البيانات، انتبه، هناك شيء ناقص يجب فحصه.
{alertWarning} لا تتعامل مع القيم الفارغة بعشوائية. حذف الصفوف أو تعبئة القيم قد يغير نتيجة التحليل، لذلك اختر الطريقة المناسبة حسب معنى البيانات.
اكتشاف القيم الفارغة باستخدام isnull
الدالة isnull() تفحص كل خلية في الجدول، وتخبرك هل هي فارغة أم لا. إذا كانت الخلية فارغة تعطي True، وإذا كانت غير فارغة تعطي False.
print(df.isnull())
الناتج سيكون جدولًا من القيم المنطقية:
name age city score
0 False False False False
1 False False False True
2 False True False False
3 False False True False
4 False False False False
هذا مفيد، لكنه قد يكون طويلًا إذا كان الجدول كبيرًا. لذلك نستخدم غالبًا sum() لمعرفة عدد القيم الفارغة في كل عمود.
print(df.isnull().sum())
الناتج:
name 0
age 1
city 1
score 1
dtype: int64
هنا نعرف أن عمود age يحتوي على قيمة فارغة واحدة، وعمود city يحتوي على قيمة فارغة واحدة، وعمود score يحتوي على قيمة فارغة واحدة.
اكتشاف القيم غير الفارغة باستخدام notnull
الدالة notnull() تعمل بالعكس. إذا كانت الخلية تحتوي على قيمة تعطي True، وإذا كانت فارغة تعطي False.
print(df.notnull())
قد تستخدمها عندما تريد اختيار الصفوف التي تحتوي على قيمة موجودة في عمود معين.
students_with_score = df[df["score"].notnull()]
print(students_with_score)
هذا الكود يعرض الطلاب الذين لديهم درجة موجودة فقط.
حذف الصفوف التي تحتوي على قيم فارغة باستخدام dropna
أسهل طريقة للتعامل مع القيم الفارغة هي حذف الصفوف التي تحتوي عليها باستخدام dropna().
clean_df = df.dropna()
print(clean_df)
الناتج سيحتوي فقط على الصفوف الكاملة التي لا تحتوي على قيم فارغة:
name age city score
0 Ali 22.0 Aden 88.0
4 Khaled 24.0 Hadramout 82.0
هذه الطريقة سريعة، لكنها قد تحذف بيانات كثيرة إذا كان الجدول كبيرًا وفيه قيم ناقصة متعددة.
{alertInfo}
استخدم dropna() بحذر. إذا كان عدد الصفوف قليلًا، فقد تخسر جزءًا مهمًا من البيانات بمجرد حذف الصفوف الناقصة.
حذف الصفوف حسب عمود معين
أحيانًا لا تريد حذف أي صف فيه أي قيمة فارغة، بل تريد حذف الصفوف فقط إذا كان عمود مهم فارغًا. مثلًا إذا كان عمود score مهمًا جدًا في التحليل، يمكن حذف الصفوف التي لا تحتوي على درجة فقط.
clean_scores = df.dropna(subset=["score"])
print(clean_scores)
بهذه الطريقة، لن نحذف الصف لمجرد أن المدينة فارغة، بل نحذف الصفوف التي لا تحتوي على قيمة في عمود score فقط.
تعبئة القيم الفارغة باستخدام fillna
بدل حذف الصفوف، يمكن تعبئة القيم الفارغة باستخدام fillna(). هذه الطريقة مفيدة عندما يكون الحذف غير مناسب.
مثال: تعبئة القيم الفارغة في عمود city بكلمة Unknown:
df["city"] = df["city"].fillna("Unknown")
print(df)
هذا يعني أن أي مدينة غير موجودة ستظهر باسم Unknown بدل NaN.
تعبئة القيم الرقمية بالمتوسط
في الأعمدة الرقمية، يمكن أحيانًا تعبئة القيمة الفارغة بمتوسط العمود. مثلًا إذا كان لدينا عمر مفقود، يمكن استخدام متوسط الأعمار:
average_age = df["age"].mean()
df["age"] = df["age"].fillna(average_age)
print(df)
وهنا يتم حساب متوسط الأعمار الموجودة، ثم استخدامه بدل العمر الفارغ.
{alertWarning} تعبئة القيم بالمتوسط ليست مناسبة دائمًا. استخدمها عندما يكون ذلك منطقيًا في سياق البيانات، ولا تستخدمها فقط لأنها سهلة.
تعبئة القيم الرقمية بقيمة ثابتة
أحيانًا تكون القيمة الثابتة أفضل من المتوسط. مثلًا إذا كان عمود score يمثل درجة اختبار، فقد تقرر أن الدرجة الفارغة تعني أن الطالب لم يدخل الاختبار، فتضع 0.
df["score"] = df["score"].fillna(0)
print(df)
لكن انتبه: وضع صفر قد يخفض المتوسط كثيرًا، لذلك يجب أن تكون متأكدًا من معنى القيمة الفارغة.
متى أحذف القيم الفارغة ومتى أعبئها؟
هذا سؤال مهم جدًا للمبتدئين في تحليل البيانات. لا توجد إجابة واحدة لكل الحالات، لكن هذا الجدول يساعدك على اتخاذ قرار مبدئي:
| الحالة | التصرف المناسب غالبًا | مثال |
|---|---|---|
| عدد القيم الفارغة قليل جدًا | يمكن حذف الصفوف الناقصة | صفان ناقصان من أصل آلاف الصفوف. |
| العمود مهم جدًا للتحليل | احذف الصفوف الناقصة في هذا العمود أو افحصها يدويًا | عمود السعر في تحليل المبيعات. |
| القيمة النصية غير معروفة | يمكن تعبئتها بكلمة مثل Unknown |
مدينة غير مسجلة. |
| القيمة الرقمية مفقودة | قد تستخدم المتوسط أو الوسيط حسب الحالة | عمر مفقود أو دخل مفقود. |
| القيمة الفارغة تعني عدم حدوث الشيء | قد تستخدم صفرًا | عدد المشتريات إذا لم يشترِ العميل شيئًا. |
فحص البيانات بعد التنظيف
بعد حذف أو تعبئة القيم الفارغة، يجب أن تفحص الجدول مرة أخرى. لا تفترض أن المشكلة انتهت دون مراجعة.
print(df.isnull().sum())
إذا كانت النتيجة كلها أصفارًا، فهذا يعني أنه لم تعد توجد قيم فارغة في الأعمدة التي فحصتها.
name 0
age 0
city 0
score 0
dtype: int64
تنظيف أسماء الأعمدة
من المشاكل الشائعة أيضًا أن تكون أسماء الأعمدة غير منظمة، مثل وجود مسافات زائدة أو اختلاف في شكل الحروف. مثلًا قد يكون اسم العمود " Score " بدل "score".
يمكن تنظيف أسماء الأعمدة بهذه الطريقة:
df.columns = df.columns.str.strip().str.lower()
print(df.columns)
هذا الكود يزيل المسافات من بداية ونهاية أسماء الأعمدة، ويحولها إلى حروف صغيرة حتى يصبح التعامل معها أسهل.
الكود الكامل للدرس
هذا مثال كامل يجمع أهم خطوات تنظيف البيانات في هذا الدرس:
import pandas as pd
import numpy as np
data = {
"name": ["Ali", "Sara", "Omar", "Mona", "Khaled"],
"age": [22, 25, np.nan, 23, 24],
"city": ["Aden", "Sanaa", "Taiz", np.nan, "Hadramout"],
"score": [88, np.nan, 76, 95, 82]
}
df = pd.DataFrame(data)
print("البيانات قبل التنظيف:")
print(df)
print("\nعدد القيم الفارغة قبل التنظيف:")
print(df.isnull().sum())
df["city"] = df["city"].fillna("Unknown")
average_age = df["age"].mean()
df["age"] = df["age"].fillna(average_age)
df["score"] = df["score"].fillna(0)
print("\nالبيانات بعد التنظيف:")
print(df)
print("\nعدد القيم الفارغة بعد التنظيف:")
print(df.isnull().sum())
ماذا تعلمت في هذا الدرس؟
| الأمر | وظيفته |
|---|---|
df.isnull() |
فحص القيم الفارغة داخل الجدول. |
df.isnull().sum() |
حساب عدد القيم الفارغة في كل عمود. |
df.notnull() |
فحص القيم الموجودة غير الفارغة. |
df.dropna() |
حذف الصفوف التي تحتوي على قيم فارغة. |
df.dropna(subset=["column"]) |
حذف الصفوف حسب وجود قيمة فارغة في عمود معين. |
df.fillna() |
تعبئة القيم الفارغة بقيمة ثابتة أو محسوبة. |
df.columns.str.strip() |
تنظيف المسافات الزائدة من أسماء الأعمدة. |
روابط داخلية مفيدة من بايثون العرب
- تحليل البيانات ببايثون للمبتدئين: من أين تبدأ؟
- أساسيات بايثون 30: التعامل مع ملفات CSV للمبتدئين
- قراءة ملفات CSV داخل مشروع بايثون بعد الأساسيات
- شرح pip في بايثون: تثبيت مكتبات بايثون وإدارة الحزم للمبتدئين
- أفضل مكتبات تحليل البيانات ببايثون
مصادر خارجية مفيدة
- دليل pandas الرسمي للتعامل مع القيم المفقودة
- توثيق dropna الرسمي في pandas
- توثيق fillna الرسمي في pandas
الخلاصة
تنظيف البيانات خطوة أساسية قبل أي تحليل باستخدام pandas. في هذا الدرس تعلمت كيف تكتشف القيم الفارغة باستخدام isnull()، وكيف تحسب عددها في كل عمود، وكيف تحذف الصفوف الناقصة باستخدام dropna() أو تعبئ القيم المفقودة باستخدام fillna().
تذكر دائمًا أن الهدف ليس إزالة NaN بأي طريقة، بل اختيار القرار الصحيح حسب معنى البيانات. أحيانًا يكون الحذف مناسبًا، وأحيانًا تكون التعبئة أفضل، وأحيانًا تحتاج إلى مراجعة مصدر البيانات نفسه.
{alertSuccess} القاعدة المهمة: لا تبدأ تحليل البيانات مباشرة بعد القراءة. افحص القيم الفارغة، راجع الأعمدة، ونظف البيانات أولًا حتى تكون النتائج أقرب إلى الواقع.
أسئلة شائعة
ما معنى القيم الفارغة في pandas؟
القيم الفارغة هي خانات لا تحتوي على بيانات، وتظهر غالبًا في pandas بالشكل NaN. وقد تكون ناتجة عن خلية فارغة في ملف CSV أو نقص في البيانات الأصلية.
ما الفرق بين isnull و notnull؟
isnull() تستخدم لاكتشاف القيم الفارغة، أما notnull() فتستخدم لاكتشاف القيم الموجودة غير الفارغة.
متى أستخدم dropna؟
استخدم dropna() عندما تكون القيم الفارغة قليلة، أو عندما يكون وجود قيمة ناقصة يجعل الصف غير صالح للتحليل.
متى أستخدم fillna؟
استخدم fillna() عندما لا تريد حذف الصفوف، وتريد استبدال القيم الفارغة بقيمة مناسبة مثل المتوسط أو كلمة Unknown أو رقم معين.
هل تعبئة القيم الفارغة بالمتوسط دائمًا صحيحة؟
لا، تعبئة القيم بالمتوسط ليست مناسبة دائمًا. يجب أن تسأل أولًا: هل المتوسط يمثل هذه القيمة فعلًا؟ وهل سيؤثر على نتيجة التحليل؟
هل يجب تنظيف البيانات قبل كل تحليل؟
نعم، من الأفضل دائمًا فحص البيانات وتنظيفها قبل التحليل، لأن القيم الفارغة أو الأعمدة غير المنظمة قد تؤدي إلى نتائج غير دقيقة.
July 10, 2026
بايثون العرب
تحليل البيانات ببايثون : قراءة ملف CSV باستخدام pandas خطوة بخطوة
قراءة ملف CSV باستخدام pandas من أهم الخطوات الأولى في تحليل البيانات ببايثون؛ لأن معظم البيانات التي ستتعامل معها في البداية تكون محفوظة في ملفات تشبه الجداول: صفوف، أعمدة، أسماء حقول، وقيم قابلة للفحص والتحليل.
في هذا الدرس ستتعلم كيف تجهز ملف CSV بسيطًا، وكيف تقرأه باستخدام الدالة read_csv()، ثم كيف تعرض أول الصفوف، وتعرف عدد الصفوف والأعمدة، وتعرض أسماء الأعمدة، وتحسب متوسط عمود رقمي، وتتعامل مع أشهر الأخطاء التي تظهر للمبتدئين.
إذا كنت جديدًا تمامًا في هذا المسار، يمكنك أولًا مراجعة دليل تحليل البيانات ببايثون للمبتدئين، وإذا أردت فهم ملفات CSV من ناحية أساسيات بايثون قبل استخدام pandas فراجع أيضًا درس التعامل مع ملفات CSV في كورس أساسيات بايثون.
{alertInfo} الفكرة ببساطة: الدالةread_csv()تقرأ ملفCSVوتحوله إلى جدول داخلpandasيسمىDataFrame، وبعدها تستطيع فحص البيانات وتحليلها بأوامر قصيرة وواضحة.
{getToc} $title={محتوى المقال}
ماذا ستتعلم في هذا الدرس؟
| المهارة | لماذا تحتاجها؟ |
|---|---|
فهم شكل ملف CSV |
حتى تعرف كيف تكون البيانات قبل قراءتها في بايثون. |
استخدام pd.read_csv() |
لقراءة الملف وتحويله إلى DataFrame. |
استخدام head() وshape وcolumns |
لفحص أول الصفوف، وحجم البيانات، وأسماء الأعمدة. |
| اختيار عمود وحساب المتوسط | لبدء أول خطوة تحليل بسيطة على البيانات. |
| حل أخطاء القراءة الشائعة | مثل خطأ عدم وجود الملف، مشكلة الترميز، أو ظهور البيانات في عمود واحد. |
ما هو ملف CSV؟
ملف CSV هو ملف نصي بسيط تُخزن فيه البيانات على شكل صفوف وأعمدة. غالبًا يكون الصف الأول هو أسماء الأعمدة، ثم تأتي البيانات في الصفوف التالية.
مثال على ملف CSV بسيط:
name,age,city,score
Ali,22,Aden,88
Sara,25,Sanaa,91
Omar,20,Taiz,76
Mona,23,Aden,95
Khaled,24,Hadramout,82
في هذا المثال لدينا أربعة أعمدة:
name: اسم الطالب.age: العمر.city: المدينة.score: الدرجة.
كل سطر بعد الصف الأول يمثل صفًا واحدًا من البيانات. لذلك عندما تقرأ هذا الملف باستخدام pandas سيظهر لك كجدول منظم يمكن فحصه وتحليله.
لماذا نستخدم pandas بدل القراءة اليدوية؟
يمكنك قراءة ملف نصي عادي باستخدام open()، لكن تحليل البيانات يحتاج أكثر من مجرد قراءة السطور. أنت تحتاج إلى اختيار أعمدة، تصفية صفوف، حساب متوسطات، معرفة القيم الفارغة، وفرز البيانات. هنا تظهر قوة مكتبة pandas.
| القراءة اليدوية | القراءة باستخدام pandas |
|---|---|
| تحتاج إلى قراءة السطور وتقسيم النصوص يدويًا. | تقرأ الملف بأمر واحد باستخدام read_csv(). |
| اختيار الأعمدة يحتاج كودًا أطول. | يمكن اختيار أي عمود باسمه مباشرة. |
| الحسابات تحتاج حلقات وشروطًا كثيرة. | يمكن حساب المتوسط والمجموع والوصف الإحصائي بدوال جاهزة. |
| فحص البيانات الكبيرة يصبح متعبًا. | يمكن استخدام head() وinfo() وdescribe() بسرعة. |
تثبيت pandas قبل قراءة الملف
قبل استخدام pandas، تأكد أن المكتبة مثبتة لديك. افتح نافذة الأوامر واكتب:
pip install pandas
وإذا كنت تستخدم أكثر من نسخة لبايثون على جهازك، فقد تحتاج إلى استخدام:
python -m pip install pandas
بعد التثبيت، نستورد المكتبة داخل ملف بايثون بهذا الشكل:
import pandas as pd
الاختصار pd هو الاختصار الشائع لمكتبة pandas، وستراه في أغلب الدروس والمشاريع.
{alertWarning}
إذا ظهر خطأ ModuleNotFoundError: No module named 'pandas'، فهذا يعني أن المكتبة غير مثبتة في البيئة التي تشغل منها الكود. ثبتها بالأمر السابق ثم شغل الملف مرة أخرى.
إنشاء ملف CSV للتجربة
أنشئ مجلدًا جديدًا للتجربة، ثم أنشئ بداخله ملفًا باسم:
students.csv
ضع داخل الملف البيانات التالية:
name,age,city,score
Ali,22,Aden,88
Sara,25,Sanaa,91
Omar,20,Taiz,76
Mona,23,Aden,95
Khaled,24,Hadramout,82
ثم أنشئ ملف بايثون باسم:
read_students.py
اجعل الملفين في نفس المجلد حتى تكون القراءة سهلة:
project-folder/
read_students.py
students.csv
{alertInfo} ملاحظة مهمة: إذا كنت تستخدم Notepad على ويندوز، تأكد أن اسم الملف أصبحstudents.csvوليسstudents.csv.txt؛ لأن هذا من أكثر أسباب خطأ عدم العثور على الملف.
قراءة ملف CSV باستخدام read_csv
اكتب الكود التالي داخل ملف read_students.py:
import pandas as pd
df = pd.read_csv("students.csv")
print(df)
في هذا الكود:
- استوردنا مكتبة
pandasبالاختصارpd. - قرأنا ملف
students.csvباستخدامpd.read_csv(). - خزنّا البيانات في متغير اسمه
df. - طبعنا الجدول الناتج باستخدام
print(df).
قد يظهر الناتج بهذا الشكل:
name age city score
0 Ali 22 Aden 88
1 Sara 25 Sanaa 91
2 Omar 20 Taiz 76
3 Mona 23 Aden 95
4 Khaled 24 Hadramout 82
الأرقام الموجودة في أقصى اليسار تسمى index. هذه الأرقام لم تكن موجودة في ملف CSV، لكن pandas يضيفها لترقيم الصفوف داخل DataFrame.
ما معنى DataFrame في pandas؟
DataFrame هو جدول بيانات داخل pandas. يمكنك تخيله مثل جدول في Excel، لكنه داخل بايثون ويمكن التعامل معه بالكود.
| العنصر | معناه داخل DataFrame |
|---|---|
| الصف | سجل واحد من البيانات، مثل بيانات طالب واحد. |
| العمود | نوع معلومة معين، مثل الاسم أو العمر أو الدرجة. |
الفهرس index |
ترقيم داخلي للصفوف يبدأ غالبًا من 0. |
| اسم العمود | العنوان الموجود في الصف الأول من ملف CSV. |
عرض أول الصفوف باستخدام head
عند التعامل مع ملف كبير، لا تطبع كل البيانات مرة واحدة. الأفضل أن تعرض أول عدة صفوف فقط باستخدام head().
import pandas as pd
df = pd.read_csv("students.csv")
print(df.head())
الدالة head() تعرض أول 5 صفوف بشكل افتراضي. ويمكنك تحديد عدد الصفوف التي تريدها:
print(df.head(3))
هذا السطر يعرض أول 3 صفوف فقط.
معرفة عدد الصفوف والأعمدة باستخدام shape
بعد قراءة البيانات، اسأل نفسك دائمًا: كم صفًا وكم عمودًا في الملف؟ نستخدم shape للإجابة على هذا السؤال.
import pandas as pd
df = pd.read_csv("students.csv")
print(df.shape)
الناتج:
(5, 4)
هذا يعني أن البيانات تحتوي على 5 صفوف و4 أعمدة.
عرض أسماء الأعمدة باستخدام columns
لمعرفة أسماء الأعمدة الموجودة في الملف، استخدم columns:
import pandas as pd
df = pd.read_csv("students.csv")
print(df.columns)
قد يظهر الناتج بهذا الشكل:
Index(['name', 'age', 'city', 'score'], dtype='object')
وإذا أردت عرض أسماء الأعمدة كقائمة أوضح، استخدم:
print(list(df.columns))
الناتج:
['name', 'age', 'city', 'score']
عرض معلومات عامة عن البيانات باستخدام info
الدالة info() تعطيك ملخصًا سريعًا عن البيانات: عدد الصفوف، أسماء الأعمدة، عدد القيم غير الفارغة، ونوع البيانات في كل عمود.
import pandas as pd
df = pd.read_csv("students.csv")
df.info()
قد ترى ناتجًا مشابهًا لهذا:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5 entries, 0 to 4
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 name 5 non-null object
1 age 5 non-null int64
2 city 5 non-null object
3 score 5 non-null int64
dtypes: int64(2), object(2)
هذا الملخص مهم قبل أي تحليل؛ لأنه يخبرك هل الأعمدة الرقمية ظهرت كأرقام فعلًا، وهل توجد قيم فارغة، وما نوع كل عمود.
اختيار عمود واحد من البيانات
يمكنك اختيار عمود واحد باستخدام اسمه بين أقواس مربعة. مثلًا لاختيار عمود الأسماء:
import pandas as pd
df = pd.read_csv("students.csv")
print(df["name"])
الناتج:
0 Ali
1 Sara
2 Omar
3 Mona
4 Khaled
Name: name, dtype: object
ولاختيار عمود الدرجات:
print(df["score"])
حساب متوسط الدرجات
بما أن عمود score يحتوي على أرقام، يمكننا حساب المتوسط باستخدام mean():
import pandas as pd
df = pd.read_csv("students.csv")
average_score = df["score"].mean()
print(average_score)
هذا المثال بسيط، لكنه يوضح فكرة مهمة: بعد قراءة ملف CSV وتحويله إلى DataFrame، تستطيع تنفيذ عمليات تحليلية على الأعمدة مباشرة.
تصفية البيانات حسب شرط
يمكنك عرض الطلاب الذين حصلوا على درجة أكبر من أو تساوي 85 بهذا الشكل:
import pandas as pd
df = pd.read_csv("students.csv")
high_scores = df[df["score"] >= 85]
print(high_scores)
الناتج:
name age city score
0 Ali 22 Aden 88
1 Sara 25 Sanaa 91
3 Mona 23 Aden 95
هذه خطوة صغيرة، لكنها بداية فعلية لتحليل البيانات؛ لأنك لم تعد تعرض الجدول فقط، بل بدأت تستخرج جزءًا محددًا منه حسب شرط.
قراءة ملف CSV من مجلد فرعي
إذا كان ملف CSV داخل مجلد فرعي اسمه data، فسيكون شكل المشروع مثل:
project-folder/
read_students.py
data/
students.csv
في هذه الحالة اكتب المسار بهذا الشكل:
import pandas as pd
df = pd.read_csv("data/students.csv")
print(df.head())
على ويندوز يمكنك غالبًا استخدام الشرطة المائلة للأمام / داخل المسار، وهي أسهل وأوضح للمبتدئين من الشرطة العكسية.
تحديد الفاصل sep عند الحاجة
ليس كل ملف شبيه بـ CSV يستخدم الفاصلة العادية ,. أحيانًا تكون البيانات مفصولة بفاصلة منقوطة ;، خصوصًا في بعض الملفات المصدرة من برامج الجداول.
مثال ملف مفصول بفاصلة منقوطة:
name;age;city;score
Ali;22;Aden;88
Sara;25;Sanaa;91
في هذه الحالة نستخدم المعامل sep:
import pandas as pd
df = pd.read_csv("students.csv", sep=";")
print(df.head())
إذا قرأت الملف ووجدت كل البيانات ظهرت داخل عمود واحد، فغالبًا المشكلة أن الفاصل المستخدم في الملف ليس فاصلة عادية.
مشكلة الترميز encoding مع اللغة العربية
إذا كان ملف CSV يحتوي على نص عربي وظهرت الحروف بشكل غريب، فقد تكون المشكلة في الترميز. جرّب قراءة الملف باستخدام encoding="utf-8":
import pandas as pd
df = pd.read_csv("students.csv", encoding="utf-8")
print(df.head())
وفي بعض الملفات المصدرة من Excel قد تحتاج إلى تجربة utf-8-sig:
df = pd.read_csv("students.csv", encoding="utf-8-sig")
{alertInfo} عند التعامل مع ملفات عربية، الأفضل أن تحفظ ملفCSVبترميزUTF-8حتى تقل مشاكل ظهور الحروف بشكل غير صحيح.
قراءة أعمدة محددة فقط باستخدام usecols
أحيانًا لا تحتاج إلى كل الأعمدة الموجودة في الملف. يمكنك قراءة أعمدة محددة فقط باستخدام usecols:
import pandas as pd
df = pd.read_csv("students.csv", usecols=["name", "score"])
print(df)
هذا مفيد عندما يكون الملف كبيرًا وتريد التركيز على أعمدة معينة فقط.
قراءة عدد محدود من الصفوف باستخدام nrows
إذا كان الملف كبيرًا جدًا وتريد تجربة سريعة، يمكنك قراءة أول عدد محدد من الصفوف باستخدام nrows:
import pandas as pd
df = pd.read_csv("students.csv", nrows=3)
print(df)
هذا يقرأ أول 3 صفوف فقط من الملف.
أخطاء شائعة عند استخدام read_csv
هذه أشهر المشاكل التي قد تظهر عند قراءة ملفات CSV باستخدام pandas، خصوصًا في بداية التعلم.
| المشكلة | السبب المحتمل | الحل السريع |
|---|---|---|
FileNotFoundError |
الملف غير موجود في المسار المكتوب. | تأكد من اسم الملف ومكانه، أو استخدم مسارًا صحيحًا مثل data/students.csv. |
ModuleNotFoundError |
مكتبة pandas غير مثبتة في البيئة الحالية. |
استخدم python -m pip install pandas. |
| كل البيانات ظهرت في عمود واحد | الفاصل داخل الملف ليس فاصلة عادية. | جرّب sep=";" أو افحص الفاصل المستخدم داخل الملف. |
| الحروف العربية ظهرت بشكل غير صحيح | مشكلة ترميز. | جرّب encoding="utf-8-sig". |
KeyError |
كتبت اسم عمود غير موجود أو مختلف في الملف. | اعرض أسماء الأعمدة باستخدام list(df.columns). |
1. خطأ FileNotFoundError
هذا الخطأ يعني أن بايثون لم يجد الملف في المسار الذي كتبته.
FileNotFoundError: [Errno 2] No such file or directory: 'students.csv'
الحل:
- تأكد أن اسم الملف مكتوب بشكل صحيح.
- تأكد أن الملف في نفس مجلد ملف بايثون.
- إذا كان الملف داخل مجلد فرعي، اكتب المسار الصحيح مثل
data/students.csv. - تأكد أن الملف ليس محفوظًا باسم
students.csv.txt.
2. ظهور كل البيانات في عمود واحد
إذا ظهر الجدول كله داخل عمود واحد، فقد يكون الفاصل داخل الملف ليس فاصلة ,. جرّب:
df = pd.read_csv("students.csv", sep=";")
3. ظهور الحروف العربية بشكل غريب
جرّب تحديد الترميز:
df = pd.read_csv("students.csv", encoding="utf-8-sig")
4. خطأ KeyError عند اختيار عمود
إذا كتبت اسم عمود غير موجود، سيظهر خطأ مثل KeyError. لذلك افحص أسماء الأعمدة أولًا:
print(list(df.columns))
انتبه أيضًا إلى المسافات الزائدة في أسماء الأعمدة. أحيانًا يكون اسم العمود " score" وليس "score".
الكود الكامل للدرس
هذا كود كامل يجمع أهم ما تعلمناه في الدرس:
import pandas as pd
df = pd.read_csv("students.csv")
print("أول الصفوف:")
print(df.head())
print("\nحجم البيانات:")
print(df.shape)
print("\nأسماء الأعمدة:")
print(list(df.columns))
print("\nمعلومات عامة عن البيانات:")
df.info()
print("\nمتوسط الدرجات:")
print(df["score"].mean())
print("\nالطلاب أصحاب الدرجات العالية:")
high_scores = df[df["score"] >= 85]
print(high_scores)
ملخص أوامر pandas التي استخدمناها
| الأمر | وظيفته |
|---|---|
import pandas as pd |
استيراد مكتبة pandas بالاختصار الشائع pd. |
pd.read_csv() |
قراءة ملف CSV وتحويله إلى DataFrame. |
df.head() |
عرض أول الصفوف من البيانات. |
df.shape |
معرفة عدد الصفوف والأعمدة. |
df.columns |
عرض أسماء الأعمدة. |
df.info() |
عرض ملخص عن الأعمدة وأنواع البيانات والقيم غير الفارغة. |
df["score"].mean() |
حساب متوسط عمود رقمي. |
df[df["score"] >= 85] |
تصفية الصفوف حسب شرط معين. |
sep |
تحديد الفاصل المستخدم داخل الملف. |
encoding |
تحديد ترميز الملف عند وجود نص عربي أو رموز غير ظاهرة بشكل صحيح. |
روابط داخلية مفيدة من بايثون العرب
- تحليل البيانات ببايثون للمبتدئين: من أين تبدأ؟
- أساسيات بايثون 30: التعامل مع ملفات CSV للمبتدئين
- قراءة ملفات CSV داخل مشروع بايثون بعد الأساسيات
- شرح pip في بايثون: تثبيت مكتبات بايثون وإدارة الحزم للمبتدئين
- أفضل مكتبات تحليل البيانات ببايثون
- كورس أساسيات بايثون للمبتدئين
مصادر خارجية مفيدة
الخلاصة
قراءة ملف CSV باستخدام pandas هي خطوة أساسية في تحليل البيانات ببايثون. باستخدام أمر واحد مثل pd.read_csv("students.csv") يمكنك تحويل ملف نصي بسيط إلى DataFrame منظم، وبعدها تبدأ بفحص البيانات وتحليلها.
في هذا الدرس تعلمت كيف تقرأ الملف، وتعرض أول الصفوف باستخدام head()، وتعرف حجم البيانات باستخدام shape، وتعرض أسماء الأعمدة باستخدام columns، وتستخدم info() لفهم بنية البيانات، ثم تحسب متوسط عمود رقمي وتستخرج صفوفًا حسب شرط معين.
{alertSuccess}
القاعدة المهمة: قبل أن تبدأ أي تحليل، اقرأ البيانات جيدًا، افحص أول الصفوف، راجع أسماء الأعمدة، وتأكد أن البيانات ظهرت بالشكل الصحيح داخل DataFrame.
أسئلة شائعة
ما معنى CSV؟
CSV اختصار لـ Comma-Separated Values، وهي صيغة بسيطة لتخزين البيانات في صفوف وأعمدة، وغالبًا تكون القيم فيها مفصولة بفواصل.
ما الفرق بين CSV وDataFrame؟
CSV هو ملف محفوظ على جهازك، أما DataFrame فهو شكل البيانات بعد قراءتها داخل pandas حتى تستطيع التعامل معها بالكود.
هل يجب تثبيت pandas قبل استخدام read_csv؟
نعم، إذا لم تكن مكتبة pandas مثبتة لديك، يجب تثبيتها أولًا باستخدام الأمر pip install pandas أو python -m pip install pandas.
لماذا يظهر خطأ FileNotFoundError عند قراءة CSV؟
لأن الملف غير موجود في المسار الذي كتبته، أو لأن اسم الملف مختلف. تأكد من مكان الملف واسمه، وتأكد أنه ليس محفوظًا باسم students.csv.txt.
ماذا أفعل إذا ظهرت البيانات كلها في عمود واحد؟
غالبًا الفاصل داخل الملف ليس فاصلة عادية. جرّب استخدام sep=";" أو افتح الملف وتأكد من الرمز المستخدم بين القيم.
كيف أتعامل مع ملف CSV يحتوي على نص عربي؟
احفظ الملف بترميز UTF-8، وإذا ظهرت الحروف بشكل غير صحيح جرّب استخدام encoding="utf-8-sig" عند قراءة الملف.
July 09, 2026
بايثون العرب
مشكلة وحل 25: حل خطأ ZeroDivisionError: division by zero في بايثون
في هذا المقال من سلسلة مشكلة وحل سنتعلم طريقة حل خطأ ZeroDivisionError: division by zero في بايثون، وهو من الأخطاء الشائعة التي تظهر عندما يحاول البرنامج قسمة رقم على صفر.
قد يظهر لك الخطأ أثناء كتابة كود بسيط للقسمة، أو عند استقبال رقم من المستخدم، أو عند حساب النسبة المئوية، أو حتى عند حساب متوسط قائمة فارغة. المهم أن تفهم السبب قبل نسخ الحل، لأن نفس الخطأ قد يظهر بأكثر من رسالة مثل float division by zero أو integer division or modulo by zero.
{alertInfo} الفكرة المختصرة: الخطأ يظهر عندما يكون المقسوم عليه صفرًا. الحل الأفضل غالبًا هو فحص القيمة قبل القسمة، أو استخدامtryوexceptعندما تكون القيمة قادمة من المستخدم أو من مصدر غير مضمون.
{getToc} $title={محتوى المقال}
ما معنى خطأ ZeroDivisionError: division by zero؟
معنى خطأ ZeroDivisionError: division by zero أن بايثون حاولت تنفيذ عملية قسمة، لكن الرقم الموجود بعد علامة القسمة كان صفرًا.
مثال بسيط يسبب الخطأ:
result = 10 / 0
print(result)
عند تشغيل الكود سيظهر خطأ يشبه:
ZeroDivisionError: division by zero
السبب أن القسمة على صفر غير مسموحة، لذلك يوقف بايثون تنفيذ البرنامج ويعرض رسالة الخطأ بدل أن يعطي نتيجة غير صحيحة.
متى يظهر هذا الخطأ في بايثون؟
يظهر خطأ ZeroDivisionError عند استخدام صفر في الجهة اليمنى من عمليات القسمة أو باقي القسمة. لا يظهر فقط مع الرمز /، بل قد يظهر أيضًا مع // و %.
| العملية | مثال يسبب الخطأ | رسالة شائعة |
|---|---|---|
| القسمة العادية | 10 / 0 |
division by zero |
| القسمة الصحيحة | 10 // 0 |
integer division or modulo by zero |
| باقي القسمة | 10 % 0 |
integer modulo by zero |
| القسمة العشرية | 10.0 / 0.0 |
float division by zero |
إذن إذا وجدت رسالة float division by zero أو integer division or modulo by zero فهي غالبًا نفس المشكلة: توجد عملية حسابية يستخدم فيها البرنامج صفرًا كمقسوم عليه.
كيف تقرأ رسالة الخطأ؟
عندما يظهر الخطأ، لا تنظر إلى آخر سطر فقط. اقرأ السطر الذي يشير إليه Traceback، لأنه يوضح أين حدثت القسمة على صفر.
مثال:
price = 100
quantity = 0
average_price = price / quantity
print(average_price)
المشكلة هنا ليست في اسم المتغيرات، بل في أن quantity قيمته صفر. لذلك اسأل نفسك دائمًا: ما المتغير الموجود بعد علامة القسمة؟ وهل يمكن أن تكون قيمته صفرًا؟
{alertWarning} لا تبحث عن الحل في السطر الأخير فقط. ابحث عن العملية الحسابية التي تستخدم/أو//أو%، ثم افحص قيمة المتغير الموجود بعد العملية.
الحل الأول: افحص المقسوم عليه قبل القسمة
أفضل حل عندما تكون قادرًا على التحكم في القيم هو فحص المقسوم عليه قبل تنفيذ القسمة.
number = 10
divider = 0
if divider != 0:
result = number / divider
print("النتيجة:", result)
else:
print("لا يمكن القسمة على صفر")
هذا الحل واضح جدًا للمبتدئين؛ لأن البرنامج لا يدخل إلى عملية القسمة أصلًا إلا إذا كانت قيمة divider مختلفة عن صفر.
الحل الثاني: استخدم دالة آمنة للقسمة
إذا كانت القسمة تتكرر في أكثر من مكان داخل مشروعك، فمن الأفضل أن تكتب دالة صغيرة تتولى الفحص بدل تكرار الشرط في كل مرة.
def safe_divide(number, divider):
if divider == 0:
return None
return number / divider
result = safe_divide(10, 0)
if result is None:
print("لا يمكن القسمة على صفر")
else:
print("النتيجة:", result)
استخدام None هنا أفضل من إرجاع صفر مباشرة في كل الحالات، لأن الصفر قد يكون نتيجة حقيقية أحيانًا. أما None فيوضح أن العملية لم تتم.
{alertInfo}
لا تجعل 10 / 0 تساوي صفرًا تلقائيًا في كل البرامج. أحيانًا هذا يخفي مشكلة منطقية في البيانات، خصوصًا عند حساب النسب والمتوسطات.
الحل الثالث: استخدم try و except مع إدخال المستخدم
عندما تكون القيم قادمة من المستخدم، قد يدخل المستخدم صفرًا أو نصًا بدل رقم. في هذه الحالة يمكن استخدام try و except للتعامل مع أكثر من خطأ بطريقة منظمة.
try:
num1 = float(input("أدخل الرقم الأول: "))
num2 = float(input("أدخل الرقم الثاني: "))
result = num1 / num2
print("النتيجة:", result)
except ValueError:
print("يجب إدخال أرقام فقط")
except ZeroDivisionError:
print("لا يمكن القسمة على صفر")
هذا المثال يعالج حالتين شائعتين:
- إذا أدخل المستخدم نصًا بدل رقم، يظهر
ValueError. - إذا أدخل المستخدم صفرًا في الرقم الثاني، يظهر
ZeroDivisionError.
متى أستخدم الشرط ومتى أستخدم try except؟
كلا الحلين صحيح، لكن اختيار الطريقة يعتمد على حالتك.
| الحالة | الحل الأفضل غالبًا |
|---|---|
| القيمة واضحة داخل الكود ويمكن فحصها بسهولة | استخدم شرط if divider != 0 |
| القيمة قادمة من مستخدم أو ملف أو مصدر خارجي | استخدم try و except مع التحقق المناسب |
| القسمة تتكرر في أكثر من مكان | اكتب دالة مثل safe_divide |
| تريد منع الخطأ قبل حدوثه | استخدم شرطًا قبل القسمة |
| تريد أن يستمر البرنامج حتى لو حدث الخطأ | استخدم try except |
مثال شائع: خطأ القسمة على صفر عند حساب المتوسط
من الحالات التي تربك المبتدئين أن يظهر الخطأ عند حساب متوسط قائمة. قد تكون القائمة فارغة، وبالتالي يكون عدد العناصر صفرًا.
مثال يسبب الخطأ:
scores = []
average = sum(scores) / len(scores)
print(average)
هنا len(scores) تساوي صفرًا، لذلك تصبح العملية مثل:
0 / 0
الحل:
scores = []
if len(scores) > 0:
average = sum(scores) / len(scores)
print("المتوسط:", average)
else:
print("لا يمكن حساب المتوسط لأن القائمة فارغة")
ويمكنك كتابة الشرط بطريقة أقصر:
if scores:
average = sum(scores) / len(scores)
print("المتوسط:", average)
else:
print("القائمة فارغة")
مثال عملي: برنامج قسمة آمن للمبتدئين
هذا مثال كامل يجمع بين إدخال المستخدم، التحقق من الأرقام، والتعامل مع خطأ القسمة على صفر.
def divide_numbers():
try:
first_number = float(input("أدخل الرقم الأول: "))
second_number = float(input("أدخل الرقم الثاني: "))
if second_number == 0:
print("لا يمكن القسمة على صفر")
return
result = first_number / second_number
print("النتيجة:", result)
except ValueError:
print("الرجاء إدخال أرقام صحيحة")
divide_numbers()
في هذا المثال استخدمنا الشرط لمنع القسمة على صفر قبل حدوثها، واستخدمنا except ValueError للتعامل مع الإدخال غير الرقمي.
هل من الصحيح استخدام except Exception؟
قد تجد بعض الأمثلة تستخدم except Exception لالتقاط كل الأخطاء، لكن هذا ليس أفضل حل للمبتدئ في هذه الحالة. الأفضل أن تحدد الخطأ الذي تتوقعه.
مثال غير مفضل:
try:
result = 10 / 0
except:
print("حدث خطأ")
هذا الكود يخفي نوع المشكلة. الأفضل:
try:
result = 10 / 0
except ZeroDivisionError:
print("لا يمكن القسمة على صفر")
{alertSuccess} كلما كان التعامل مع الخطأ محددًا، كان الكود أوضح وأسهل في الإصلاح. لا تلتقط كل الأخطاء إذا كنت تعرف نوع الخطأ المتوقع.
أخطاء شائعة عند حل ZeroDivisionError
- إرجاع صفر دائمًا: قد يخفي مشكلة في البيانات، خصوصًا في النسب والمتوسطات.
- استخدام
exceptبدون نوع الخطأ: يجعل معرفة سبب المشكلة أصعب. - نسيان باقي القسمة: الخطأ قد يظهر مع
%وليس مع/فقط. - فحص الرقم الخطأ: يجب فحص الرقم الموجود بعد علامة القسمة، لا الرقم الأول.
- حساب متوسط قائمة فارغة: لأن
len(list)قد تكون صفرًا.
مراجعة سريعة للحل
قبل أن تستخدم القسمة في بايثون، اسأل نفسك:
- هل المقسوم عليه يمكن أن يكون صفرًا؟
- هل القيمة قادمة من المستخدم؟
- هل القائمة التي أحسب متوسطها قد تكون فارغة؟
- هل أستخدم
/أو//أو%؟ - هل أحتاج إلى شرط قبل القسمة أم إلى
try except؟
روابط مفيدة من بايثون العرب
- شرح try و except في بايثون والتعامل مع الأخطاء
- شرح أخطاء بايثون وطريقة قراءة رسائل الخطأ
- سلسلة مشكلة وحل في بايثون
- كورس أساسيات بايثون للمبتدئين
مصادر خارجية موثوقة
الخلاصة
خطأ ZeroDivisionError: division by zero في بايثون يعني أن البرنامج حاول قسمة رقم على صفر. الحل ليس أن تخفي الخطأ فقط، بل أن تفهم لماذا أصبحت قيمة المقسوم عليه صفرًا.
إذا كانت القيمة واضحة داخل الكود، استخدم شرطًا قبل القسمة. وإذا كانت القيمة قادمة من المستخدم أو من ملف أو من مصدر خارجي، استخدم try و except مع رسائل واضحة. وعند حساب المتوسط أو النسب، انتبه من القوائم الفارغة والقيم التي قد تساوي صفرًا.
{alertSuccess}
الحل العملي: افحص المقسوم عليه قبل القسمة، ولا تستخدم try except لإخفاء المشكلة فقط. اجعل رسالة الخطأ مفهومة، واجعل الكود يمنع القسمة على صفر قبل حدوثها متى أمكن.
أسئلة شائعة
ما سبب خطأ ZeroDivisionError: division by zero؟
السبب هو محاولة قسمة رقم على صفر في بايثون، مثل 10 / 0 أو استخدام متغير قيمته صفر بعد علامة القسمة.
كيف أحل خطأ division by zero في بايثون؟
افحص قيمة المقسوم عليه قبل القسمة باستخدام شرط مثل if divider != 0، أو استخدم try و except ZeroDivisionError إذا كانت القيمة غير مضمونة.
ما معنى float division by zero؟
تعني أن القسمة حدثت باستخدام أعداد عشرية وكان المقسوم عليه صفرًا، مثل 10.0 / 0.0.
لماذا يظهر integer division or modulo by zero؟
تظهر هذه الرسالة غالبًا عند استخدام القسمة الصحيحة // أو باقي القسمة % مع صفر في الجهة اليمنى.
هل أستخدم try except أم if لحل الخطأ؟
استخدم if عندما تستطيع فحص القيمة قبل القسمة، واستخدم try except عندما تكون القيمة قادمة من المستخدم أو من مصدر خارجي وقد تسبب خطأ أثناء التشغيل.
هل يمكن جعل القسمة على صفر تعطي صفرًا؟
يمكنك برمجيًا إرجاع صفر بدل الخطأ، لكن هذا ليس مناسبًا دائمًا. في بعض الحالات الأفضل إرجاع None أو عرض رسالة واضحة حتى لا تخفي مشكلة في البيانات.
July 08, 2026
بايثون العرب
مشكلة وحل 24: حل خطأ TypeError: 'int' object is not iterable في بايثون للمبتدئين
قد تكتب كودًا بسيطًا في بايثون، ثم تفاجأ برسالة خطأ تقول:
TypeError: 'int' object is not iterable
هذا الخطأ يظهر عادة عندما تتعامل مع رقم صحيح int وكأنه مجموعة عناصر يمكن المرور عليها. مثلًا: تحاول استخدام الرقم داخل حلقة for، أو تمرره إلى دالة تنتظر قائمة أو نصًا أو أي كائن قابل للتكرار.
في هذا المقال من سلسلة مشكلة وحل سنشرح معنى الخطأ ببساطة، وما معنى iterable، ولماذا الرقم int ليس قابلًا للتكرار، ثم نعرض أشهر الحالات التي تسبب الخطأ مع الحل الصحيح لكل حالة.
{alertInfo} الفكرة ببساطة: الخطأTypeError: 'int' object is not iterableيعني أنك تحاول المرور على رقم صحيح وكأنه قائمة أو نص. الرقم يمثل قيمة واحدة فقط، وليس مجموعة عناصر يمكن تكرارها داخلfor.
{getToc} $title={محتوى المقال}
ما معنى TypeError: 'int' object is not iterable؟
لفهم الرسالة، قسمها إلى ثلاثة أجزاء:
TypeError: استخدمت نوع بيانات بطريقة غير مناسبة.int object: الكائن الذي سبب المشكلة هو رقم صحيح.is not iterable: هذا الرقم لا يمكن المرور عليه عنصرًا عنصرًا.
في بايثون، بعض الكائنات يمكن تكرارها مثل:
- القوائم
list - النصوص
str - الصفوف
tuple - المجموعات
set - القواميس
dict - الكائنات الناتجة من
range()
لكن الرقم الصحيح int ليس مجموعة عناصر، بل قيمة واحدة، لذلك لا يمكنك أن تطلب من بايثون المرور على الرقم كما تمر على القائمة.
ما معنى iterable في بايثون؟
كلمة iterable تعني كائنًا يمكن المرور عليه عنصرًا بعد عنصر. أبسط مثال هو القائمة:
numbers = [10, 20, 30]
for number in numbers:
print(number)
الناتج:
10
20
30
هنا numbers تحتوي على أكثر من عنصر، لذلك يمكن لـ for المرور عليها.
أما الرقم:
number = 10
فهو قيمة واحدة فقط، ولا يحتوي على عناصر داخلية يمكن المرور عليها. لذلك إذا كتبت:
for item in number:
print(item)
سيظهر الخطأ:
TypeError: 'int' object is not iterable
السبب الأول: استخدام رقم داخل for loop
هذه أكثر حالة شائعة:
count = 5
for i in count:
print(i)
الخطأ هنا أن count رقم صحيح، وليس قائمة أو نطاقًا. إذا كنت تريد تكرار الأمر 5 مرات، استخدم range():
count = 5
for i in range(count):
print(i)
الناتج:
0
1
2
3
4
لاحظ أن range(5) تعطي تسلسلًا يبدأ من 0 وينتهي قبل 5.
متى أستخدم range ومتى أستخدم list؟
استخدم range() عندما تريد التكرار بعدد مرات معين:
for i in range(3):
print("مرحبًا")
واستخدم القائمة عندما تريد المرور على عناصر موجودة فعلًا:
students = ["Ali", "Sara", "Omar"]
for student in students:
print(student)
| الحالة | الاختيار الصحيح |
|---|---|
| أريد تنفيذ كود 10 مرات | range(10) |
| أريد المرور على أسماء الطلاب | for student in students |
| أريد المرور على فهارس القائمة | range(len(items)) عند الحاجة فقط |
| أريد الفهرس والعنصر معًا | enumerate(items) |
{alertWarning} خطأ شائع: لا تكتبfor i in 5. الرقم 5 ليس iterable. اكتبfor i in range(5)إذا كنت تريد تكرار الحلقة 5 مرات.
السبب الثاني: تمرير رقم إلى sum بدل قائمة
الدالة sum() تتوقع كائنًا قابلًا للتكرار مثل قائمة أرقام. لذلك هذا الكود صحيح:
numbers = [10, 20, 30]
print(sum(numbers))
الناتج:
60
لكن هذا الكود خطأ:
number = 10
print(sum(number))
لأن number رقم واحد فقط، وليس قائمة أرقام.
الحل يعتمد على قصدك:
إذا كان لديك رقم واحد فقط
لا تحتاج إلى sum() أصلًا:
number = 10
print(number)
إذا كنت تريد جمع عدة أرقام
ضع الأرقام داخل قائمة:
numbers = [10, 20, 30]
print(sum(numbers))
السبب الثالث: تمرير رقم إلى list
الدالة list() تحول كائنًا قابلًا للتكرار إلى قائمة. لذلك هذا الكود صحيح مع النص:
text = "بايثون"
print(list(text))
الناتج:
['ب', 'ا', 'ي', 'ث', 'و', 'ن']
لكن هذا الكود خطأ:
number = 123
print(list(number))
لأن الرقم 123 ليس iterable.
إذا كنت تريد قائمة تحتوي على الرقم نفسه، اكتب:
number = 123
print([number])
الناتج:
[123]
أما إذا كنت تريد تحويل أرقام العدد إلى عناصر منفصلة، حوّله أولًا إلى نص:
number = 123
digits = list(str(number))
print(digits)
الناتج:
['1', '2', '3']
وإذا أردتها أرقامًا صحيحة بدل نصوص:
number = 123
digits = [int(digit) for digit in str(number)]
print(digits)
الناتج:
[1, 2, 3]
السبب الرابع: نسيان وضع الأرقام داخل قائمة
أحيانًا يكون سبب الخطأ بسيطًا جدًا: تريد التعامل مع مجموعة أرقام، لكنك خزّنت رقمًا واحدًا بدل قائمة.
مثال خاطئ:
scores = 90
for score in scores:
print(score)
الصحيح إذا كانت هناك عدة درجات:
scores = [90, 85, 70]
for score in scores:
print(score)
والصحيح إذا كانت درجة واحدة فقط:
score = 90
print(score)
السبب الخامس: تغيير نوع المتغير بالخطأ
قد يبدأ المتغير كقائمة، ثم تعيد تعيينه إلى رقم دون أن تنتبه.
مثال:
numbers = [10, 20, 30]
# خطأ: هنا غيّرنا numbers من قائمة إلى رقم
numbers = 5
for number in numbers:
print(number)
سيظهر الخطأ لأن numbers لم تعد قائمة. أصبحت رقمًا صحيحًا.
للتأكد من نوع المتغير، استخدم type() أثناء التشخيص:
numbers = 5
print(type(numbers))
الناتج:
<class 'int'>
إذا كنت تتوقع أن يكون المتغير قائمة، فهذا يعني أن هناك سطرًا سابقًا غيّر نوعه.
كيف تفحص هل الكائن iterable؟
للمبتدئين، يكفي أن تتذكر أن القائمة والنص وrange() قابلة للتكرار، بينما الرقم int ليس كذلك.
لكن عند التشخيص، يمكنك تجربة iter() داخل try/except:
def is_iterable(value):
try:
iter(value)
return True
except TypeError:
return False
print(is_iterable([1, 2, 3]))
print(is_iterable(10))
print(is_iterable("بايثون"))
الناتج:
True
False
True
هذا المثال ليس ضروريًا في كل كود، لكنه مفيد لفهم الفكرة: الشيء iterable هو الشيء الذي يمكن استخدام iter() معه.
حلول سريعة حسب سبب الخطأ
| الكود الخاطئ | السبب | الحل الصحيح |
|---|---|---|
for i in 5 |
الرقم ليس iterable | for i in range(5) |
sum(10) |
sum تنتظر كائنًا قابلًا للتكرار |
sum([10]) أو لا تستخدم sum |
list(123) |
الرقم ليس قابلًا للتحويل مباشرة إلى قائمة | [123] أو list(str(123)) |
for x in scores مع scores = 90 |
المتغير رقم وليس قائمة | scores = [90] أو اطبع الرقم مباشرة |
مثال عملي: طباعة درجات الطلاب
لنفترض أنك تريد طباعة درجات الطلاب. إذا كتبت:
scores = 85
for score in scores:
print(score)
سيظهر الخطأ لأن scores رقم واحد.
إذا كان لديك طالب واحد فقط:
score = 85
print(score)
أما إذا كان لديك أكثر من طالب:
scores = [85, 92, 78]
for score in scores:
print(score)
مثال عملي: حساب مجموع الأرقام من 1 إلى رقم معين
قد يكتب المبتدئ:
number = 5
print(sum(number))
وهذا يسبب الخطأ. إذا كنت تريد جمع الأرقام من 1 إلى 5، استخدم range():
number = 5
total = sum(range(1, number + 1))
print(total)
الناتج:
15
لأن range(1, 6) يمثل الأرقام: 1 و2 و3 و4 و5.
خطوات تشخيص الخطأ بسرعة
- اقرأ السطر الذي ظهر في رسالة الخطأ.
- ابحث عن مكان استخدام
forأوsum()أوlist()أو أي دالة تنتظر iterable. - اطبع نوع المتغير باستخدام
type(variable). - إذا كان النوع
int، اسأل نفسك: هل كنت أحتاج رقمًا واحدًا أم قائمة؟ - إذا كنت تريد تكرار عدد مرات، استخدم
range(number). - إذا كنت تريد مجموعة قيم، ضعها داخل قائمة مثل
[1, 2, 3]. - إذا كنت تريد تحويل رقم إلى أرقامه، استخدم
str(number)أولًا.
أخطاء قريبة من هذا الخطأ
| الخطأ | المعنى | مثال |
|---|---|---|
TypeError: 'int' object is not iterable |
تحاول المرور على رقم صحيح. | for i in 5 |
TypeError: 'NoneType' object is not iterable |
تحاول المرور على قيمة None. |
for x in result عندما تكون result = None |
TypeError: 'float' object is not iterable |
تحاول المرور على رقم عشري. | for x in 3.5 |
TypeError: object is not subscriptable |
تحاول استخدام الفهرسة مع كائن لا يدعمها. | number[0] |
روابط داخلية مفيدة من بايثون العرب
- سلسلة مشكلة وحل في بايثون
- شرح القوائم Lists في بايثون للمبتدئين
- شرح الحلقات Loops في بايثون للمبتدئين
- حل خطأ TypeError: object is not subscriptable في بايثون
- كورس أساسيات بايثون للمبتدئين
الخلاصة
- خطأ
TypeError: 'int' object is not iterableيعني أنك تعاملت مع رقم وكأنه مجموعة عناصر. - الرقم
intليس قابلًا للتكرار داخلfor. - إذا أردت التكرار عدد مرات، استخدم
range(). - إذا أردت المرور على مجموعة أرقام، ضعها داخل قائمة.
- إذا أردت استخدام
sum()، مرر لها قائمة أوrange()أو أي iterable مناسب. - إذا أردت تحويل رقم إلى قائمة تحتوي على الرقم نفسه، استخدم
[number]. - إذا أردت تحويل رقم إلى أرقامه، استخدم
list(str(number))أو List Comprehension.
{alertSuccess} قاعدة ذهبية: قبل كتابةfor x in variableاسأل نفسك: هلvariableيحتوي على عناصر متعددة؟ إذا كان رقمًا واحدًا، فلن يعمل. استخدمrange()أو قائمة حسب هدفك.
مصادر خارجية رسمية للتوسع
- TypeError — توثيق بايثون الرسمي
- iter() — الدوال المدمجة في بايثون
- range() — الدوال المدمجة في بايثون
- sum() — الدوال المدمجة في بايثون
أسئلة شائعة
ما معنى TypeError: 'int' object is not iterable؟
يعني أنك حاولت استخدام رقم صحيح int في مكان يحتاج كائنًا قابلًا للتكرار، مثل قائمة أو نص أو range().
لماذا لا يعمل for i in 5؟
لأن الرقم 5 ليس تسلسلًا من العناصر. إذا أردت تنفيذ الحلقة 5 مرات، اكتب for i in range(5).
كيف أجعل رقمًا واحدًا داخل قائمة؟
اكتب الرقم بين أقواس مربعة مثل [number]. هذا ينشئ قائمة تحتوي على عنصر واحد.
كيف أحول الرقم 123 إلى قائمة أرقام؟
حوّله أولًا إلى نص ثم مر على أرقامه: [int(digit) for digit in str(123)]، وستحصل على [1, 2, 3].
ما الفرق بين int و iterable؟
int يمثل رقمًا واحدًا، أما iterable فهو كائن يحتوي على عناصر يمكن المرور عليها مثل list وstr وrange.
مشكلة وحل 23: حل خطأ TypeError: unsupported operand type(s) في بايثون للمبتدئين
من أكثر الأخطاء التي تظهر للمبتدئين عند تنفيذ العمليات الحسابية أو دمج القيم في بايثون خطأ:
TypeError: unsupported operand type(s).
هذا الخطأ لا يعني أن بايثون لا يفهم العملية، بل يعني غالبًا أنك طلبت منه تنفيذ عملية بين نوعين من البيانات لا يمكن استخدامهما معًا بهذه الطريقة.
{alertInfo} الفكرة ببساطة: يظهر خطأunsupported operand type(s)عندما تستخدم عاملًا مثل+أو-أو*بين قيمتين لا يناسبهما هذا العامل.
{getToc} $title={محتوى المقال}
ما معنى TypeError في بايثون؟
خطأ TypeError يعني أن هناك مشكلة في نوع البيانات المستخدم داخل العملية. فمثلًا، قد تحاول جمع رقم مع نص، أو طرح قائمة من رقم، أو ضرب نوعين لا يدعم كل منهما هذه العملية.
إذا كنت لا تزال في بداية تعلمك، أنصحك بمراجعة درس
أنواع البيانات في بايثون
لأنه يساعدك على فهم الفرق بين int و float و str و list.
حسب توثيق بايثون الرسمي، يتم رفع خطأ TypeError عندما تُطبّق عملية أو دالة على كائن من نوع غير مناسب:
توثيق TypeError الرسمي.
شكل رسالة الخطأ
قد تظهر رسالة الخطأ بهذا الشكل:
TypeError: unsupported operand type(s) for +: 'int' and 'str'
ومعناها: لا يمكن استخدام العامل + بين قيمة من نوع int وقيمة من نوع str.
مثال خاطئ: جمع رقم مع نص
هذا المثال من أشهر أسباب الخطأ:
age = 25
message = "عمري هو " + age
print(message)
الكود السابق خطأ لأن المتغير age رقم، بينما النص "عمري هو " من نوع str.
بايثون لا يستطيع دمج نص مع رقم باستخدام + مباشرة.
الحل هو تحويل الرقم إلى نص باستخدام str():
age = 25
message = "عمري هو " + str(age)
print(message)
ويمكنك أيضًا استخدام f-string وهي طريقة أوضح:
age = 25
print(f"عمري هو {age}")
{alertWarning}
انتبه: العامل + يستخدم للجمع بين الأرقام، ويستخدم أيضًا لدمج النصوص، لكنه لا يدمج رقمًا مع نص إلا بعد تحويل أحدهما.
مثال خاطئ: جمع مدخلات المستخدم كأنها أرقام
عند استخدام input() في بايثون، فإن القيمة الناتجة تكون نصًا حتى لو كتب المستخدم رقمًا.
num1 = input("أدخل الرقم الأول: ")
num2 = input("أدخل الرقم الثاني: ")
result = num1 + num2
print(result)
إذا أدخل المستخدم 5 و 3، قد تتوقع أن النتيجة ستكون 8، لكنها ستكون:
53
لأن القيمتين نصوص، وليس أرقامًا. والحل هو تحويلهما إلى أرقام:
num1 = int(input("أدخل الرقم الأول: "))
num2 = int(input("أدخل الرقم الثاني: "))
result = num1 + num2
print(result)
للمزيد عن التحويل إلى رقم، يمكنك الرجوع إلى توثيق int و float في بايثون.
متى أستخدم int ومتى أستخدم float؟
استخدم int() عندما تريد رقمًا صحيحًا بدون فاصلة عشرية، مثل العمر أو عدد المنتجات.
واستخدم float() عندما تتوقع رقمًا عشريًا، مثل السعر أو الوزن أو النسبة.
| الدالة | الاستخدام | مثال |
|---|---|---|
int() |
تحويل القيمة إلى عدد صحيح | int("10") |
float() |
تحويل القيمة إلى عدد عشري | float("10.5") |
str() |
تحويل القيمة إلى نص | str(25) |
مثال خاطئ: طرح نص من رقم
الطرح لا يعمل إلا مع أنواع مناسبة مثل الأرقام:
price = 100
discount = "20"
final_price = price - discount
print(final_price)
هذا الكود سيؤدي إلى خطأ لأن discount نص وليس رقمًا.
الحل:
price = 100
discount = "20"
final_price = price - int(discount)
print(final_price)
إذا أردت تدريبًا عمليًا قريبًا من هذا المثال، راجع مقال حساب الخصم والسعر النهائي في بايثون.
مثال خاطئ: قسمة نص على رقم
في بعض الأحيان تكون القيمة مخزنة كنص بسبب قراءة من ملف أو إدخال من المستخدم:
total = "100"
count = 4
average = total / count
print(average)
الحل هو تحويل النص إلى رقم قبل القسمة:
total = "100"
count = 4
average = int(total) / count
print(average)
استخدام type لمعرفة نوع القيمة
قبل أن تصلح الخطأ، يجب أن تعرف نوع القيم التي تتعامل معها.
يمكنك استخدام type() لمعرفة نوع أي قيمة:
value = "25"
print(type(value))
الناتج سيكون:
<class 'str'>
أي أن القيمة نص وليست رقمًا. يمكنك قراءة المزيد عن type() من
توثيق بايثون الرسمي.
استخدام isinstance للتحقق من النوع
إذا أردت التحقق من نوع القيمة قبل تنفيذ العملية، يمكنك استخدام isinstance().
value = 50
if isinstance(value, int):
print("القيمة رقم صحيح")
else:
print("القيمة ليست رقمًا صحيحًا")
وتفيدك هذه الطريقة عندما يكون لديك بيانات غير مضمونة المصدر. يمكنك الرجوع إلى توثيق isinstance الرسمي للتفاصيل.
{alertSuccess}
قاعدة عملية: قبل تنفيذ عملية حسابية، تأكد أن القيم أرقام. وقبل دمج نصوص، تأكد أن القيم نصوص أو حوّلها باستخدام str().
الفرق بين TypeError و ValueError
قد تختلط عليك أخطاء TypeError و ValueError.
الفرق ببساطة:
TypeError: النوع نفسه غير مناسب للعملية.ValueError: النوع مناسب، لكن القيمة لا يمكن تحويلها أو استخدامها.
مثال على TypeError:
result = 10 + "5"
هنا المشكلة أن أحد الطرفين رقم والآخر نص.
مثال على ValueError:
number = int("abc")
هنا int() تقبل نصًا قابلًا للتحويل إلى رقم، لكن القيمة "abc" ليست رقمًا.
ولفهم هذا الخطأ أكثر، يمكنك مراجعة مقال
حل خطأ ValueError: invalid literal for int.
استخدام try/except بطريقة آمنة
عند التعامل مع إدخال المستخدم، لا تعتمد على أن المستخدم سيكتب رقمًا دائمًا. يمكنك استخدام try/except لمعالجة الخطأ بطريقة منظمة.
try:
price = float(input("أدخل السعر: "))
tax = 0.15
total = price + (price * tax)
print("السعر بعد الضريبة:", total)
except ValueError:
print("الرجاء إدخال رقم صحيح")
في هذا المثال استخدمنا ValueError لأن الخطأ المتوقع غالبًا سيحدث أثناء تحويل إدخال المستخدم إلى رقم.
أما TypeError فيحدث عادة عندما تنفذ عملية بين أنواع غير متوافقة.
لمراجعة التعامل مع الأخطاء بشكل أوسع، يمكنك قراءة درس try except في بايثون.
جدول سريع: سبب الخطأ والحل
| الكود الخاطئ | سبب الخطأ | الحل |
|---|---|---|
10 + "5" |
جمع رقم مع نص | 10 + int("5") |
"Age: " + 25 |
دمج نص مع رقم | "Age: " + str(25) |
100 - "20" |
طرح نص من رقم | 100 - int("20") |
"100" / 4 |
قسمة نص على رقم | int("100") / 4 |
مثال عملي كامل
في هذا المثال سنحسب السعر النهائي بعد الخصم، مع تحويل القيم إلى أرقام بطريقة صحيحة:
price_text = "150"
discount_text = "25"
price = float(price_text)
discount = float(discount_text)
final_price = price - discount
print(f"السعر النهائي هو: {final_price}")
هنا حوّلنا النصوص إلى أرقام قبل تنفيذ عملية الطرح، لذلك لن يظهر خطأ unsupported operand type(s).
{alertInfo}
ملاحظة مهمة: لا تحفظ الأرقام كنصوص إلا إذا كنت تحتاجها كنص فعلًا. وإذا كانت القيمة قادمة من input() أو ملف، فحوّلها إلى النوع المناسب قبل الحساب.
أخطاء قريبة قد تواجهك
إذا ظهر لك خطأ قريب من هذا الخطأ، فقد تساعدك هذه المقالات:
- حل خطأ can only concatenate str not int to str
- حل خطأ ValueError عند تحويل النص إلى رقم
- حل خطأ KeyError في القواميس
- حل خطأ UnboundLocalError في بايثون
ملخص المقال
خطأ TypeError: unsupported operand type(s) يظهر عندما تستخدم عاملًا مثل الجمع أو الطرح أو القسمة بين أنواع بيانات لا تقبل هذه العملية معًا.
المشكلة ليست في العامل نفسه، بل في نوع القيم التي تستخدمها.
لتجنب الخطأ:
- افهم نوع كل قيمة قبل استخدامها.
- استخدم
type()عند الحاجة لمعرفة النوع. - حوّل النصوص الرقمية إلى أرقام باستخدام
int()أوfloat(). - حوّل الأرقام إلى نصوص باستخدام
str()عند دمجها مع نصوص. - استخدم
isinstance()إذا أردت التحقق من النوع قبل العملية. - استخدم
try/exceptمع إدخال المستخدم أو البيانات غير المضمونة.
{alertSuccess}
الخلاصة العملية: عندما يظهر لك خطأ unsupported operand type(s)، اسأل نفسك: ما نوع القيمة الأولى؟ وما نوع القيمة الثانية؟ وهل هذا العامل مناسب لهذين النوعين؟
أسئلة شائعة
ما معنى TypeError: unsupported operand type(s) في بايثون؟
يعني أنك استخدمت عاملًا مثل + أو - أو / بين نوعين لا يدعمان هذه العملية معًا.
لماذا يظهر الخطأ عند جمع رقم مع نص؟
لأن الرقم من نوع int أو float، بينما النص من نوع str. يجب تحويل أحدهما حسب الهدف: إما تحويل النص إلى رقم للحساب، أو تحويل الرقم إلى نص للدمج.
هل input يعيد رقمًا في بايثون؟
لا. الدالة input() تعيد نصًا دائمًا، حتى لو كتب المستخدم أرقامًا. لذلك تحتاج إلى int() أو float() قبل العمليات الحسابية.
ما الفرق بين TypeError و ValueError؟
TypeError يعني أن نوع البيانات غير مناسب للعملية، بينما ValueError يعني أن النوع مقبول لكن القيمة نفسها غير صالحة، مثل محاولة تحويل "abc" إلى رقم.
كيف أعرف نوع المتغير في بايثون؟
استخدم الدالة type():
value = "10"
print(type(value))
ما أفضل حل لخطأ unsupported operand type(s)؟
أفضل حل هو معرفة نوع القيم أولًا، ثم تحويلها إلى النوع المناسب قبل العملية. لا تعتمد على التخمين، واستخدم type() أو isinstance() عند الحاجة.
July 07, 2026
بايثون العرب
أساسيات بايثون 37: شرح تعدد الأشكال للمبتدئين
بعد أن تعلمت معنى class وobject والوراثة، ستلاحظ أن عدة أنواع من الكائنات قد تشترك في سلوك عام، لكن كل نوع ينفذه بطريقته. القط والكلب والطائر كلها حيوانات، ويمكن أن نطلب من كل واحد إصدار صوت، لكن النتيجة تختلف. هذه الفكرة تسمى تعدد الأشكال.
المقصود ليس أن نكتب دوال كثيرة بأسماء مختلفة، بل أن نستخدم اسم دالة موحدًا مثل make_sound() أو move()، ثم يدع كل كائن ينفذ السلوك المناسب لنوعه.
{alertInfo}
الفكرة ببساطة: عند استدعاء object.make_sound()، قد يطبع القط "مواء" والكلب "نباح" والطائر "زقزقة". اسم الدالة واحد، لكن النتيجة تعتمد على نوع الكائن.
{getToc} $title={محتوى المقال}
ما هو تعدد الأشكال في بايثون؟
تعدد الأشكال، أو Polymorphism، هو استخدام واجهة أو استدعاء موحد مع أنواع مختلفة من الكائنات. كل كائن يستجيب بما يناسبه، بينما يبقى الكود الذي يستدعي الدالة بسيطًا.
سبق أن استخدمت فكرة مشابهة حتى قبل دراسة الكلاسات. انظر إلى len():
print(len("بايثون"))
print(len([10, 20, 30]))
print(len({"name": "علي", "score": 90}))
الدالة len() تعمل مع النص والقائمة والقاموس، لكنها تعيد العدد المناسب لكل نوع. في الكلاسات سنطبق المبدأ نفسه، لكننا سنصمم الدوال بأنفسنا.
قبل البدء
هذا الدرس يبني على الدروس السابقة في البرمجة الكائنية، لذلك راجعها عند الحاجة:
- أساسيات بايثون 34: شرح الكلاس والكائنات
- أساسيات بايثون 35: الفرق بين خصائص الكلاس وخصائص الكائن
- أساسيات بايثون 36: شرح الوراثة للمبتدئين
أول مثال: حيوانات تصدر أصواتًا مختلفة
سننشئ كلاسًا أبًا باسم Animal، ثم نرث منه ثلاثة كلاسات: Cat وDog وBird. كل كلاس ابن يعيد تعريف الدالة make_sound() بطريقته.
class Animal:
def make_sound(self):
print("صوت عام لحيوان")
class Cat(Animal):
def make_sound(self):
print("مواء")
class Dog(Animal):
def make_sound(self):
print("نباح")
class Bird(Animal):
def make_sound(self):
print("زقزقة")
cat = Cat()
dog = Dog()
bird = Bird()
cat.make_sound()
dog.make_sound()
bird.make_sound()
الناتج:
مواء
نباح
زقزقة
الاستدعاء واحد في كل مرة: make_sound(). لكن بايثون تنفذ الدالة الموجودة في الكلاس الحقيقي للكائن. كائن القط ينفذ نسخة Cat، وكائن الكلب ينفذ نسخة Dog.
تعديل الدالة الموروثة
في المثال السابق، كتبت الكلاسات الأبناء دالة بالاسم نفسه الموجود في الكلاس الأب. هذا يسمى تعديل الدالة الموروثة أو Method Overriding.
| الكلاس | الدالة الموجودة داخله | النتيجة عند الاستدعاء |
|---|---|---|
Animal |
make_sound() |
صوت عام لحيوان. |
Cat |
make_sound() |
مواء. |
Dog |
make_sound() |
نباح. |
Bird |
make_sound() |
زقزقة. |
تعديل الدالة الموروثة ليس هو تعدد الأشكال نفسه، لكنه طريقة مهمة للوصول إليه: نكتب سلوكًا خاصًا لكل كلاس، ثم نستفيد من الاسم الموحد للدالة عند استخدام الكائنات معًا.
القائمة الواحدة: أين تظهر الفائدة الحقيقية؟
بدل استدعاء كل كائن يدويًا، ضع الكائنات المختلفة داخل قائمة ثم استخدم حلقة واحدة:
animals = [Cat(), Dog(), Bird()]
for animal in animals:
animal.make_sound()
الناتج يبقى:
مواء
نباح
زقزقة
الحلقة لا تحتاج إلى معرفة هل الكائن قط أو كلب أو طائر؛ فهي تعرف أن كل عنصر يجب أن يقدم الدالة make_sound(). وإذا أضفت كلاسًا جديدًا باسم Horse يملك الدالة نفسها، فلن تحتاج إلى تعديل الحلقة.
class Horse(Animal):
def make_sound(self):
print("صهيل")
animals.append(Horse())
مثال عملي: حساب راتب أنواع مختلفة من الموظفين
هذا المثال أقرب إلى تطبيقات العمل. الموظف بدوام كامل والموظف بالساعات كلاهما يحتاج إلى دالة calculate_salary()، لكن طريقة الحساب مختلفة.
class Employee:
def calculate_salary(self):
raise NotImplementedError(
"يجب أن يكتب كل نوع موظف طريقة حساب راتبه."
)
class FullTimeEmployee(Employee):
def __init__(self, monthly_salary):
self.monthly_salary = monthly_salary
def calculate_salary(self):
return self.monthly_salary
class HourlyEmployee(Employee):
def __init__(self, hours, hourly_rate):
self.hours = hours
self.hourly_rate = hourly_rate
def calculate_salary(self):
return self.hours * self.hourly_rate
employees = [
FullTimeEmployee(900),
HourlyEmployee(30, 12),
]
for employee in employees:
print(employee.calculate_salary())
الناتج:
900
360
لاحظ أن الحلقة تستدعي calculate_salary() فقط، ولا تحتاج إلى استخدام سلسلة شروط لمعرفة نوع الموظف. كل كلاس مسؤول عن تنفيذ حسابه الخاص.
مثال آخر: وسائل نقل تتحرك بأساليب مختلفة
class Vehicle:
def move(self):
print("المركبة تتحرك")
class Car(Vehicle):
def move(self):
print("السيارة تسير على الطريق")
class Boat(Vehicle):
def move(self):
print("القارب يبحر في الماء")
class Plane(Vehicle):
def move(self):
print("الطائرة تطير في الجو")
vehicles = [Car(), Boat(), Plane()]
for vehicle in vehicles:
vehicle.move()
هنا الدالة الموحدة هي move(). يختلف التنفيذ، لكن طريقة الاستخدام تظل واضحة.
هل تحتاج إلى الوراثة دائمًا؟
الوراثة طريقة سهلة لفهم تعدد الأشكال، لكنها ليست الطريق الوحيد في بايثون. أحيانًا يكفي أن يوفّر الكائن الدالة التي تحتاجها، حتى لو لم يرث من كلاس مشترك.
class EmailNotification:
def send(self, message):
print(f"إرسال بريد إلكتروني: {message}")
class ConsoleNotification:
def send(self, message):
print(f"عرض في الطرفية: {message}")
def notify(channel, message):
channel.send(message)
notify(EmailNotification(), "تم حفظ التقرير.")
notify(ConsoleNotification(), "تم حفظ التقرير.")
الدالة notify() لا تسأل عن النوع. يكفي أن الكائن الذي تستقبله يوفر الدالة send().
{alertWarning} تنبيه: لا تجعل كل الكلاسات في مشروعك ترث من كلاس أب فقط لأنك تريد دالة مشتركة. استخدم الوراثة عندما توجد علاقة منطقية فعلًا، مثل القط نوع من الحيوان. أما الكائنات غير المرتبطة فقد يكفي أن تتفق على السلوك المطلوب.
استخدام super عند تعديل الدالة
أحيانًا لا تريد استبدال سلوك الكلاس الأب بالكامل. تريد تشغيل جزء منه ثم إضافة تفاصيل خاصة بالكلاس الابن. هنا تستخدم super().
class Person:
def introduce(self):
print("مرحبًا، أنا شخص.")
class Student(Person):
def introduce(self):
super().introduce()
print("وأنا أتعلم بايثون.")
student = Student()
student.introduce()
الناتج:
مرحبًا، أنا شخص.
وأنا أتعلم بايثون.
الكلاس Student عدّل الدالة introduce()، لكنه احتفظ بسلوك الأب أولًا عبر super().introduce().
الفرق بين تعديل الدالة الموروثة وتعدد الأشكال
| المفهوم | معناه | مثال |
|---|---|---|
| تعديل الدالة الموروثة | الكلاس الابن يكتب دالة بالاسم نفسه الموجود في الكلاس الأب ليغير تنفيذها. | Dog.make_sound() بدل النسخة العامة داخل Animal. |
| تعدد الأشكال | نستخدم الاستدعاء نفسه مع كائنات مختلفة، فتظهر نتيجة مناسبة لكل نوع. | حلقة تستدعي make_sound() على قط وكلب وطائر. |
أخطاء شائعة
1. تغيير اسم الدالة في كل كلاس
إذا كانت دالة القط اسمها meow() ودالة الكلب اسمها bark()، فلن تملك واجهة موحدة. في المثال نستخدم make_sound() للجميع، ثم نغير التنفيذ الداخلي فقط.
2. اختلاف معلمات الدالة بلا سبب
حاول أن تكون الدوال المتشابهة متناسقة. إذا كانت send() تستقبل message في كلاس، فلا تجعلها تحتاج قيمًا مختلفة تمامًا في كلاس آخر إلا عند وجود سبب واضح في تصميم المشروع.
3. كتابة شروط كثيرة لفحص النوع
عندما تكتب سلسلة طويلة من isinstance() فقط لتحديد السلوك، فقد تكون الكلاسات بحاجة إلى دالة مشتركة. لا يعني ذلك أن isinstance() ممنوعة، لكنها ليست الحل الأول دائمًا.
4. استخدام super دون حاجة
استخدم super() عندما تحتاج فعلًا إلى جزء من سلوك الأب. أما إذا كان الكلاس الابن يملك تنفيذًا مختلفًا بالكامل، فلا تحتاج إلى استدعاء دالة الأب.
5. الخلط بين الكلاس والكائن
الكلاس هو القالب مثل Dog، أما الكائن فهو نسخة مثل dog = Dog(). نستدعي الدالة على الكائن: dog.make_sound().
تمرين عملي
أنشئ كلاس أبًا باسم Shape يحتوي على دالة get_area(). ثم أنشئ كلاسَين:
Rectangleيحسب المساحة من الطول والعرض.Squareيحسب المساحة من طول الضلع.
ضع كائنات من النوعين داخل قائمة، ثم استخدم حلقة واحدة لطباعة مساحة كل شكل عبر get_area().
حل مقترح
class Shape:
def get_area(self):
raise NotImplementedError(
"يجب أن يكتب كل شكل طريقة حساب مساحته."
)
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def get_area(self):
return self.width * self.height
class Square(Shape):
def __init__(self, side):
self.side = side
def get_area(self):
return self.side * self.side
shapes = [
Rectangle(5, 3),
Square(4),
]
for shape in shapes:
print(shape.get_area())
الناتج:
15
16
روابط داخلية مفيدة من بايثون العرب
- أساسيات بايثون 34: شرح الكلاس والكائنات
- أساسيات بايثون 35: الفرق بين خصائص الكلاس وخصائص الكائن
- أساسيات بايثون 36: شرح الوراثة
- أساسيات بايثون 13: شرح الدوال
- أساسيات بايثون 14: شرح return في الدوال
- محرر بايثون أونلاين لتجربة الأمثلة
الخلاصة
- تعدد الأشكال يعني استخدام استدعاء موحد مع كائنات مختلفة، فتظهر نتيجة مناسبة لكل نوع.
- يمكن أن تملك عدة كلاسات دالة بالاسم نفسه مثل
make_sound()أوmove(). - تعديل دالة موروثة داخل الكلاس الابن طريقة شائعة لإنشاء سلوك مختلف.
- تظهر الفائدة عندما تضع كائنات مختلفة في قائمة واحدة وتستدعي الدالة نفسها داخل حلقة.
- يساعد
super()على استدعاء جزء من سلوك الكلاس الأب عند الحاجة. - استخدم الوراثة عندما توجد علاقة منطقية، ولا تعتمد على فحص الأنواع بالشروط عندما يمكن أن توفر دالة موحدة.
{alertSuccess} قاعدة عملية: عندما تجد نفسك تسأل عن نوع الكائن كل مرة قبل تنفيذ السلوك، فكّر: هل يمكن أن أضع هذا السلوك داخل دالة موحدة مثلsend()أوget_area()وأدع كل كلاس ينفذها بطريقته؟
مصادر رسمية للتوسع
أسئلة شائعة
ما معنى تعدد الأشكال في بايثون؟
هو استخدام الدالة أو الواجهة نفسها مع كائنات مختلفة، بحيث ينفذ كل كائن السلوك المناسب لنوعه. مثل تشغيل make_sound() على قط وكلب وطائر.
هل تعدد الأشكال هو نفسه الوراثة؟
لا. الوراثة تسمح لك بإنشاء كلاس ابن يستفيد من كلاس أب. أما تعدد الأشكال فيتعلق باستخدام واجهة أو دالة موحدة مع كائنات قد تنفذها بطرق مختلفة. الوراثة طريقة شائعة للوصول إليه، لكنها ليست الوحيدة في بايثون.
ما هو تعديل الدالة الموروثة؟
هو كتابة دالة داخل الكلاس الابن بالاسم نفسه الموجود في الكلاس الأب، حتى يوفر الابن تنفيذًا أكثر تحديدًا. مثل كتابة make_sound() داخل Dog بدل استخدام النسخة العامة في Animal.
متى أستخدم super داخل دالة معدلة؟
استخدمها عندما تحتاج إلى تشغيل جزء من الدالة الموجودة في الكلاس الأب ثم إضافة سلوك خاص بالكلاس الابن. لا تحتاج إليها عندما تريد استبدال السلوك بالكامل.
هل يجب أن ترث كل الكلاسات من كلاس واحد؟
لا. في بايثون يمكن لدالة أن تعمل مع أي كائن يوفّر السلوك الذي تحتاجه، مثل كائن لديه دالة send(). لكن الوراثة مفيدة عندما توجد علاقة حقيقية وواضحة بين الأنواع.
بايثون بالمثال 17: إنشاء برنامج اختبار أسئلة وأجوبة وحساب النتيجة
بعد أن تتعلم القوائم والقواميس والحلقات والشروط، تحتاج إلى مشروع صغير يجمعها في برنامج واحد مفهوم. في هذا المثال سننشئ برنامج اختبار أسئلة وأجوبة: يعرض سؤالًا مع عدة خيارات، يقرأ إجابة المستخدم، يتحقق من صحتها، ثم يحسب النتيجة والنسبة المئوية في النهاية.
الفكرة مناسبة جدًا للتجربة داخل محرر بايثون أو Terminal، ويمكنك تطويرها لاحقًا بإضافة أسئلة جديدة أو مستويات مختلفة أو إعادة تشغيل الاختبار.
{alertInfo}
الفكرة ببساطة: سنخزن كل سؤال وخياراته والإجابة الصحيحة داخل قاموس، ثم نضع كل القواميس داخل قائمة. بعد ذلك نمر على الأسئلة بحلقة for، ونقارن إجابة المستخدم بالإجابة الصحيحة لزيادة النتيجة عند النجاح.
{getToc} $title={محتوى المقال}
ماذا سنتعلم من هذا المثال؟
- تنظيم بيانات متعددة داخل
listمن القواميس. - عرض الأسئلة والخيارات باستخدام حلقة
for. - استقبال إجابة المستخدم عبر
input(). - توحيد شكل الإدخال باستخدام
strip()وlower(). - التحقق من صحة الإجابة باستخدام
if. - زيادة عداد النتيجة عند الإجابة الصحيحة.
- حساب نسبة النتيجة وإظهار رسالة مناسبة للمستخدم.
كيف نفكر في شكل بيانات الاختبار؟
لكل سؤال نحتاج إلى نص السؤال، والخيارات، وحرف الإجابة الصحيحة، وشرح قصير يظهر عند الإجابة الخاطئة. القاموس مناسب لتجميع هذه المعلومات في مكان واحد، والقائمة مناسبة لأنها تحفظ عدة أسئلة بالترتيب.
question = {
"question": "أي كلمة نستخدمها لتعريف دالة في بايثون؟",
"options": {
"a": "def",
"b": "loop",
"c": "return",
},
"answer": "a",
"explanation": "نستخدم def قبل اسم الدالة.",
}
هنا المفتاح question يحمل نص السؤال، وoptions يحمل قاموسًا صغيرًا للخيارات، بينما answer يحدد حرف الإجابة الصحيح.
النسخة الأولى: سؤال واحد فقط
ابدأ دائمًا بنسخة صغيرة. هذا البرنامج يعرض سؤالًا واحدًا ويخبر المستخدم هل كانت إجابته صحيحة:
correct_answer = "a"
print("أي كلمة نستخدمها لتعريف دالة في بايثون؟")
print("A) def")
print("B) loop")
print("C) return")
user_answer = input("اكتب حرف الإجابة: ").strip().lower()
if user_answer == correct_answer:
print("إجابة صحيحة ✓")
else:
print("إجابة غير صحيحة. الإجابة هي A.")
استخدمنا strip() لإزالة المسافات الزائدة، وlower() لتحويل الحرف إلى صغير. لذلك لو كتب المستخدم A أو a أو أضاف مسافة قبلها أو بعدها، ستبقى المقارنة صحيحة.
لماذا نضع الأسئلة في قائمة من القواميس؟
لو كتبنا كل سؤال في مجموعة أوامر print() منفصلة، سيصبح البرنامج طويلًا وصعب التعديل عند إضافة أسئلة جديدة. أما القائمة من القواميس فتسمح لك بإضافة سؤال كامل في بضعة أسطر فقط.
questions = [
{
"question": "أي كلمة نستخدمها لتعريف دالة في بايثون؟",
"options": {
"a": "def",
"b": "loop",
"c": "return",
},
"answer": "a",
"explanation": "نستخدم def قبل اسم الدالة.",
},
{
"question": "ما الدالة التي تعيد عدد عناصر القائمة؟",
"options": {
"a": "sum()",
"b": "len()",
"c": "print()",
},
"answer": "b",
"explanation": "تستخدم len() لمعرفة عدد العناصر.",
},
]
عرض الخيارات باستخدام حلقة for
بداخل كل سؤال يوجد قاموس للخيارات. نستطيع المرور على الحرف والنص معًا باستخدام items():
for letter, option in item["options"].items():
print(f"{letter.upper()}) {option}")
إذا كان letter يساوي "a" وoption يساوي "def"، سيظهر للمستخدم A) def.
راجع بايثون بالمثال 10: البحث عن عنصر داخل قائمة والفرق بين القائمة والقاموس والمجموعة والصف إذا احتجت إلى مراجعة القوائم والقواميس قبل متابعة التطبيق.
الكود الكامل لبرنامج الاختبار
انسخ الكود التالي إلى ملف باسم quiz_app.py، ثم شغّله من Terminal أو داخل محرر بايثون:
def normalize_answer(answer):
return answer.strip().lower()
def get_feedback(percentage):
if percentage == 100:
return "نتيجة ممتازة! أجبت عن كل الأسئلة بشكل صحيح."
if percentage >= 70:
return "نتيجة جيدة جدًا. راجع الأسئلة التي أخطأت فيها."
if percentage >= 40:
return "بداية جيدة. أعد قراءة الشرح ثم جرّب مرة أخرى."
return "لا بأس. تعلّم من الإجابات الصحيحة وحاول من جديد."
def run_quiz(questions):
score = 0
print("=" * 45)
print("اختبار بايثون للمبتدئين")
print("=" * 45)
for number, item in enumerate(questions, start=1):
print(f"\nالسؤال {number}: {item['question']}")
for letter, option in item["options"].items():
print(f"{letter.upper()}) {option}")
user_answer = normalize_answer(
input("اكتب حرف الإجابة الصحيحة: ")
)
while user_answer not in item["options"]:
print("اختر حرفًا موجودًا من الخيارات المعروضة.")
user_answer = normalize_answer(
input("اكتب حرف الإجابة الصحيحة: ")
)
if user_answer == item["answer"]:
score += 1
print("إجابة صحيحة ✓")
else:
correct_answer = item["answer"].upper()
print(f"إجابة غير صحيحة. الإجابة الصحيحة: {correct_answer}")
print(f"الشرح: {item['explanation']}")
total_questions = len(questions)
percentage = (score / total_questions) * 100
print("\n" + "=" * 45)
print(f"نتيجتك: {score} من {total_questions}")
print(f"النسبة: {percentage:.0f}%")
print(get_feedback(percentage))
questions = [
{
"question": "أي كلمة نستخدمها لتعريف دالة في بايثون؟",
"options": {
"a": "def",
"b": "loop",
"c": "return",
},
"answer": "a",
"explanation": "نستخدم def قبل اسم الدالة.",
},
{
"question": "ما الدالة التي تعيد عدد عناصر القائمة؟",
"options": {
"a": "sum()",
"b": "len()",
"c": "print()",
},
"answer": "b",
"explanation": "تستخدم len() لمعرفة عدد العناصر.",
},
{
"question": "أي تعليمة نستخدمها لاتخاذ قرار بناءً على شرط؟",
"options": {
"a": "if",
"b": "import",
"c": "break",
},
"answer": "a",
"explanation": "تستخدم if لفحص شرط وتنفيذ كود عند تحققه.",
},
]
run_quiz(questions)
مثال على نتيجة التشغيل
=============================================
اختبار بايثون للمبتدئين
=============================================
السؤال 1: أي كلمة نستخدمها لتعريف دالة في بايثون؟
A) def
B) loop
C) return
اكتب حرف الإجابة الصحيحة: a
إجابة صحيحة ✓
...
=============================================
نتيجتك: 2 من 3
النسبة: 67%
بداية جيدة. أعد قراءة الشرح ثم جرّب مرة أخرى.
شرح البرنامج خطوة بخطوة
1. توحيد إجابة المستخدم
def normalize_answer(answer):
return answer.strip().lower()
لو كتب المستخدم مسافة قبل الحرف أو بعده، تحذفها strip(). ولو كتب الحرف كبيرًا مثل A، تحوله lower() إلى a. هذا يجعل التعامل مع الإدخال أسهل بدل رفض إجابة صحيحة بسبب شكل كتابتها.
2. المرور على الأسئلة
for number, item in enumerate(questions, start=1):
الدالة enumerate() تعطيك رقم السؤال والعنصر نفسه أثناء المرور داخل القائمة. استخدمنا start=1 حتى يبدأ ترقيم الأسئلة من 1 بدل 0.
3. منع إجابة غير موجودة
while user_answer not in item["options"]:
print("اختر حرفًا موجودًا من الخيارات المعروضة.")
هذا الشرط مهم. بدونه قد يكتب المستخدم z أو رقمًا أو كلمة كاملة، وسيحسب البرنامج الإجابة خاطئة دون أن يوضح أن الخيار غير موجود. باستخدام while نطلب إدخالًا جديدًا حتى يختار المستخدم أحد الحروف المتاحة.
4. زيادة النتيجة
if user_answer == item["answer"]:
score += 1
المتغير score يبدأ من صفر. في كل مرة تكون الإجابة مطابقة للإجابة الصحيحة، نضيف إليه واحدًا.
5. حساب النسبة المئوية
percentage = (score / total_questions) * 100
نقسم عدد الإجابات الصحيحة على إجمالي عدد الأسئلة، ثم نضرب الناتج في 100. استخدمنا :.0f داخل النص المنسق لطباعة النسبة من دون أرقام عشرية.
كيف تضيف سؤالًا جديدًا؟
أضف قاموسًا جديدًا داخل القائمة questions بالهيكل نفسه:
{
"question": "ما الكلمة التي نستخدمها لاستيراد مكتبة؟",
"options": {
"a": "import",
"b": "input",
"c": "class",
},
"answer": "a",
"explanation": "نستخدم import لاستيراد المكتبات والوحدات.",
},
لا تغير مفاتيح القاموس مثل question أو options أو answer في سؤال واحد فقط، لأن دالة الاختبار تعتمد على هذه الأسماء في كل الأسئلة.
تحسينات يمكنك إضافتها لاحقًا
- خلط ترتيب الأسئلة في كل تشغيل.
- إضافة مؤقت بسيط لكل سؤال.
- إعادة الاختبار من البداية عند طلب المستخدم.
- تقسيم الأسئلة إلى مستويات: مبتدئ ومتوسط.
- قراءة الأسئلة من ملف JSON بدل كتابتها داخل الكود.
- إظهار الإجابات الصحيحة كلها في النهاية بدل عرضها فورًا.
أخطاء شائعة في هذا المشروع
1. كتابة الإجابة الصحيحة كنص الخيار بدل حرف الخيار
في هذا المثال نخزن "a" أو "b" في المفتاح answer، لأن المستخدم يكتب حرفًا. لا تكتب "def" في الإجابة الصحيحة ثم تقارنها مباشرة بإدخال "a"؛ النوعان مختلفان.
2. نسيان إعادة score إلى صفر
ضع score = 0 في بداية دالة run_quiz. بهذه الطريقة يبدأ كل تشغيل بنتيجة جديدة.
3. القسمة على قائمة فارغة
إذا حذفت كل الأسئلة ثم شغلت البرنامج، ستكون total_questions مساوية للصفر، وستظهر مشكلة عند حساب النسبة. عند تطوير المشروع تحقق دائمًا من أن القائمة ليست فارغة.
4. قبول خيار خارج القائمة
استخدم حلقة while للتحقق من أن الإجابة موجودة في مفاتيح options. هذا يجعل البرنامج أوضح للمستخدم.
5. تكرار كود السؤال يدويًا
تخزين الأسئلة داخل قائمة من القواميس يمنع نسخ كود الطباعة والتحقق لكل سؤال، ويجعل إضافة الأسئلة أسهل.
روابط داخلية مفيدة من بايثون العرب
- أساسيات بايثون 11: شرح if وelse للمبتدئين
- أساسيات بايثون 14: شرح return في الدوال
- بايثون بالمثال 10: البحث عن عنصر داخل قائمة
- الفرق بين القائمة والقاموس والمجموعة والصف في بايثون
- محرر بايثون أونلاين لتجربة الكود
- أفضل مشاريع بايثون للمبتدئين
الخلاصة
- القائمة من القواميس طريقة مرتبة لتخزين أسئلة متعددة وخياراتها.
- تساعد حلقة
forعلى عرض كل سؤال وخياراته دون تكرار الكود. - استخدم
strip()وlower()لتوحيد شكل إجابة المستخدم. - استخدم
whileللتأكد من أن المستخدم اختار حرفًا موجودًا. - تزداد النتيجة بواسطة
score += 1عند الإجابة الصحيحة. - تحسب النسبة بقسمة الإجابات الصحيحة على إجمالي الأسئلة ثم الضرب في 100.
{alertSuccess} تطبيق مهم: لا تكتفِ بتشغيل البرنامج كما هو. أضف 5 أسئلة من الدروس التي تعلمتها، ثم جرّب الإجابة عليها بنفسك. تعديل المشروع هو أسرع طريقة لفهم القوائم والقواميس والحلقات.
مصادر رسمية للتوسع
أسئلة شائعة
هل يمكن أن أضيف أكثر من ثلاثة خيارات للسؤال؟
نعم. أضف خيارات جديدة داخل القاموس options مثل "d"، ثم تأكد من كتابة حرف الإجابة الصحيح في answer.
لماذا نستخدم قائمة من القواميس بدل قائمة من النصوص؟
لأن كل سؤال يحتاج أكثر من معلومة: نص السؤال، الخيارات، الإجابة الصحيحة، والشرح. القاموس يجمع هذه المعلومات، والقائمة تجمع كل الأسئلة.
هل يمكن أن يقبل البرنامج الإجابة العربية بدل الحروف؟
نعم، لكن ذلك يحتاج منطقًا مختلفًا للمقارنة مع نصوص الخيارات. البدء بالحروف a وb وc يجعل المثال أوضح للمبتدئ.
كيف أجعل الأسئلة تظهر بترتيب مختلف كل مرة؟
يمكنك خلط نسخة من القائمة قبل تشغيل الاختبار. نفّذ هذه الإضافة بعد فهم النسخة الحالية، ولا تغيّر القائمة الأصلية بلا حاجة إذا كنت تريد الاحتفاظ بترتيبها.
هل يمكن أن أحفظ الأسئلة في ملف خارجي؟
نعم. الخطوة التالية المناسبة هي تخزينها في ملف JSON ثم قراءتها عند بدء البرنامج. بهذه الطريقة تضيف أسئلة دون تعديل منطق الاختبار نفسه.
بايثون بالمثال 16: إنشاء كلمة مرور عشوائية باستخدام secrets
هل احتجت يومًا إلى كلمة مرور طويلة مختلفة لكل حساب، لكنك لم تعرف كيف تنشئها دون اختيار كلمات سهلة أو نمط متكرر؟ في هذا المثال سنبني برنامجًا صغيرًا ينشئ كلمة مرور عشوائية من الحروف والأرقام والرموز باستخدام مكتبة secrets المدمجة في بايثون.
هذا التطبيق مناسب للمبتدئ لأنه يجمع بين الدوال، النصوص، القوائم، الشروط، الحلقات، إدخال المستخدم، ومعالجة الأخطاء. والأهم أنه يوضح فرقًا عمليًا مهمًا: ليست كل العشوائية مناسبة للمهام المرتبطة بكلمات المرور.
{alertInfo}
الفكرة ببساطة: سنكوّن مجموعة من الحروف الصغيرة والكبيرة والأرقام والرموز، ثم نختار منها عناصر عشوائية باستخدام secrets.choice()، ونرتبها بطريقة عشوائية قبل عرض كلمة المرور النهائية.
{getToc} $title={محتوى المقال}
ماذا سنتعلم من هذا المثال؟
- لماذا نستخدم
secretsبدلrandomعند إنشاء كلمات المرور. - استخدام
string.ascii_lowercaseوstring.ascii_uppercaseوstring.digits. - اختيار رمز عشوائي باستخدام
secrets.choice(). - إنشاء دالة تقبل طول كلمة المرور وتعيد كلمة جاهزة.
- ضمان وجود حرف صغير وحرف كبير ورقم ورمز في النتيجة.
- قراءة طول كلمة المرور من المستخدم والتحقق من الإدخال.
- التعامل مع
ValueErrorعند إدخال قيمة غير صحيحة.
لماذا لا نستخدم random لكلمات المرور؟
مكتبة random رائعة للألعاب والمحاكاة والاختيارات التعليمية، مثل رمي حجر نرد أو خلط أسئلة اختبار. لكن عندما تكون العشوائية مرتبطة بكلمات مرور أو رموز تسجيل دخول أو بيانات حساسة، استخدم secrets.
توثيق بايثون الرسمي يوصي باستخدام secrets بدل مولد الأرقام شبه العشوائي الافتراضي في random عند التعامل مع كلمات المرور والرموز الأمنية، لأن secrets يعتمد على مصدر عشوائية مناسب لهذا النوع من الاستخدام.
| المكتبة | استخدام مناسب | هل نستخدمها لكلمة مرور؟ |
|---|---|---|
random |
الألعاب، المحاكاة، أسئلة عشوائية، أمثلة تدريبية. | لا، ليست الخيار المناسب لكلمات المرور الفعلية. |
secrets |
كلمات المرور، الرموز المؤقتة، مفاتيح الاسترداد، المعرفات الصعبة التخمين. | نعم، استخدمها لهذا الغرض. |
راجع درس أساسيات بايثون 28: شرح مكتبة random لفهم استخدامات random المناسبة، ثم اعتبر secrets خيارك عندما يكون الموضوع متعلقًا بكلمات المرور.
مكونات كلمة المرور
سنستخدم أربع مجموعات من الرموز:
- الحروف الصغيرة مثل
aوb. - الحروف الكبيرة مثل
AوB. - الأرقام مثل
0و1. - الرموز مثل
!و@و#.
توفر مكتبة string مجموعات جاهزة للحروف والأرقام، لذلك لا نحتاج إلى كتابة الأبجدية يدويًا:
import string
lowercase_letters = string.ascii_lowercase
uppercase_letters = string.ascii_uppercase
digits = string.digits
symbols = "!@#$%^&*_-+="
print(lowercase_letters)
print(uppercase_letters)
print(digits)
print(symbols)
استخدمنا قائمة رموز صغيرة ومحددة بدل كل الرموز الممكنة، لأن بعض المواقع قد لا تقبل رموزًا معينة، وقد يكون بعضها محيرًا عند النسخ واللصق. تستطيع تعديل مجموعة symbols لاحقًا بما يناسبك.
أول نسخة بسيطة من مولد كلمة المرور
هذه النسخة تنشئ كلمة من 16 رمزًا بالاختيار من مجموعة واحدة كبيرة:
import secrets
import string
characters = (
string.ascii_lowercase
+ string.ascii_uppercase
+ string.digits
+ "!@#$%^&*_-+="
)
password = "".join(
secrets.choice(characters)
for _ in range(16)
)
print("كلمة المرور الجديدة:", password)
سيعطيك البرنامج كلمة مختلفة غالبًا في كل تشغيل، مثل:
كلمة المرور الجديدة: nR7@eQ4s!V2kL9#x
لكن هذه النسخة لا تضمن ظهور كل نوع من الرموز. من الممكن، رغم أن ذلك غير شائع، أن تتكون النتيجة من حروف وأرقام فقط دون رمز. لذلك سنحسن البرنامج في الخطوة التالية.
بناء دالة تضمن تنوع الرموز
سننشئ دالة تطلب طول كلمة المرور، ثم تضع في البداية رمزًا واحدًا على الأقل من كل مجموعة:
- حرف صغير واحد على الأقل.
- حرف كبير واحد على الأقل.
- رقم واحد على الأقل.
- رمز واحد على الأقل.
بعد ذلك نكمل بقية الطول المطلوب من كل المجموعات، ثم نخلط ترتيب العناصر. هكذا لا تظهر كلمة المرور دائمًا بالحرف الصغير ثم الكبير ثم الرقم ثم الرمز بالترتيب نفسه.
import secrets
import string
def generate_password(length):
if length < 12:
raise ValueError(
"اختر طولًا لا يقل عن 12 رمزًا."
)
lowercase_letters = string.ascii_lowercase
uppercase_letters = string.ascii_uppercase
digits = string.digits
symbols = "!@#$%^&*_-+="
all_characters = (
lowercase_letters
+ uppercase_letters
+ digits
+ symbols
)
password_characters = [
secrets.choice(lowercase_letters),
secrets.choice(uppercase_letters),
secrets.choice(digits),
secrets.choice(symbols),
]
remaining_length = length - len(password_characters)
for _ in range(remaining_length):
password_characters.append(
secrets.choice(all_characters)
)
secure_random = secrets.SystemRandom()
secure_random.shuffle(password_characters)
return "".join(password_characters)
password = generate_password(16)
print("كلمة المرور الجديدة:", password)
هذه الدالة تستخدم return لإرجاع كلمة المرور، ثم نخزنها في المتغير password. راجع درس أساسيات بايثون 14: شرح return في الدوال إن لم تكن مرتاحًا لفكرة إرجاع القيم.
شرح الدالة خطوة بخطوة
1. التحقق من الطول
if length < 12:
raise ValueError(
"اختر طولًا لا يقل عن 12 رمزًا."
)
الشرط يمنع تمرير طول قصير جدًا في هذا المثال التعليمي. الرقم 12 ليس قاعدة عامة لكل المواقع، لأن لكل خدمة قواعدها الخاصة، لكنه حد عملي مناسب للبرنامج الذي نبنيه هنا.
2. إضافة رمز من كل مجموعة
password_characters = [
secrets.choice(lowercase_letters),
secrets.choice(uppercase_letters),
secrets.choice(digits),
secrets.choice(symbols),
]
بهذه الطريقة نضمن أن كلمة المرور تحتوي على كل الأنواع التي اخترناها. لاحظ أن secrets.choice() تأخذ عنصرًا واحدًا عشوائيًا من النص أو القائمة التي تمررها إليها.
3. إكمال باقي الرموز
remaining_length = length - len(password_characters)
for _ in range(remaining_length):
password_characters.append(
secrets.choice(all_characters)
)
لأننا أضفنا أربعة رموز مسبقًا، نطرح 4 من الطول المطلوب ضمنيًا عبر len(password_characters). إذا اخترت طول 16، ستضيف الحلقة 12 رمزًا إضافيًا.
4. خلط الترتيب
secure_random = secrets.SystemRandom()
secure_random.shuffle(password_characters)
لو لم نخلط القائمة، قد تبدأ كل كلمة مرور بحرف صغير ثم حرف كبير ثم رقم ثم رمز. الخلط يجعل مواضع الرموز متنوعة بدل ترتيب ثابت.
نسخة تفاعلية تطلب الطول من المستخدم
الآن سنحوّل المثال إلى برنامج صغير يطلب من المستخدم طول كلمة المرور. سنستخدم try وexcept حتى لا يتوقف البرنامج إذا كتب المستخدم نصًا مثل عشرة بدل رقم.
import secrets
import string
def generate_password(length):
if length < 12:
raise ValueError(
"اختر طولًا لا يقل عن 12 رمزًا."
)
lowercase_letters = string.ascii_lowercase
uppercase_letters = string.ascii_uppercase
digits = string.digits
symbols = "!@#$%^&*_-+="
all_characters = (
lowercase_letters
+ uppercase_letters
+ digits
+ symbols
)
password_characters = [
secrets.choice(lowercase_letters),
secrets.choice(uppercase_letters),
secrets.choice(digits),
secrets.choice(symbols),
]
for _ in range(
length - len(password_characters)
):
password_characters.append(
secrets.choice(all_characters)
)
secure_random = secrets.SystemRandom()
secure_random.shuffle(password_characters)
return "".join(password_characters)
try:
length = int(
input("أدخل طول كلمة المرور، مثل 16: ")
)
password = generate_password(length)
print("كلمة المرور الجديدة:")
print(password)
except ValueError as error:
print(f"إدخال غير صالح: {error}")
شغّل البرنامج، ثم جرّب هذه الحالات:
| الإدخال | ما المتوقع؟ |
|---|---|
16 |
ينشئ البرنامج كلمة مرور طولها 16 رمزًا. |
8 |
يظهر تنبيه يطلب طولًا لا يقل عن 12. |
abc |
يظهر تنبيه لأن int() لا يستطيع تحويل النص إلى رقم. |
{alertWarning} تنبيه مهم: لا تضع كلمة مرور حقيقية داخل ملف مشروعك، ولا ترفعها إلى GitHub، ولا تسجلها داخلloggingفي تطبيق حقيقي. استخدم مدير كلمات مرور موثوقًا لحفظ الكلمات الجديدة.
فحص بسيط لقواعد كلمة المرور
يمكننا كتابة دالة تعليمية تفحص هل النص يحتوي على حرف صغير وكبير ورقم ورمز. هذه الدالة مفيدة لفهم الشروط، لكنها لا تقيس كل جوانب أمان كلمة المرور ولا تغني عن سياسات الحماية في المواقع الحقيقية.
def check_password_rules(password):
has_lowercase = any(char.islower() for char in password)
has_uppercase = any(char.isupper() for char in password)
has_digit = any(char.isdigit() for char in password)
has_symbol = any(
char in "!@#$%^&*_-+="
for char in password
)
return (
len(password) >= 12
and has_lowercase
and has_uppercase
and has_digit
and has_symbol
)
test_password = "aB7!samplePass"
if check_password_rules(test_password):
print("كلمة المرور تحقق قواعد المثال.")
else:
print("كلمة المرور لا تحقق قواعد المثال.")
أخطاء شائعة في مولد كلمات المرور
1. استخدام random مع كلمة مرور فعلية
قد يعمل الكود، لكن اختيار المكتبة ليس مناسبًا للغرض. استخدم secrets عندما تنشئ كلمات مرور أو رموزًا يصعب تخمينها.
2. اختيار كلمات أو أنماط سهلة التخمين
كلمة مثل Ali123 أو Password2026 قد تكون سهلة التذكر، لكنها ليست فكرة جيدة لكلمة مرور فعلية. الهدف من البرنامج هو إنشاء نص مختلف وغير متوقع، وليس بناء كلمة مألوفة.
3. تثبيت الترتيب نفسه للرموز
إضافة حرف صغير ثم كبير ثم رقم ثم رمز أمر جيد لضمان التنوع، لكن يجب خلط القائمة قبل تحويلها إلى نص نهائي. لهذا استخدمنا SystemRandom().shuffle().
4. قبول طول قصير جدًا
لا تجعل الدالة تقبل أي رقم دون فحص. في مثالنا نرفض القيم الأقل من 12 حتى يظهر للمستخدم سبب الرفض بدل توليد كلمة قصيرة جدًا دون تنبيه.
5. طباعة أو حفظ كلمة المرور في مكان عام
في مثال تعليمي على جهازك، يمكن عرض الكلمة في Terminal. أما في تطبيق حقيقي فلا تسجل كلمات المرور في ملفات السجل، ولا تضعها في مستودع GitHub أو لقطة شاشة أو رسالة عامة.
تطوير المشروع خطوة بخطوة
- أضف خيارًا يتيح للمستخدم تحديد هل يريد الرموز أم لا.
- أضف خيارًا لاستبعاد الرموز المتشابهة مثل
Oو0وlو1. - أنشئ قائمة كلمات مرور متعددة بدل كلمة واحدة.
- اكتب اختبارًا بسيطًا يتأكد أن الدالة تعيد طولًا صحيحًا.
- حوّل البرنامج إلى أداة سطر أوامر باستخدام
argparseبعد أن تتعلم أساسيات برامج سطر الأوامر.
روابط داخلية مفيدة من بايثون العرب
- أساسيات بايثون 28: شرح مكتبة random
- أساسيات بايثون 13: شرح الدوال
- أساسيات بايثون 14: شرح return في الدوال
- أساسيات بايثون 11: شرح if وelse
- أساسيات بايثون 12: شرح الحلقات
- أفكار مشاريع بايثون للمبتدئين حسب المهارة
الخلاصة
- استخدم
secretsبدلrandomعند إنشاء كلمات مرور أو رموز حساسة. - تساعد مكتبة
stringعلى الحصول على الحروف والأرقام دون كتابتها يدويًا. - ضع رمزًا واحدًا على الأقل من كل مجموعة تريدها، ثم أضف بقية الرموز عشوائيًا.
- اخلط ترتيب الرموز قبل تحويل القائمة إلى نص نهائي.
- تحقق من طول الإدخال وتعامل مع
ValueErrorبدل توقف البرنامج. - لا تحفظ كلمة مرور حقيقية في الكود أو ملفات المشروع أو السجلات.
{alertSuccess} قاعدة عملية: كلمة المرور الجيدة لا تحتاج إلى أن تكون سهلة الحفظ؛ تحتاج إلى أن تكون طويلة وفريدة ومخزنة في مدير كلمات مرور موثوق، مع تفعيل التحقق بخطوتين عندما يكون متاحًا.
مصادر رسمية للتوسع
أسئلة شائعة
هل أحتاج إلى تثبيت مكتبة secrets عبر pip؟
لا. secrets مكتبة قياسية تأتي مع بايثون الحديثة، لذلك يكفي استدعاؤها باستخدام import secrets.
هل يمكنني استخدام random بدل secrets؟
استخدم random في الألعاب والتمارين والمحاكاة. أما لكلمات المرور أو الرموز الأمنية فاستخدم secrets لأنه مخصص لهذا النوع من العشوائية.
لماذا لا أستخدم كل رموز string.punctuation؟
يمكنك ذلك، لكن بعض المواقع لا تقبل جميع الرموز، وبعضها قد يكون مزعجًا عند النسخ أو غير واضح في بعض الخطوط. لذلك استخدمنا مجموعة صغيرة يمكنك تعديلها حسب الحاجة.
هل طول 12 مناسب دائمًا؟
هو حد تعليمي اختاره المثال، وليس قاعدة ثابتة لكل موقع. اتبع متطلبات الخدمة التي تستخدمها، ويفضل في العموم اختيار كلمات مرور أطول وفريدة لكل حساب.
هل برنامج مولد كلمات المرور يغني عن مدير كلمات المرور؟
لا. البرنامج ينشئ كلمة مرور فقط. مدير كلمات المرور يساعدك على حفظ كلمات مختلفة وطويلة لكل حساب دون الحاجة إلى كتابتها في ملف أو تذكرها يدويًا.
July 06, 2026
بايثون العرب
بايثون بالمثال 15: حساب نسبة الخصم والسعر النهائي في بايثون خطوة بخطوة
تظهر عبارة خصم 20% أو خصم 35% في المتاجر كثيرًا، لكن كيف نحولها إلى برنامج Python صغير يحسب قيمة الخصم والسعر النهائي بدقة؟ هذا المثال مناسب جدًا للمبتدئين لأنه يجمع أكثر من مهارة أساسية في برنامج واحد: استقبال البيانات عبر input()، تحويل النصوص إلى أرقام باستخدام float()، تنفيذ عمليات حسابية، استخدام شرط if للتحقق من الإدخال، ثم عرض النتيجة بصورة منظمة.
سنبدأ بحساب بسيط لقيمة ثابتة، ثم نجعل المستخدم يدخل السعر ونسبة الخصم بنفسه، وبعد ذلك نضيف التحقق من أن البيانات منطقية. وفي نهاية المقال ستبني دالة قابلة لإعادة الاستخدام لحساب الخصم في أي مشروع صغير.
{alertInfo} الفكرة ببساطة: لحساب قيمة الخصم نضرب السعر في نسبة الخصم ثم نقسم على100. ولحساب السعر النهائي نطرح قيمة الخصم من السعر الأصلي. إذا كان السعر200والخصم25%، فقيمة الخصم50والسعر النهائي150.
{getToc} $title={محتوى المقال}
ماذا سنبني في هذا المثال؟
سنكتب برنامجًا يطلب من المستخدم إدخال:
- السعر الأصلي للمنتج.
- نسبة الخصم المئوية.
ثم يعرض:
- قيمة الخصم.
- السعر بعد الخصم.
- رسالة توضح إن كانت البيانات غير صحيحة، مثل خصم أكبر من
100%.
معادلة حساب الخصم والسعر النهائي
قبل كتابة الكود، من المهم فهم العملية الحسابية نفسها:
| المطلوب | المعادلة | مثال: السعر 200 والخصم 25% |
|---|---|---|
| قيمة الخصم | price * discount / 100 |
200 * 25 / 100 = 50 |
| السعر النهائي | price - discount_value |
200 - 50 = 150 |
إذن ترتيب العمل سيكون كالتالي:
- نحفظ السعر الأصلي في متغير.
- نحفظ نسبة الخصم في متغير.
- نحسب قيمة الخصم.
- نطرحها من السعر الأصلي.
- نطبع النتيجة.
الكود الأول: حساب الخصم بقيم ثابتة
لنبدأ بأسهل نسخة. سنضع السعر والخصم داخل الكود مباشرة:
price = 200
discount = 25
discount_value = price * discount / 100
final_price = price - discount_value
print("السعر الأصلي:", price)
print("قيمة الخصم:", discount_value)
print("السعر بعد الخصم:", final_price)
الناتج:
السعر الأصلي: 200
قيمة الخصم: 50.0
السعر بعد الخصم: 150.0
في هذا المثال:
priceيمثل السعر قبل الخصم.discountيمثل النسبة المئوية للخصم، وليس قيمته المالية.discount_valueهو المبلغ الذي سيُخصم.final_priceهو المبلغ الذي سيدفعه العميل بعد الخصم.
لماذا نقسم على 100؟
النسبة المئوية تعني جزءًا من مئة. لذلك 25% تعني 25 / 100 أي 0.25. عندما نكتب:
price * discount / 100
نحن نحسب الجزء الذي تمثله نسبة الخصم من السعر. يمكنك كتابة المعادلة بطريقة مكافئة:
discount_value = price * (discount / 100)
لكن الصيغة الأولى أبسط للقراءة في هذا المثال.
نسخة تفاعلية: اجعل المستخدم يدخل السعر والخصم
الآن سنجعل البرنامج أكثر فائدة. الدالة input() تستقبل ما يكتبه المستخدم، لكنها تعيد النص على شكل str. لذلك نستخدم float() لتحويل السعر ونسبة الخصم إلى أرقام عشرية يمكن إجراء الحسابات عليها.
price = float(input("أدخل السعر الأصلي: "))
discount = float(input("أدخل نسبة الخصم: "))
discount_value = price * discount / 100
final_price = price - discount_value
print(f"قيمة الخصم: {discount_value}")
print(f"السعر النهائي: {final_price}")
مثال تشغيل:
أدخل السعر الأصلي: 149.99
أدخل نسبة الخصم: 15
قيمة الخصم: 22.4985
السعر النهائي: 127.4915
النتيجة الحسابية صحيحة، لكن عرض عدد كبير من المنازل العشرية غير مناسب عادة للأسعار. لذلك سننسق النتيجة في الخطوة التالية.
عرض السعر بمنزلتين عشريتين
داخل f-string تستطيع استخدام :.2f لعرض الرقم بمنزلتين عشريتين:
print(f"قيمة الخصم: {discount_value:.2f}")
print(f"السعر النهائي: {final_price:.2f}")
سيصبح الناتج أوضح:
قيمة الخصم: 22.50
السعر النهائي: 127.49
استخدمنا f-string لأنها طريقة واضحة لدمج القيم داخل النص. راجع توثيق Python الرسمي حول Input and Output لفهم تنسيق المخرجات بصورة أوسع.
أضف التحقق من صحة السعر ونسبة الخصم
النسخة السابقة تفترض أن المستخدم يكتب أرقامًا منطقية، لكن ماذا لو أدخل سعرًا سالبًا؟ أو خصمًا بقيمة 150؟ هذه القيم لا تصلح في نموذج خصم عادي.
نستخدم if للتحقق من الآتي:
- السعر أكبر من صفر.
- نسبة الخصم بين
0و100.
price = float(input("أدخل السعر الأصلي: "))
discount = float(input("أدخل نسبة الخصم: "))
if price <= 0:
print("السعر يجب أن يكون أكبر من صفر.")
elif discount < 0 or discount > 100:
print("نسبة الخصم يجب أن تكون بين 0 و100.")
else:
discount_value = price * discount / 100
final_price = price - discount_value
print(f"قيمة الخصم: {discount_value:.2f}")
print(f"السعر النهائي: {final_price:.2f}")
تدعم Python استخدام if وelif وelse لبناء مسارات قرار متعددة في البرنامج.
نسخة أفضل: معالجة إدخال النص بدل الرقم
إذا كتب المستخدم كلمة مثل عشرون بدل 20، فإن float() سترفع ValueError. يمكننا التعامل مع ذلك باستخدام try وexcept:
try:
price = float(input("أدخل السعر الأصلي: "))
discount = float(input("أدخل نسبة الخصم: "))
if price <= 0:
print("السعر يجب أن يكون أكبر من صفر.")
elif not 0 <= discount <= 100:
print("نسبة الخصم يجب أن تكون بين 0 و100.")
else:
discount_value = price * discount / 100
final_price = price - discount_value
print(f"قيمة الخصم: {discount_value:.2f}")
print(f"السعر النهائي: {final_price:.2f}")
except ValueError:
print("الرجاء إدخال أرقام صحيحة أو عشرية فقط.")
الشرط المختصر:
0 <= discount <= 100
يعني أن الخصم لا يقل عن صفر ولا يزيد على مئة في الوقت نفسه.
ضع الحساب داخل دالة قابلة لإعادة الاستخدام
عندما يتكرر منطق الحساب في أكثر من مكان، من الأفضل وضعه في دالة. هكذا يصبح الكود منظمًا، ويمكنك استخدامه لاحقًا في برنامج متجر أو فاتورة أو واجهة ويب.
def calculate_discount(price, discount):
discount_value = price * discount / 100
final_price = price - discount_value
return discount_value, final_price
discount_value, final_price = calculate_discount(350, 30)
print(f"قيمة الخصم: {discount_value:.2f}")
print(f"السعر بعد الخصم: {final_price:.2f}")
الناتج:
قيمة الخصم: 105.00
السعر بعد الخصم: 245.00
لاحظ أن الدالة تعيد قيمتين. Python تسمح بإعادة عدة قيم، ثم يمكن حفظها في متغيرين بهذا الشكل:
discount_value, final_price = calculate_discount(350, 30)
برنامج كامل: حاسبة خصم للمبتدئين
هذه نسخة عملية تجمع ما تعلمناه: إدخال المستخدم، التحقق، الحساب، والتنسيق.
def calculate_discount(price, discount):
discount_value = price * discount / 100
final_price = price - discount_value
return discount_value, final_price
try:
price = float(input("أدخل السعر الأصلي: "))
discount = float(input("أدخل نسبة الخصم: "))
if price <= 0:
print("السعر يجب أن يكون أكبر من صفر.")
elif not 0 <= discount <= 100:
print("نسبة الخصم يجب أن تكون بين 0 و100.")
else:
discount_value, final_price = calculate_discount(price, discount)
print("\n--- ملخص العملية ---")
print(f"السعر الأصلي: {price:.2f}")
print(f"نسبة الخصم: {discount:.2f}%")
print(f"قيمة الخصم: {discount_value:.2f}")
print(f"السعر النهائي: {final_price:.2f}")
except ValueError:
print("خطأ: الرجاء إدخال قيمة رقمية صحيحة.")
تطبيق إضافي: خصم حسب قيمة الشراء
يمكنك تطوير البرنامج بحيث يحدد نسبة الخصم تلقائيًا بحسب قيمة الطلب. هذا يدرّبك على استخدام if وelif وelse بصورة عملية:
price = float(input("أدخل إجمالي المشتريات: "))
if price >= 500:
discount = 25
elif price >= 250:
discount = 15
elif price >= 100:
discount = 10
else:
discount = 0
discount_value = price * discount / 100
final_price = price - discount_value
print(f"الخصم المطبق: {discount}%")
print(f"السعر النهائي: {final_price:.2f}")
إذا أدخل المستخدم 300 مثلًا، سيطبق البرنامج خصمًا بنسبة 15%.
هل نستخدم float أم Decimal للأسعار؟
في أمثلة التعلم البسيطة نستخدم float لأنه مباشر وسهل الفهم. لكن في التطبيقات المالية الحقيقية التي تحتاج دقة محاسبية صارمة، قد تفضّل استخدام Decimal من مكتبة decimal؛ لأن بعض الأرقام العشرية لا تمثل بدقة تامة في نظام float الثنائي.
لا تحتاج إلى استعمال Decimal في هذا الدرس، لكن من المفيد أن تعرف هذه النقطة عندما تبدأ ببناء برامج فواتير أو محاسبة. توضح وثائق Python أن Decimal صمم لتمثيل الأرقام العشرية بطريقة أدق في الحالات المناسبة للحسابات البشرية والمالية.
أخطاء شائعة عند حساب الخصم
1. نسيان القسمة على 100
# خطأ
discount_value = price * discount
إذا كان السعر 200 والخصم 25، سيصبح الناتج 5000 بدل 50.
الحل:
discount_value = price * discount / 100
2. طرح نسبة الخصم مباشرة من السعر
# خطأ
final_price = price - discount
هذه العملية تطرح الرقم discount نفسه من السعر، لا نسبة منه. يجب أولًا حساب discount_value.
3. قبول خصم أكبر من 100%
أي خصم أكبر من 100 سيجعل السعر النهائي سالبًا، وهذا غير مناسب في هذا المثال. لذلك تحقق من المدى قبل الحساب.
4. استخدام int بدل float مع أسعار عشرية
إذا كان السعر 99.99 واستخدمت int()، ستظهر مشكلة لأن int("99.99") لا يقبل هذا النص. استخدم float() عندما تتوقع أسعارًا تحتوي منازل عشرية.
5. عدم معالجة إدخال غير رقمي
عند تحويل إدخال المستخدم إلى رقم، استخدم try/except ValueError إذا أردت منع توقف البرنامج عند كتابة نص غير صالح.
تمرين عملي
اكتب برنامجًا يطلب من المستخدم سعر المنتج ونسبة خصم، ثم:
- يرفض السعر إذا كان أقل من أو يساوي صفرًا.
- يرفض نسبة الخصم إذا لم تكن بين
0و100. - يعرض السعر الأصلي وقيمة الخصم والسعر النهائي بمنزلتين عشريتين.
- يعرض رسالة إضافية إذا كان الخصم يساوي
0.
حل مقترح
try:
price = float(input("أدخل السعر: "))
discount = float(input("أدخل نسبة الخصم: "))
if price <= 0:
print("السعر غير صالح.")
elif not 0 <= discount <= 100:
print("نسبة الخصم غير صالحة.")
elif discount == 0:
print(f"لا يوجد خصم. السعر النهائي: {price:.2f}")
else:
discount_value = price * discount / 100
final_price = price - discount_value
print(f"قيمة الخصم: {discount_value:.2f}")
print(f"السعر النهائي: {final_price:.2f}")
except ValueError:
print("أدخل أرقامًا صحيحة فقط.")
روابط داخلية مفيدة من بايثون العرب
- سلسلة بايثون بالمثال
- بايثون بالمثال 2: معرفة هل العدد زوجي أم فردي
- بايثون بالمثال 14: عدّ تكرار العناصر في List
- محرر بايثون أونلاين لتجربة الكود
- كورس أساسيات بايثون للمبتدئين
الخلاصة
- قيمة الخصم تساوي
price * discount / 100. - السعر النهائي يساوي
price - discount_value. - استخدم
float()لقراءة الأسعار والنسب العشرية من المستخدم. - استخدم
ifللتحقق من أن السعر أكبر من صفر والخصم بين0و100. - استخدم
:.2fلعرض السعر وقيمة الخصم بمنزلتين عشريتين. - ضع منطق الحساب داخل دالة عندما تريد إعادة استخدامه في أكثر من مكان.
- في التطبيقات المالية الدقيقة، تعرّف لاحقًا على
Decimalبدل الاعتماد الكامل علىfloat.
{alertSuccess}
قاعدة ذهبية: لا تطرح نسبة الخصم مباشرة من السعر. احسب أولًا قيمة الخصم بقسمة النسبة على 100، ثم اطرح هذه القيمة من السعر الأصلي.
مصادر خارجية رسمية للتوسع
- Built-in Functions: input وfloat في Python
- if Statements — Python Documentation
- Input and Output Formatting — Python Documentation
- decimal — Decimal fixed-point arithmetic
أسئلة شائعة
كيف أحسب 20% خصم في Python؟
استخدم المعادلة discount_value = price * 20 / 100، ثم احسب السعر النهائي عبر final_price = price - discount_value.
كيف أحسب السعر بعد الخصم في Python؟
بعد حساب قيمة الخصم، اطرحها من السعر الأصلي. مثال: final_price = price - (price * discount / 100).
هل يمكن أن تكون نسبة الخصم رقمًا عشريًا؟
نعم. استخدم float() مثل discount = 12.5، وسيحسب البرنامج الخصم بصورة طبيعية.
لماذا نستخدم float بدل int عند حساب الأسعار؟
لأن السعر أو نسبة الخصم قد يحتويان على قيمة عشرية مثل 99.99 أو 12.5. الدالة float() تقبل هذه القيم.
هل float مناسب لحسابات المتجر الحقيقية؟
هو مناسب للتعلم والأمثلة البسيطة. للحسابات المالية التي تتطلب دقة محاسبية صارمة، استخدم لاحقًا Decimal من مكتبة decimal.
مشكلة وحل 22: حل خطأ IsADirectoryError عند محاولة فتح مجلد كملف في بايثون
أثناء التعامل مع الملفات في بايثون، قد تكتب كودًا يبدو صحيحًا ثم يتوقف البرنامج برسالة مثل:
IsADirectoryError: [Errno 21] Is a directory: 'reports'
هذه الرسالة لا تعني أن مكتبة pathlib فيها مشكلة، ولا أن ترميز الملف خطأ. معناها المباشر أن الكود طلب تنفيذ عملية خاصة بملف، مثل القراءة أو الكتابة، لكنه أعطى بايثون مسار مجلد بدل مسار ملف.
{alertInfo} الفكرة ببساطة: استخدمis_file()عندما تريد قراءة ملف، واستخدمis_dir()عندما تريد التأكد أن المسار يشير إلى مجلد. وجود المسار وحده لا يكفي، لأن المسار قد يكون ملفًا أو مجلدًا.
{getToc} $title={محتوى المقال}
متى يظهر خطأ IsADirectoryError؟
يظهر الخطأ عندما تحاول استخدام مجلد كما لو كان ملفًا. أمثلة العمليات التي تتوقع ملفًا:
- قراءة النص عبر
open()أوread_text(). - الكتابة داخل ملف عبر
write_text()أوopen(..., "w"). - قراءة محتوى ملف CSV أو JSON من مسار يشير في الحقيقة إلى مجلد.
المثال التالي يعيد إنتاج المشكلة:
from pathlib import Path
path = Path("reports")
with path.open("r", encoding="utf-8") as file:
content = file.read()
print(content)
إذا كان reports مجلدًا موجودًا، فإن path.open() لن يستطيع قراءته كنص، لأن هذه العملية تحتاج اسم ملف مثل reports/summary.txt.
في هذا التنظيم:
# خطأ: reports مجلد
Path("reports").read_text(encoding="utf-8")
# صحيح: summary.txt ملف داخل المجلد
file_path = Path("reports") / "summary.txt"
الخطأ الأكثر شيوعًا: الفحص بـ exists فقط
قد يحاول بعض المبتدئين حل المشكلة بهذا الشكل:
from pathlib import Path
path = Path("reports")
if path.exists():
content = path.read_text(encoding="utf-8")
المشكلة أن exists() يجيب عن سؤال واحد فقط: هل يوجد هذا المسار؟ لكنه لا يخبرك هل هو ملف أم مجلد. لذلك قد تكون النتيجة True للمجلد reports، ثم يظهر الخطأ عند محاولة القراءة.
الحل هو فحص نوع المسار قبل محاولة قراءة محتواه:
from pathlib import Path
path = Path("reports")
if path.is_file():
content = path.read_text(encoding="utf-8")
print(content)
elif path.is_dir():
print("المسار يشير إلى مجلد، وليس ملفًا.")
else:
print("المسار غير موجود.")
الفرق بين exists وis_file وis_dir
| الدالة | السؤال الذي تجيب عنه | مثال لنتيجة محتملة |
|---|---|---|
exists() |
هل يوجد المسار أصلًا؟ | قد تكون True للملف أو المجلد. |
is_file() |
هل يشير المسار إلى ملف عادي؟ | True لملف مثل notes.txt. |
is_dir() |
هل يشير المسار إلى مجلد؟ | True لمجلد مثل reports. |
توفر مكتبة pathlib هذه الدوال للتعامل الواضح مع المسارات. راجع أيضًا درس شرح مكتبة pathlib للتعامل مع مسارات الملفات والمجلدات لتفهم طريقة بناء المسارات باستخدام العامل /.
الحل الصحيح عند معرفة اسم الملف
عندما تعرف أن الملف المطلوب موجود داخل مجلد محدد، ابنِ مساره كاملًا بدل فتح اسم المجلد:
from pathlib import Path
reports_folder = Path("reports")
file_path = reports_folder / "summary.txt"
if file_path.is_file():
content = file_path.read_text(encoding="utf-8")
print(content)
else:
print("لم يتم العثور على الملف المطلوب.")
هذه الطريقة أفضل من كتابة مسار نصي طويل مثل "reports/summary.txt"، خصوصًا عندما تحتاج إلى بناء مسارات متعددة داخل مشروعك.
دالة آمنة لقراءة ملف نصي
في البرامج الحقيقية، لا تعتمد على الفحص فقط. قد يتغير المسار بين لحظة الفحص ولحظة القراءة، أو تحدث مشكلة في الصلاحيات. لذلك استخدم فحصًا واضحًا للمستخدم، ثم تعامل مع الأخطاء المحتملة بشكل محدد.
from pathlib import Path
def read_text_file(path_text):
path = Path(path_text)
if path.is_dir():
print(f"لا يمكن قراءة المجلد كملف: {path}")
return None
if not path.is_file():
print(f"الملف غير موجود: {path}")
return None
try:
return path.read_text(encoding="utf-8")
except PermissionError:
print("لا توجد صلاحية كافية لقراءة هذا الملف.")
except IsADirectoryError:
print("المسار أصبح مجلدًا أو يشير إلى مجلد بدل ملف.")
except UnicodeDecodeError:
print("تعذر قراءة الملف بترميز UTF-8.")
return None
content = read_text_file("reports/summary.txt")
if content is not None:
print(content)
لاحظ أننا لم نستخدم except Exception أو except: بلا تحديد. التقاط الأخطاء المحددة يجعل الرسالة أوضح ولا يخفي أخطاء برمجية أخرى تحتاج إلى إصلاح.
{alertWarning} تنبيه: لا تستخدمis_file()وحدها كبديل كامل عنtry/except. الفحص يقدّم رسالة مفهومة للمستخدم، لكن عملية القراءة نفسها يمكن أن تفشل لاحقًا بسبب الصلاحيات أو تغيير الملف أو المسار.
مثال عملي: قراءة الملفات النصية من مجلد
أحيانًا يكون هدفك فعلًا التعامل مع مجلد، لكنك تريد قراءة الملفات الموجودة بداخله. في هذه الحالة لا تستدعِ read_text() على المجلد نفسه؛ اعرض العناصر، ثم اقرأ الملفات فقط.
from pathlib import Path
folder = Path("reports")
if not folder.is_dir():
print("المجلد المطلوب غير موجود أو أن المسار ليس مجلدًا.")
else:
for item in folder.iterdir():
if item.is_file() and item.suffix.lower() == ".txt":
content = item.read_text(encoding="utf-8")
print(f"--- {item.name} ---")
print(content)
الكود السابق يتجاهل المجلد archive والملف chart.png، لأنه يقرأ العناصر التي تحقق شرطين معًا:
- العنصر ملف حقيقي:
item.is_file(). - امتداد الملف هو
.txt.
تستطيع لاحقًا تطوير المثال لقراءة ملفات CSV أو JSON، لكن لا تفترض أن كل عنصر داخل المجلد مناسب للقراءة بالطريقة نفسها.
الفرق بين IsADirectoryError وأخطاء قريبة منه
| الخطأ | معناه | مثال مختصر |
|---|---|---|
IsADirectoryError |
استخدمت مجلدًا في عملية تحتاج ملفًا. | Path("reports").read_text() |
FileNotFoundError |
المسار أو الملف المطلوب غير موجود. | Path("missing.txt").read_text() |
PermissionError |
لا توجد صلاحية كافية للوصول إلى الملف أو المجلد. | قراءة ملف محمي أو مفتوح بصلاحيات مقيدة. |
NotADirectoryError |
استخدمت ملفًا في عملية تحتاج مجلدًا. | محاولة استعراض ما داخل ملف كما لو كان مجلدًا. |
يمكنك الرجوع إلى حل خطأ FileNotFoundError عند فتح الملفات عندما لا يكون الملف موجودًا، وإلى حل خطأ PermissionError عند فتح الملفات عندما يكون المسار صحيحًا لكن المشكلة في الصلاحيات.
هل أستخدم os أم pathlib؟
يمكن حل المشكلة باستخدام os.path.isfile() وos.path.isdir() أيضًا، لكن pathlib مناسب جدًا للمبتدئ لأنه يجمع بناء المسار وفحصه وقراءته في كائن واحد.
import os
path = "reports"
if os.path.isfile(path):
with open(path, "r", encoding="utf-8") as file:
print(file.read())
elif os.path.isdir(path):
print("المسار مجلد وليس ملفًا.")
لا توجد مشكلة في استخدام os، خاصة إن كان مشروعك قديمًا ويستخدمه بالفعل. لكن لا تخلط الطريقتين بلا سبب داخل المثال نفسه؛ اختر أسلوبًا واضحًا وثابتًا.
أخطاء شائعة يجب تجنبها
1. فتح اسم المجلد بدل اسم الملف
لا تكتب Path("reports").read_text() إن كنت تريد قراءة ملف داخل المجلد. اكتب مسار الملف كاملًا مثل Path("reports") / "summary.txt".
2. الاعتماد على exists فقط
وجود المسار لا يثبت أنه ملف. استخدم is_file() أو is_dir() حسب ما تحتاجه.
3. استخدام except فارغ
try:
content = path.read_text()
except:
print("حدث خطأ.")
هذا الأسلوب يخفي سبب المشكلة. اكتب استثناءات محددة، مثل IsADirectoryError أو FileNotFoundError أو PermissionError، ثم اعرض رسالة مناسبة.
4. قراءة كل ما داخل المجلد بلا فلترة
داخل المجلد قد توجد صور ومجلدات فرعية وملفات مختلفة. افحص is_file() وفلتر الامتداد الذي تتوقعه قبل القراءة.
روابط داخلية مفيدة من بايثون العرب
- أساسيات بايثون 26: شرح مكتبة pathlib للتعامل مع مسارات الملفات والمجلدات
- أساسيات بايثون 22: التعامل مع الملفات
- أساسيات بايثون 31: شرح try وexcept والتعامل مع الأخطاء
- مشكلة وحل 13: حل خطأ FileNotFoundError
- مشكلة وحل 16: حل خطأ PermissionError
- بايثون بعد الأساسيات 2: تنظيم ملفات مشروع بايثون
الخلاصة
- يظهر
IsADirectoryErrorعندما تطلب من بايثون تنفيذ عملية ملف على مسار يشير إلى مجلد. exists()يثبت وجود المسار فقط، لكنه لا يحدد نوعه.- استخدم
is_file()قبل قراءة ملف، وis_dir()قبل استعراض محتوى مجلد. - عند قراءة ملف داخل مجلد، ابنِ مسار الملف كاملًا باستخدام
Path("folder") / "file.txt". - تعامل مع الأخطاء المحددة داخل
try/exceptبدل التقاط كل الأخطاء برسالة عامة. - عند استعراض مجلد، اقرأ الملفات المطابقة فقط واترك المجلدات والامتدادات غير المناسبة.
{alertSuccess} قاعدة عملية: قبل تنفيذ أي قراءة أو كتابة، اسأل: هل أتعامل مع ملف أم مجلد؟ هذا السؤال البسيط يمنع كثيرًا من أخطاء المسارات في مشاريع بايثون.
مصادر رسمية للتوسع
- توثيق IsADirectoryError في بايثون
- توثيق pathlib وPath في بايثون
- توثيق مكتبة os للتعامل مع نظام التشغيل
أسئلة شائعة
هل IsADirectoryError يعني أن المجلد غير موجود؟
لا. غالبًا يعني العكس: المسار موجود، لكنه مجلد بينما الكود كان يحتاج ملفًا. عندما لا يكون المسار موجودًا غالبًا ستواجه FileNotFoundError.
هل يكفي استخدام exists قبل read_text؟
لا. exists() لا يفرق بين الملف والمجلد. استخدم is_file() إذا كنت ستقرأ ملفًا، ثم تعامل مع الأخطاء المتوقعة عبر try/except.
كيف أقرأ ملفًا داخل مجلد؟
أنشئ مسارًا للملف نفسه، مثل Path("reports") / "summary.txt"، ثم تحقق من is_file() واقرأه باستخدام read_text() أو open().
هل أستخدم pathlib أم os لحل الخطأ؟
كلتاهما صالحتان. pathlib يقدم صياغة مرتبة لبناء المسارات وفحصها، بينما os.path مفيد في المشاريع التي تستخدمه مسبقًا. الأهم هو فحص ما إذا كان المسار ملفًا أو مجلدًا قبل العملية المناسبة.
لماذا أضع try وexcept بعد is_file؟
لأن الفحص يساعدك على إعطاء رسالة واضحة مبكرًا، لكنه لا يضمن أن القراءة ستنجح دائمًا. يمكن أن تتغير الصلاحيات أو حالة المسار قبل القراءة، لذلك يبقى التعامل المحدد مع الاستثناءات ضروريًا.
مشروع ذكاء اصطناعي 1: تصنيف الرسائل المزعجة باستخدام بايثون وscikit-learn
ملفات المشروع وكتيب الشرح: ستجد ملفات البيانات والكود والتمارين في مستودع بايثون العرب على GitHub بعد رفعها.
تحميل ملفات المشروع من GitHubهل يمكن أن يتعلم برنامج من أمثلة سابقة أن يفرّق بين رسالة عادية ورسالة مزعجة؟ نعم. هذا أحد أبسط تطبيقات تصنيف النصوص في تعلم الآلة، وهو مشروع ممتاز للانتقال من فهم المفاهيم إلى تنفيذ نموذج فعلي ببايثون.
في هذا المشروع سنبني نموذجًا تعليميًا يقرأ رسائل قصيرة مصنّفة مسبقًا إلى فئتين: spam للرسائل المزعجة وnormal للرسائل العادية. ثم نحوّل النصوص إلى أرقام يفهمها النموذج، وندربه، ونختبره على جزء لم يره أثناء التدريب، ثم نجرب رسالة جديدة بأنفسنا.
المشروع لا يهدف إلى بناء فلتر بريد جاهز للاستخدام الحقيقي. البيانات هنا صغيرة وتعليمية، لذلك لا يجوز الاعتماد عليه لفلترة رسائل عمل أو اتخاذ قرار حقيقي عن المستخدمين. الهدف هو فهم الخطوات الأساسية: البيانات ← تحويل النص ← التدريب ← الاختبار ← التنبؤ.
{alertInfo} الفكرة ببساطة: لا يفهم النموذج النص كما يفهمه الإنسان. نحن نحول الرسائل إلى تمثيل رقمي باستخدامTfidfVectorizer، ثم يتعلم نموذجMultinomialNBالأنماط التي تظهر في الرسائل المزعجة والعادية ليصنف رسالة جديدة.
{getToc} $title={محتوى المقال}
ماذا سنتعلم من هذا المشروع؟
- فهم الفرق بين التصنيف والتوقع الرقمي بصورة عملية.
- تنظيم بيانات نصية داخل ملف
CSV. - قراءة البيانات باستخدام
pandas. - تقسيم البيانات إلى تدريب واختبار باستخدام
train_test_split. - تحويل الرسائل إلى خصائص رقمية عبر
TfidfVectorizer. - تدريب مصنف نصي باستخدام
MultinomialNB. - عرض تقرير تقييم باستخدام
classification_report. - تجربة رسالة جديدة بعد تدريب النموذج.
ما نوع مشكلة هذا المشروع؟
هذا المشروع من نوع التصنيف Classification. أي أن النموذج لا يعيد رقمًا مثل سعر أو درجة، بل يختار فئة من فئات محددة مسبقًا.
| نوع المهمة | مثال | الناتج |
|---|---|---|
| توقع رقمي | توقع درجة طالب من ساعات الدراسة. | رقم مثل 82.5. |
| تصنيف | تحديد هل الرسالة مزعجة أو عادية. | spam أو normal. |
ظهر مشروع التوقع الرقمي في درس كيف تبدأ أول مشروع ذكاء اصطناعي باستخدام بايثون. أما هنا فسننتقل إلى نوع مختلف من المشاكل: تصنيف النصوص القصيرة.
قبل البدء: تجهيز بيئة المشروع
أنشئ مجلدًا جديدًا للمشروع، ثم أنشئ بيئة افتراضية وثبت المكتبات التي نحتاجها. استخدام البيئة الافتراضية يحافظ على مكتبات هذا المشروع منفصلة عن المشاريع الأخرى.
# Windows
py -m venv .venv
.venv\Scripts\activate
# macOS أو Linux
python3 -m venv .venv
source .venv/bin/activate
بعد تفعيل البيئة، ثبت المكتبات:
python -m pip install pandas scikit-learn
راجع درس تجهيز بيئة بايثون للذكاء الاصطناعي خطوة بخطوة إذا لم تكن معتادًا على venv أو pip.
تنظيم ملفات المشروع
سنستخدم هذا التنظيم البسيط:
أنشئ الملف requirements.txt واكتب فيه:
pandas
scikit-learn
لاحقًا، يستطيع أي شخص يستخدم المشروع تثبيت المكتبات المطلوبة عبر:
python -m pip install -r requirements.txt
الخطوة 1: تجهيز بيانات الرسائل
أنشئ الملف data/messages.csv. سنستخدم عمودين فقط:
message: نص الرسالة.label: تصنيف الرسالة، إماspamأوnormal.
ضع البيانات التالية داخل الملف. هذه البيانات تعليمية ومصطنعة عمدًا؛ لا تمثل بيانات بريد حقيقية ولا تكفي لتدريب نظام موثوق.
message,label
"تهانينا ربحت جائزة كبيرة اضغط الآن لاستلامها",spam
"عرض محدود اربح قسيمة شراء مجانية اليوم",spam
"اربح هاتفًا جديدًا أرسل بياناتك الآن",spam
"لديك فرصة أخيرة للحصول على هدية مجانية",spam
"اضغط على الرابط للفوز بجائزة فورية",spam
"تم اختيارك للحصول على مكافأة نقدية",spam
"ربح مضمون خلال دقائق سجل الآن",spam
"عرض سريع لا تفوت هديتك المجانية",spam
"لديك رصيد مجاني اضغط للاستلام",spam
"اربح رحلة مجانية اليوم فقط",spam
"تم قبولك للفوز بجائزة خاصة",spam
"لا تفوت الفرصة احصل على مكافأة الآن",spam
"النتائج ستعلن غدًا في اجتماع الفريق",normal
"هل يمكن إرسال ملف التقرير قبل الظهر",normal
"موعدنا يوم الخميس الساعة العاشرة",normal
"شكرًا لك تم استلام الرسالة",normal
"يرجى مراجعة ملاحظات المشروع المرفقة",normal
"سنناقش خطة العمل في الاجتماع القادم",normal
"هل انتهيت من تحديث جدول المبيعات",normal
"تم تأكيد حجز الموعد بنجاح",normal
"أرسل لي النسخة النهائية من العرض",normal
"تذكير بدفع فاتورة الإنترنت هذا الأسبوع",normal
"سأشارك معك رابط الاجتماع لاحقًا",normal
"يرجى الرد عند الانتهاء من المهمة",normal
لاحظ أن كل صف يحتوي رسالة ثم تصنيفها. ومن المهم أن توجد أمثلة من الفئتين حتى يتعلم النموذج الفرق بينهما.
الخطوة 2: لماذا نحتاج إلى تحويل النص إلى أرقام؟
لا يستطيع نموذج تعلم الآلة التعامل مع الجملة النصية مباشرة مثل:
اربح جائزة مجانية الآن
لذلك نستخدم TfidfVectorizer لتحويل مجموعة الرسائل إلى تمثيل رقمي. بصورة مبسطة، ينشئ أرقامًا تعكس الكلمات التي ظهرت في الرسائل ومدى أهميتها نسبيًا داخل مجموعة البيانات.
لا تحتاج إلى حفظ المعادلات الرياضية وراء TF-IDF الآن. المهم أن تعرف دوره في المشروع:
- يقرأ الرسائل النصية.
- يستخرج كلمات أو مجموعات كلمات.
- ينشئ تمثيلًا رقميًا لكل رسالة.
- يمرر هذه الأرقام إلى نموذج التصنيف.
استخدام ngram_range=(1, 2) في الكود لاحقًا يعني أن الأداة تنظر إلى الكلمات المفردة مثل جائزة، وإلى زوج من الكلمات مثل جائزة مجانية. هذه خطوة بسيطة قد تساعد النموذج على ملاحظة عبارات قصيرة بدل كلمات منفصلة فقط.
الخطوة 3: تدريب نموذج التصنيف
أنشئ الملف train_model.py ثم ضع الكود التالي كاملًا:
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics import classification_report
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline
# قراءة البيانات
data = pd.read_csv("data/messages.csv")
# التحقق من الأعمدة المطلوبة
required_columns = {"message", "label"}
if not required_columns.issubset(data.columns):
raise ValueError("ملف البيانات يجب أن يحتوي على message وlabel.")
# حذف الصفوف الناقصة إن وجدت
data = data.dropna(subset=["message", "label"])
# تحديد النصوص والتصنيفات
X = data["message"]
y = data["label"]
# تقسيم البيانات: 75% تدريب و25% اختبار
X_train, X_test, y_train, y_test = train_test_split(
X,
y,
test_size=0.25,
random_state=42,
stratify=y
)
# بناء خط معالجة: تحويل النص ثم تدريب المصنف
model = Pipeline(
steps=[
(
"vectorizer",
TfidfVectorizer(
ngram_range=(1, 2),
lowercase=False
)
),
("classifier", MultinomialNB())
]
)
# تدريب النموذج على بيانات التدريب فقط
model.fit(X_train, y_train)
# التنبؤ بتصنيفات بيانات الاختبار
predictions = model.predict(X_test)
# عرض تقرير التقييم
print("--- تقرير تقييم النموذج ---")
print(
classification_report(
y_test,
predictions,
zero_division=0
)
)
# تجربة رسائل جديدة
new_messages = [
"اربح هدية مجانية الآن اضغط على الرابط",
"يرجى حضور اجتماع المشروع غدًا"
]
new_predictions = model.predict(new_messages)
print("--- تجربة رسائل جديدة ---")
for message, label in zip(new_messages, new_predictions):
print(f"الرسالة: {message}")
print(f"التصنيف المتوقع: {label}")
print("-" * 40)
شغّل الملف من Terminal وأنت داخل مجلد المشروع:
python train_model.py
قد يختلف تقرير التقييم عند تغيير البيانات أو إعدادات التقسيم. لا تعتبر أي رقم دقة نهائيًا لأن مجموعة البيانات هنا صغيرة جدًا؛ المهم أن تفهم بنية دورة العمل.
شرح أهم أجزاء الكود
1. قراءة CSV باستخدام pandas
data = pd.read_csv("data/messages.csv")
هذا السطر يحول ملف CSV إلى جدول يمكن التعامل معه داخل الكود. بعد ذلك نقرأ عمود message بوصفه المدخلات، وعمود label بوصفه النتيجة الصحيحة التي يجب أن يتعلمها النموذج.
2. تقسيم البيانات إلى تدريب واختبار
X_train, X_test, y_train, y_test = train_test_split(
X,
y,
test_size=0.25,
random_state=42,
stratify=y
)
نحن لا نريد اختبار النموذج بالرسائل نفسها التي تعلم منها. لذلك نحتفظ بجزء من البيانات للاختبار. استخدام stratify=y يحاول الحفاظ على وجود الفئتين بنسب قريبة داخل مجموعة التدريب والاختبار، وهو مناسب عندما تكون لديك بيانات مصنفة إلى فئات.
3. استخدام Pipeline
model = Pipeline(
steps=[
("vectorizer", TfidfVectorizer()),
("classifier", MultinomialNB())
]
)
Pipeline ينفذ خطوات المشروع بالترتيب. في حالتنا:
- تحويل النصوص إلى أرقام عبر
TfidfVectorizer. - تمرير التمثيل الرقمي إلى
MultinomialNBللتدريب أو التنبؤ.
هذه الطريقة تجعل الكود أنظف وتساعد على منع استخدام معلومات من بيانات الاختبار عند تجهيز خصائص التدريب.
4. لماذا اخترنا MultinomialNB؟
MultinomialNB من نماذج Naive Bayes الشائعة كبداية لتصنيف النصوص. وهو مناسب لخصائص منفصلة مثل عدد الكلمات أو تمثيلات النصوص، وتوضح وثائق scikit-learn أنه يمكن أن يعمل عمليًا أيضًا مع تمثيلات TF-IDF.
كيف تقرأ تقرير التقييم؟
الدالة classification_report تطبع عدة مقاييس لكل فئة. لا تحتاج إلى حفظها كلها في أول مشروع، لكن افهم معناها المبسط:
| المقياس | المعنى المبسط |
|---|---|
precision |
عندما قال النموذج إن الرسالة مزعجة، كم مرة كان حكمه صحيحًا؟ |
recall |
من بين الرسائل المزعجة الموجودة فعلًا، كم رسالة استطاع النموذج التقاطها؟ |
f1-score |
مقياس يجمع بين precision وrecall بصورة متوازنة. |
support |
عدد الأمثلة الفعلية من كل فئة داخل بيانات الاختبار. |
عندما تكون بيانات الاختبار صغيرة، يمكن أن تتغير هذه الأرقام كثيرًا إذا غيرت التقسيم أو أضفت رسائل جديدة. لهذا لا تركز على الحصول على رقم مرتفع في هذا المثال، بل على فهم ما الذي يقيسه كل رقم ولماذا تحتاج إلى بيانات أكبر ومتنوعة لاحقًا.
جرّب رسائل من عندك
غيّر القائمة التالية داخل الملف:
new_messages = [
"اربح هدية مجانية الآن اضغط على الرابط",
"يرجى حضور اجتماع المشروع غدًا"
]
ثم شغّل البرنامج مرة أخرى. جرّب رسائل متنوعة، لكن تذكر أن النموذج لا يفهم المعنى الكامل للغة؛ هو يتعلم من الكلمات والأنماط المحدودة التي رأى أمثلة منها.
مثلًا، إذا أدخلت رسالة تحتوي على كلمات لم تظهر في البيانات إطلاقًا، فقد يقدم النموذج تصنيفًا غير مناسب. هذه ليست مشكلة غريبة في الكود، بل نتيجة طبيعية لبيانات تدريب صغيرة ومحدودة.
لماذا لا نستخدم كلمات ممنوعة أو روابط حقيقية في البيانات؟
في مشروع تعليمي، من الأفضل أن تستخدم رسائل عامة وآمنة. لا تضع أرقام حسابات أو بيانات أشخاص أو روابط مشبوهة أو نسخًا من رسائل حقيقية خاصة. التدريب الجيد يبدأ من بيانات منظمة ومسموح باستخدامها، وليس من جمع محتوى شخصي أو حساس دون سبب وموافقة.
أخطاء شائعة في مشروع تصنيف الرسائل
1. تدريب النموذج على البيانات واختباره عليها نفسها
إذا دربت النموذج واختبرته على الرسائل ذاتها، قد تحصل على نتيجة تبدو ممتازة، لكنك لا تعرف هل يستطيع التعامل مع رسائل جديدة. استخدم دائمًا فصلًا واضحًا بين التدريب والاختبار.
2. نسيان وجود أمثلة من الفئتين
إذا كانت بياناتك تحتوي رسائل مزعجة فقط أو عادية فقط، فلن يتعلم النموذج الفرق بين الفئتين.
3. وضع التصنيفات بطريقة غير متسقة
لا تخلط بين spam وSpam ومزعج داخل الملف نفسه. اختر شكلًا واحدًا ثابتًا للتصنيفات.
4. الحكم على المشروع من بيانات صغيرة جدًا
مجموعة التدريب هنا للتعلم، وليست معيارًا حقيقيًا لجودة النموذج. نموذج جيد للاستخدام الفعلي يحتاج بيانات كثيرة ومتنوعة وتمثّل الحالات التي سيواجهها.
5. تحويل البيانات قبل التقسيم بشكل غير منظم
استخدام Pipeline يساعدك على تطبيق تحويل النص والتدريب في ترتيب سليم، بدل تجهيز النصوص بطريقة قد تخلط معلومات التدريب والاختبار.
6. اعتبار التنبؤ قرارًا نهائيًا
تصنيف النصوص قد يخطئ، خصوصًا مع الرسائل القصيرة أو العربية أو الصيغ الجديدة. في الأنظمة الحقيقية تحتاج سياسات مراجعة وحدودًا واضحة قبل اتخاذ أي إجراء تلقائي.
كيف تطور المشروع لاحقًا؟
- أضف عددًا أكبر من الرسائل التعليمية المتنوعة.
- أضف فئة ثالثة مثل
promotionللرسائل الترويجية غير المزعجة. - اعرض مصفوفة الالتباس لفهم أنواع الأخطاء.
- جرّب مقارنة
MultinomialNBمع نموذج آخر مثلLogisticRegression. - احفظ النموذج بعد التدريب في ملف باستخدام
joblibثم استخدمه لاحقًا في برنامج منفصل. - أنشئ واجهة بسيطة بعد أن تفهم منطق التدريب والتنبؤ.
روابط داخلية مفيدة من بايثون العرب
- كيف تبدأ أول مشروع ذكاء اصطناعي باستخدام بايثون
- أفضل مكتبات بايثون للذكاء الاصطناعي للمبتدئين
- تجهيز بيئة بايثون للذكاء الاصطناعي خطوة بخطوة
- ما هو الذكاء الاصطناعي؟ شرح مبسط للمبتدئين
- مسار مشاريع ذكاء اصطناعي ببايثون
الخلاصة
- تصنيف الرسائل المزعجة مثال عملي على مشكلة تصنيف نصوص.
- ملف CSV يحتوي النص والتصنيف هو نقطة البداية لتدريب النموذج.
TfidfVectorizerيحول الرسائل النصية إلى تمثيل رقمي يمكن للنموذج التعامل معه.MultinomialNBنموذج مناسب كبداية لتصنيف النصوص التعليمية.- يجب فصل بيانات التدريب عن بيانات الاختبار حتى تقيس أداء النموذج على أمثلة لم يرها.
classification_reportيساعدك على فهم الدقة والاستدعاء وF1 لكل فئة.- البيانات الصغيرة المصطنعة مفيدة للتعلم فقط، وليست أساسًا لنظام فلترة حقيقي.
{alertSuccess} قاعدة ذهبية: لا تبدأ بقول “أريد بناء ذكاء اصطناعي”. ابدأ بمشكلة صغيرة لها فئات واضحة وبيانات منظمة، ثم افهم كل خطوة من قراءة البيانات إلى اختبار النموذج قبل تطوير المشروع.
مصادر خارجية رسمية للتوسع
- توثيق TfidfVectorizer في scikit-learn
- توثيق MultinomialNB في scikit-learn
- توثيق train_test_split في scikit-learn
- توثيق classification_report في scikit-learn
- توثيق Pipeline في scikit-learn
أسئلة شائعة
هل هذا المشروع يستخدم ذكاءً اصطناعيًا حقيقيًا؟
هو مشروع تعلم آلة تعليمي حقيقي من ناحية الخطوات: بيانات وتصنيفات وتدريب واختبار وتنبؤ. لكن بياناته صغيرة ومصطنعة، لذلك لا يصلح كنظام فلترة حقيقي أو كقرار آلي في الاستخدام الفعلي.
لماذا نستخدم TfidfVectorizer؟
لأن نموذج تعلم الآلة لا يتعامل مع الجملة كنص مباشر. TfidfVectorizer يحول مجموعة الرسائل إلى خصائص رقمية مبنية على الكلمات وأهميتها النسبية.
هل MultinomialNB مناسب للنصوص العربية؟
يمكن استخدامه كنقطة بداية مع النصوص العربية، لكنه يعتمد بقوة على جودة البيانات وتنوعها وطريقة تجهيزها. في مشروع حقيقي قد تحتاج إلى تنظيف نصي أفضل وبيانات أكبر وتجارب متعددة.
لماذا نقسم البيانات إلى تدريب واختبار؟
حتى نعرف هل يستطيع النموذج تصنيف رسائل لم يرها أثناء التدريب. اختبار النموذج على الرسائل نفسها التي تدرب عليها لا يقيس قدرته على التعميم.
هل أستطيع إضافة فئات أخرى؟
نعم. يمكنك مثلًا إضافة فئة promotion أو important، لكن يجب أن توفر عددًا مناسبًا من الأمثلة المصنفة لكل فئة، ثم تعيد تدريب النموذج.
July 05, 2026
بايثون العرب
بايثون بعد الأساسيات 6: اختبار الدوال باستخدام unittest للمبتدئين
بعد أن تكتب دالة في مشروعك، كيف تتأكد أنها ما زالت تعمل بعد تعديل الكود؟ وكيف تتأكد أن حساب الخصم، أو قراءة ملف، أو تنفيذ أمر في برنامج CLI يعطي النتيجة نفسها في الحالات الطبيعية والحالات غير المتوقعة؟ التجربة اليدوية مفيدة، لكنها تصبح بطيئة وغير مضمونة عندما يبدأ المشروع في النمو.
هنا يأتي دور unittest: مكتبة قياسية مدمجة مع بايثون تساعدك على كتابة اختبارات تلقائية للدوال والكلاسات. بدل أن تشغل البرنامج كل مرة وتدخل القيم يدويًا، تكتب اختبارات واضحة ثم تطلب من بايثون تشغيلها. إذا نجحت كلها، تحصل على ثقة أكبر في أن التعديل الأخير لم يكسر وظيفة قديمة.
هذا الدرس يكمل ما بدأته في سلسلة بايثون بعد الأساسيات: تنظيم ملفات المشروع، قراءة CSV، بناء برنامج سطر أوامر، وتسجيل أحداث البرنامج باستخدام logging. الآن سنضيف طبقة مهمة تجعل المشروع أكثر قابلية للصيانة: اختبارات آلية بسيطة وواضحة.
{alertInfo} الفكرة ببساطة: اختبار الوحدةunit testهو كود صغير يفحص جزءًا محددًا من برنامجك، مثل دالة واحدة. تكتب القيمة المتوقعة، تشغل الاختبار، ثم يخبركunittestهل النتيجة صحيحة أم لا.
{getToc} $title={محتوى المقال}
ما هو unittest في بايثون؟
unittest هو إطار اختبارات مدمج مع بايثون؛ لذلك لا تحتاج إلى تثبيته باستخدام pip. يوفر أدوات لإنشاء الاختبارات وتشغيلها ومقارنة النتيجة الفعلية بالنتيجة التي تتوقعها.
الفكرة الأساسية بسيطة:
- تكتب دالة تؤدي وظيفة محددة.
- تكتب ملف اختبار منفصل يستورد هذه الدالة.
- تحدد المدخلات والنتيجة المتوقعة.
- تشغّل الاختبارات من Terminal.
- تصلح الكود إذا أخبرك الاختبار أن هناك فشلًا.
توثيق بايثون الرسمي يصف unittest بأنه مجموعة أدوات لبناء الاختبارات وتشغيلها، ويعتمد أسلوبًا منظمًا حول TestCase ودوال التأكد المختلفة.
لماذا لا نكتفي بالتجربة اليدوية؟
لنفترض أن لديك دالة تحسب السعر بعد الخصم. عند إنشائها قد تجرب ثلاث قيم يدويًا وتجد أنها تعمل. لكن بعد أسبوع تضيف التحقق من نسبة الخصم، ثم تعدل المعادلة، ثم تنقل الكود إلى ملف آخر. هل ستتذكر تجربة كل حالة قديمة مرة أخرى؟
| التجربة اليدوية | الاختبارات التلقائية |
|---|---|
| تدخل القيم بنفسك في كل مرة. | تشغل الاختبارات كلها بأمر واحد. |
| قد تنسى حالة مهمة. | تسجل الحالات المهمة داخل ملفات الاختبار. |
| مناسبة للتجارب السريعة. | مناسبة لحماية وظائف المشروع مع الوقت. |
| لا تعطي تقريرًا منظمًا عن النجاح والفشل. | توضح اسم الاختبار الفاشل وسبب الفشل. |
الاختبارات لا تغني عن تجربة البرنامج كاملًا، لكنها تحمي الأجزاء التي تعرف كيف يجب أن تعمل.
المثال الذي سنختبره: دالة حساب السعر بعد الخصم
سنستخدم دالة بسيطة، لكنها قريبة من مشروع عملي. تنفذ الدالة ثلاث مهام:
- تتأكد أن السعر ليس سالبًا.
- تتأكد أن نسبة الخصم بين
0و100. - تعيد السعر النهائي بعد الخصم.
أنشئ ملفًا باسم calculator.py واكتب فيه:
def calculate_discount(price, discount):
if price < 0:
raise ValueError("السعر لا يمكن أن يكون سالبًا.")
if not 0 <= discount <= 100:
raise ValueError("نسبة الخصم يجب أن تكون بين 0 و100.")
discount_value = price * discount / 100
return price - discount_value
يمكنك تجربة الدالة يدويًا:
print(calculate_discount(200, 15))
والنتيجة المتوقعة:
170.0
لكننا لا نريد الاكتفاء بهذه التجربة. نريد اختبار حالات متعددة آليًا.
كيف تنظم ملفات الاختبار داخل المشروع؟
في المشاريع الصغيرة جدًا تستطيع وضع ملف الاختبار بجوار ملف الكود. لكن في المشروع المنظم، استخدم مجلدًا مستقلًا باسم tests. هذا يجعل ملفات الاختبار واضحة وسهلة الوصول.
لاحظ قاعدة تسمية مهمة: ابدأ ملف الاختبار بكلمة test_، مثل test_calculator.py. بهذه الطريقة يستطيع unittest العثور على الاختبارات تلقائيًا عند استخدام discovery.
راجع درس تنظيم ملفات مشروع بايثون بطريقة صحيحة لفهم دور المجلدات والملفات داخل أي مشروع ينمو تدريجيًا.
أنشئ أول اختبار باستخدام TestCase
الآن أنشئ الملف tests/test_calculator.py. أولًا نستورد مكتبة unittest، ثم نستورد الدالة التي نريد اختبارها:
import unittest
from calculator import calculate_discount
بعد ذلك ننشئ class يرث من unittest.TestCase. عادة تبدأ أسماء الكلاسات في الاختبارات بكلمة Test لتكون واضحة:
class TestCalculateDiscount(unittest.TestCase):
def test_calculate_discount_with_valid_values(self):
result = calculate_discount(200, 15)
self.assertEqual(result, 170.0)
هناك ثلاث نقاط يجب فهمها هنا:
- اسم method يبدأ بـ
test_حتى يعتبرهunittestاختبارًا. resultهي النتيجة الفعلية التي أعادتها الدالة.assertEqualتقارن بين النتيجة الفعلية والنتيجة المتوقعة.
إذا كانت القيمتان متساويتين ينجح الاختبار. إذا اختلفتا يفشل ويظهر لك تقرير يوضح المشكلة.
تشغيل أول اختبار
افتح Terminal داخل المجلد الرئيسي للمشروع، أي داخل discount_project، ثم نفّذ:
python -m unittest discover -s tests -p "test_*.py" -v
في بعض أنظمة macOS وLinux قد تحتاج إلى:
python3 -m unittest discover -s tests -p "test_*.py" -v
المعاني:
-m unittest: يشغل مكتبةunittestكوحدة.discover: يبحث عن ملفات الاختبار تلقائيًا.-s tests: يبدأ البحث داخل مجلدtests.-p "test_*.py": يبحث عن الملفات التي تبدأ بـtest_.-v: يعرض أسماء الاختبارات والنتائج بتفصيل أكثر.
إذا نجح الاختبار سترى مخرجات قريبة من:
test_calculate_discount_with_valid_values (test_calculator.TestCalculateDiscount) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
أضف حالات اختبار أكثر
الاختبار الحقيقي لا يكتفي بحالة واحدة. أضف الحالات التي يجب أن يحميها برنامجك، مثل عدم وجود خصم، أو خصم كامل، أو سعر عشري.
import unittest
from calculator import calculate_discount
class TestCalculateDiscount(unittest.TestCase):
def test_calculate_discount_with_valid_values(self):
result = calculate_discount(200, 15)
self.assertEqual(result, 170.0)
def test_calculate_discount_with_zero_discount(self):
result = calculate_discount(200, 0)
self.assertEqual(result, 200.0)
def test_calculate_discount_with_full_discount(self):
result = calculate_discount(200, 100)
self.assertEqual(result, 0.0)
def test_calculate_discount_with_decimal_price(self):
result = calculate_discount(99.99, 15)
self.assertAlmostEqual(result, 84.9915, places=4)
استخدمنا assertAlmostEqual مع السعر العشري لأن العمليات العشرية باستخدام float قد تنتج تمثيلًا قريبًا جدًا من القيمة المتوقعة بدل تمثيل مطابق تمامًا في بعض الحالات.
أهم دوال التأكد Assertions في unittest
يوفر unittest دوال كثيرة تبدأ غالبًا بـ assert. لا تحفظها كلها الآن؛ ابدأ بالأكثر استخدامًا:
| الدالة | متى تستخدمها؟ | مثال |
|---|---|---|
assertEqual(a, b) |
عندما تتوقع أن قيمتين متساويتان. | self.assertEqual(total, 50) |
assertNotEqual(a, b) |
عندما تتوقع أن قيمتين مختلفتان. | self.assertNotEqual(status, "failed") |
assertTrue(value) |
عندما تتوقع قيمة صحيحة. | self.assertTrue(is_valid) |
assertFalse(value) |
عندما تتوقع قيمة غير صحيحة. | self.assertFalse(is_empty) |
assertIsNone(value) |
عندما تتوقع None. |
self.assertIsNone(result) |
assertAlmostEqual(a, b) |
لمقارنة الأرقام العشرية تقريبًا. | self.assertAlmostEqual(price, 99.99) |
assertRaises(Error) |
عندما تتوقع أن الكود يرفع استثناء محددًا. | with self.assertRaises(ValueError): |
اختبار الأخطاء باستخدام assertRaises
اختبار الخطأ لا يعني أن برنامجك فشل. أحيانًا تريد أن تتأكد أن الدالة ترفض البيانات غير الصحيحة بطريقة مقصودة. في دالتنا، من الطبيعي أن يظهر ValueError عندما يكون السعر سالبًا أو الخصم خارج النطاق.
أضف الاختبارات التالية داخل TestCalculateDiscount:
def test_negative_price_raises_value_error(self):
with self.assertRaises(ValueError):
calculate_discount(-50, 10)
def test_discount_over_100_raises_value_error(self):
with self.assertRaises(ValueError):
calculate_discount(200, 150)
المعنى: ينجح الاختبار فقط إذا رفعت الدالة ValueError. أما إذا لم يظهر الخطأ، فسيعتبر unittest الاختبار فاشلًا؛ لأن الكود قبل قيمة غير صالحة دون حماية.
الفرق بين assert العادي وunittest
ربما تعرفت سابقًا على كلمة assert داخل الكود. توجد علاقة في الاسم، لكن الاستخدام مختلف:
assert العادي |
unittest |
|---|---|
| تعليمة داخل الكود للتحقق من افتراض أو شرط. | إطار منظم لإنشاء وتشغيل مجموعة اختبارات. |
مثال: assert age >= 0 |
مثال: self.assertEqual(result, 10) |
| مناسب للتحقق الداخلي أثناء التطوير. | مناسب لاختبار الدوال والكلاسات بشكل متكرر ومنظم. |
| ليس تقرير اختبارات كاملًا. | يعرض عدد الاختبارات الناجحة والفاشلة. |
راجع أساسيات بايثون 33: شرح assert واختبار صحة الشروط إذا أردت تثبيت الفرق بين التحقق داخل الكود والاختبارات الآلية.
استخدم setUp لتجهيز بيانات مشتركة
عندما تحتاج إلى نفس البيانات قبل كل اختبار، بدل تكرارها داخل كل method استخدم setUp(). تنفذ هذه الدالة قبل كل اختبار داخل class.
import unittest
from calculator import calculate_discount
class TestCalculateDiscount(unittest.TestCase):
def setUp(self):
self.price = 200
def test_discount_10_percent(self):
result = calculate_discount(self.price, 10)
self.assertEqual(result, 180.0)
def test_discount_25_percent(self):
result = calculate_discount(self.price, 25)
self.assertEqual(result, 150.0)
استخدم setUp() عندما توجد بيانات إعداد مشتركة بوضوح. أما إذا كانت كل حالة مختلفة جدًا، فاجعل البيانات داخل الاختبار نفسه حتى يبقى كل اختبار مفهومًا وحده.
ماذا تفعل عندما يفشل الاختبار؟
فشل الاختبار ليس مشكلة بحد ذاته؛ إنه إشارة مفيدة. قد يكون السبب أحد ثلاثة أمور:
- يوجد خطأ في الدالة التي تختبرها.
- النتيجة المتوقعة التي كتبتها غير صحيحة.
- تغيرت متطلبات المشروع، وتحتاج إلى تحديث الاختبار والكود معًا.
مثال: لو غيرت الدالة بالخطأ إلى:
return price + discount_value
بدل:
return price - discount_value
سيكشف اختبار assertEqual(result, 170.0) الخطأ فورًا، حتى لو لم تتذكر تشغيل سيناريو الخصم يدويًا.
{alertWarning} نصيحة مهمة: لا تعدّل القيمة المتوقعة داخل الاختبار فقط حتى يصبح أخضر. افهم أولًا: هل تغير السلوك المطلوب فعلًا، أم أن تعديلك الأخير كسر النتيجة الصحيحة؟
اختبر الدوال الصغيرة أولًا
عند بدء استخدام الاختبارات، لا تحاول اختبار البرنامج كاملًا دفعة واحدة. ابدأ بدوال صغيرة وواضحة، مثل:
- دالة حساب سعر بعد خصم.
- دالة تتحقق من صحة بريد إلكتروني أو كلمة مرور وفق شروط محددة.
- دالة تقرأ قيمة من صف CSV ثم تحولها إلى نوع مناسب.
- دالة تنسق اسم ملف أو مسار.
- دالة تحسب إجمالي قائمة أسعار.
كلما كانت الدالة لها مدخلات ومخرجات واضحة، كان اختبارها أسهل. هذا أحد أسباب فائدة تنظيم الكود إلى دوال صغيرة بدل وضع كل منطق المشروع داخل ملف طويل.
اختبارات unittest مع برنامج سطر أوامر
في برنامج CLI، لا تبدأ باختبار input() أو طباعة Terminal مباشرة. افصل منطق العمل في دوال، ثم اجعل ملف CLI يستدعيها. هكذا يمكنك اختبار المنطق بعيدًا عن واجهة الأوامر.
بدل هذا التصميم المختلط:
# تصميم يصعب اختباره
price = float(input("السعر: "))
discount = float(input("الخصم: "))
print(price - (price * discount / 100))
اكتب منطق الحساب في دالة مستقلة، ثم استدعها من برنامج CLI:
# calculator.py
def calculate_discount(price, discount):
return price - (price * discount / 100)
# main.py
from calculator import calculate_discount
price = float(input("السعر: "))
discount = float(input("الخصم: "))
print(calculate_discount(price, discount))
بهذه الطريقة تختبر calculate_discount() بسهولة، وتبقي واجهة CLI بسيطة. راجع درس بناء برنامج سطر أوامر CLI بسيط لتطوير هذا الأسلوب داخل مشروع منظم.
تشغيل الاختبارات قبل حفظ التعديلات أو رفع المشروع
اجعل تشغيل الاختبارات عادة بسيطة قبل مشاركة مشروعك أو إضافة ميزة جديدة. في المشاريع الصغيرة يكفي تنفيذ الأمر من Terminal. ومع نمو المشروع، يمكن لاحقًا تشغيل الاختبارات تلقائيًا داخل خدمات التكامل المستمر، لكن لا تحتاج إلى ذلك في هذه المرحلة.
الأمر الذي ستحتاجه غالبًا هو:
python -m unittest discover -s tests -p "test_*.py" -v
أخطاء شائعة عند استخدام unittest
1. نسيان أن يبدأ اسم الاختبار بـ test_
إذا كتبت method باسم check_discount() بدل test_discount()، فلن يعتبرها unittest اختبارًا تلقائيًا عند discovery.
2. وضع ملف الاختبار باسم غير مناسب
سمِّه مثل test_calculator.py بدل اسم غامض مثل checks.py. هذا يجعل العثور على الاختبارات وتشغيلها أسهل.
3. اختبار أكثر من فكرة في اختبار واحد
الأفضل أن يفحص كل test سلوكًا محددًا. عندما يفشل، ستعرف مباشرة ما الذي يحتاج إلى إصلاح.
4. الاعتماد على ترتيب تنفيذ الاختبارات
يجب أن يعمل كل اختبار مستقلًا. لا تفترض أن اختبارًا آخر شغّل شيئًا قبله أو غيّر قيمة يحتاجها اختبارك.
5. اختبار تفاصيل داخلية بدل النتيجة المطلوبة
ركز على ما يفترض أن تعيده الدالة أو الخطأ الذي يجب أن ترفعه، بدل ربط الاختبار بتفاصيل تنفيذ لا يراها المستخدم.
6. تجاهل الاختبارات بعد تعديل الكود
قيمة الاختبارات تظهر عندما تشغلها باستمرار، خصوصًا بعد التعديلات التي تبدو صغيرة.
تمرين عملي
أنشئ دالة باسم calculate_average(numbers) تعيد متوسط قائمة من الأرقام. ثم اكتب اختبارات تتحقق من الحالات التالية:
- قائمة تحتوي على أرقام صحيحة.
- قائمة تحتوي على أرقام عشرية.
- قائمة فارغة ترفع
ValueError.
حل مقترح: ملف average.py
def calculate_average(numbers):
if not numbers:
raise ValueError("لا يمكن حساب متوسط قائمة فارغة.")
return sum(numbers) / len(numbers)
حل مقترح: ملف tests/test_average.py
import unittest
from average import calculate_average
class TestCalculateAverage(unittest.TestCase):
def test_average_of_integers(self):
result = calculate_average([10, 20, 30])
self.assertEqual(result, 20.0)
def test_average_of_decimals(self):
result = calculate_average([1.5, 2.5])
self.assertAlmostEqual(result, 2.0)
def test_empty_list_raises_value_error(self):
with self.assertRaises(ValueError):
calculate_average([])
روابط داخلية مفيدة من بايثون العرب
- بايثون بعد الأساسيات 5: شرح logging لتسجيل أحداث البرنامج والأخطاء
- بايثون بعد الأساسيات 4: بناء برنامج سطر أوامر CLI بسيط
- بايثون بعد الأساسيات 2: تنظيم ملفات مشروع بايثون بطريقة صحيحة
- أساسيات بايثون 33: شرح assert واختبار صحة الشروط
- أساسيات بايثون 31: شرح try وexcept للمبتدئين
- كورس بايثون بعد الأساسيات
الخلاصة
unittestمكتبة قياسية مدمجة مع بايثون لإنشاء اختبارات تلقائية.- اكتب الاختبارات في ملفات تبدأ بـ
test_وضعها داخل مجلدtestsفي المشروع المنظم. - استخدم
unittest.TestCaseلإنشاء مجموعة اختبارات مرتبطة بوظيفة أو class محدد. - ابدأ بـ
assertEqualوassertAlmostEqualوassertRaises. - استخدم
assertRaisesللتأكد من أن الدالة ترفض القيم غير الصحيحة بالطريقة المتوقعة. - شغّل الاختبارات عبر
python -m unittest discover -s tests -p "test_*.py" -v. - افصل منطق البرنامج في دوال صغيرة حتى تصبح الاختبارات أسهل وأوضح.
{alertSuccess} قاعدة ذهبية: كلما أضفت دالة مهمة إلى مشروعك، فكّر في ثلاث حالات على الأقل لاختبارها: حالة عادية، وحالة حدّية، وحالة بيانات غير صحيحة يجب أن تتعامل معها الدالة بوضوح.
مصادر خارجية رسمية للتوسع
أسئلة شائعة
هل أحتاج إلى تثبيت unittest؟
لا. مكتبة unittest تأتي ضمن المكتبة القياسية في بايثون، لذلك تستطيع استيرادها واستخدامها مباشرة.
ما الفرق بين unittest وpytest؟
unittest مدمج مع بايثون ويكفي لتعلم أساسيات الاختبارات وتنظيمها. pytest أداة خارجية شائعة توفر أسلوبًا مختصرًا وميزات إضافية، لكن البدء بـ unittest مناسب لفهم المفاهيم الأساسية دون تثبيت أدوات جديدة.
كيف أسمي ملف الاختبار؟
استخدم اسمًا يبدأ بـ test_ مثل test_calculator.py. هذا يسهل على unittest discover العثور عليه تلقائيًا.
هل أختبر كل سطر من الكود؟
لا تبدأ بهذا الهدف. اختبر السلوك المهم: المدخلات المتوقعة، الحالات الحدّية، والبيانات غير الصحيحة التي يجب أن تتعامل معها الدالة أو ترفضها.
متى أستخدم assertRaises؟
استخدمها عندما يكون رفع استثناء معين سلوكًا صحيحًا ومقصودًا، مثل محاولة حساب خصم بنسبة أكبر من 100 أو حساب متوسط قائمة فارغة.
مشكلة وحل 21: حل خطأ UnboundLocalError local variable referenced before assignment في Python
قد تعرّف متغيرًا خارج دالة في Python، ثم تستدعيه داخل الدالة، فتظهر لك رسالة مثل:
UnboundLocalError: local variable 'count' referenced before assignment
وفي إصدارات حديثة من Python قد تظهر بصيغة قريبة:
UnboundLocalError: cannot access local variable 'count' where it is not associated with a value
الخطأ محيّر لأن المتغير قد يكون موجودًا فعلًا خارج الدالة. لكن Python ترى أن هناك عملية إسناد مثل count = ... أو count += 1 داخل الدالة، فتتعامل مع الاسم كمتغير محلي في كامل الدالة. وعند محاولة قراءته قبل أن يحصل على قيمة محلية، يظهر الخطأ.
{alertInfo} الفكرة ببساطة: إذا عدّلت اسم متغير داخل دالة، تعتبره Python متغيرًا محليًا تلقائيًا ما لم تعلن بوضوح أنهglobalأوnonlocal. لذلك لا يمكنك قراءته قبل أن يحصل على قيمة داخل الدالة.
{getToc} $title={محتوى المقال}
ما معنى UnboundLocalError في Python؟
UnboundLocalError هو نوع خاص من NameError. يظهر عندما تحاول دالة استخدام متغير محلي قبل أن ترتبط به قيمة. بمعنى أبسط: Python تعرف أن الاسم تابع للدالة الحالية، لكنها لم تجد له قيمة وقت استخدامه.
| جزء الرسالة | معناه |
|---|---|
UnboundLocalError | المتغير اعتُبر محليًا داخل الدالة، لكنه لم يحصل على قيمة محلية بعد. |
local variable | Python تتعامل مع الاسم كمتغير تابع للدالة الحالية. |
referenced before assignment | تمت قراءة المتغير قبل تنفيذ سطر يعطيه قيمة. |
أبسط مثال يسبب الخطأ
score = 10
def increase_score():
print(score)
score = score + 1
increase_score()
قد تتوقع أن يطبع الكود 10 ثم يزيد القيمة، لكنه سيظهر خطأ. السبب أن السطر التالي يحتوي على إسناد إلى score داخل الدالة:
score = score + 1
لذلك Python تقرر أن score محلي في هذه الدالة كلها. وعندما تصل إلى print(score)، تحاول قراءة المتغير المحلي قبل أن يحصل على قيمة.
ما هو Scope أو نطاق المتغير؟
نطاق المتغير scope يحدد المكان الذي يمكن الوصول إلى المتغير منه. لفهم هذا الخطأ نحتاج إلى معرفة ثلاثة نطاقات:
| النوع | أين يُعرّف؟ | مثال |
|---|---|---|
متغير عام global | خارج الدوال في مستوى الملف. | score = 10 |
متغير محلي local | داخل دالة. | name = "Ali" داخل def. |
متغير من نطاق خارجي nonlocal | داخل دالة متداخلة، ويخص الدالة المحيطة. | متغير داخل outer() تستخدمه inner(). |
يمكنك قراءة متغير عام داخل دالة ما دمت لا تعيد إسناد قيمة إليه داخلها. أما إذا عدلته أو كتبت += عليه، يجب أن تحدد بوضوح ما إذا كنت تريد متغيرًا محليًا أم متغيرًا خارجيًا.
لماذا لا يظهر الخطأ عند القراءة فقط؟
site_name = "بايثون العرب"
def show_site_name():
print(site_name)
show_site_name()
هذا الكود يعمل لأن الدالة تقرأ site_name فقط، ولا توجد داخلها عملية إسناد إلى الاسم نفسه.
لكن عند كتابة:
site_name = "بايثون العرب"
def change_site_name():
site_name = "Arab Python"
print(site_name)
change_site_name()
print(site_name)
لن يحدث خطأ هنا، لكن القيمة الجديدة داخل الدالة محلية ومستقلة، لذلك يبقى المتغير الخارجي كما هو:
Arab Python
بايثون العرب
السبب الأكثر شيوعًا: استخدام += داخل الدالة
الاختصار += يتضمن عملية إسناد. لهذا يسبب الخطأ عند استخدامه على متغير خارجي دون توضيح:
visits = 0
def add_visit():
visits += 1
print(visits)
add_visit()
تتعامل Python مع visits هنا كمتغير محلي، لكنها لا تملك قيمة محلية له قبل محاولة الزيادة.
الحل الأفضل غالبًا: مرّر القيمة وأعد النتيجة
في أغلب البرامج، الأفضل ألا تعتمد على متغير عام. مرّر القيمة إلى الدالة ثم أعد القيمة الجديدة. هذا يجعل الكود أوضح وأسهل في الاختبار.
def increase_score(score):
return score + 1
score = 10
score = increase_score(score)
print(score)
11
حل آخر: هيّئ المتغير المحلي قبل استخدامه
إذا كان المتغير يجب أن يكون محليًا داخل الدالة، أعطه قيمة أولية قبل استخدام +=.
مثال خاطئ
def calculate_total(prices):
for price in prices:
total += price
return total
الحل
def calculate_total(prices):
total = 0
for price in prices:
total += price
return total
print(calculate_total([10, 20, 30]))
60
هذه من أكثر الحالات تكرارًا: كتابة count += 1 أو total += price قبل تهيئة المتغير داخل النطاق المناسب.
هل أستخدم global لحل الخطأ؟
يمكن استخدام global عندما تحتاج فعلًا إلى تعديل متغير عام من داخل دالة. لكنه ليس الحل الافتراضي، لأن كثرة المتغيرات العامة تجعل البرنامج أصعب في الفهم والتتبع.
visits = 0
def add_visit():
global visits
visits += 1
print(f"عدد الزيارات: {visits}")
add_visit()
add_visit()
عدد الزيارات: 1
عدد الزيارات: 2
{alertWarning}
تنبيه: لا تستخدم global فقط لإصلاح الخطأ بسرعة. في أغلب الدوال، تمرير القيم وإرجاع النتائج أوضح وأكثر قابلية للصيانة.
متى نستخدم nonlocal؟
تستخدم nonlocal مع الدوال المتداخلة. وهي تعني أن المتغير موجود في الدالة الخارجية المحيطة، وليس في مستوى الملف كله.
def create_counter():
count = 0
def increase():
nonlocal count
count += 1
return count
return increase
counter = create_counter()
print(counter())
print(counter())
1
2
بدون nonlocal count ستعامل Python المتغير داخل increase() كمتغير محلي جديد، ثم يظهر UnboundLocalError.
الفرق بين NameError وUnboundLocalError
| الخطأ | متى يظهر؟ | مثال |
|---|---|---|
NameError | عندما لا تجد Python الاسم في النطاقات المتاحة أصلًا. | print(user_name) دون تعريف user_name. |
UnboundLocalError | عندما يكون الاسم محليًا داخل الدالة، لكنك استخدمته قبل إعطائه قيمة. | count += 1 قبل تهيئة count. |
بمعنى آخر: UnboundLocalError هو حالة أكثر تحديدًا من NameError.
أخطاء شائعة تسبب UnboundLocalError
1. تعريف متغير داخل شرط قد لا يعمل
def get_discount(is_member):
if is_member:
discount = 20
return discount
عندما تكون is_member بقيمة False، لا ينفذ الشرط ولا يحصل discount على قيمة. الحل هو إعطاء قيمة افتراضية قبل الشرط:
def get_discount(is_member):
discount = 0
if is_member:
discount = 20
return discount
2. تعريف متغير داخل try ثم استخدامه بعد except
def read_age(text):
try:
age = int(text)
except ValueError:
print("العمر غير صالح")
return age
إذا حدث ValueError، لن يحصل age على قيمة. الحل أن تعيد قيمة مناسبة من except:
def read_age(text):
try:
return int(text)
except ValueError:
print("العمر غير صالح")
return None
3. استخدام += أو -= قبل القيمة الأولية
لا تكتب count += 1 أو total += price إلا بعد تهيئة المتغير داخل النطاق الصحيح.
4. توقع أن if أو try تعمل دائمًا
إذا عرفت متغيرًا في مسار واحد فقط من التنفيذ، اسأل: ماذا يحدث في المسار الآخر؟ ضع قيمة افتراضية أو أعد نتيجة واضحة من كل فرع.
مثال عملي كامل: حساب إجمالي طلب بأمان
def calculate_order_total(prices):
total = 0
for price in prices:
total += price
return total
order_prices = [15, 25, 40]
print(f"إجمالي الطلب: {calculate_order_total(order_prices)}")
print(f"إجمالي السلة الفارغة: {calculate_order_total([])}")
إجمالي الطلب: 80
إجمالي السلة الفارغة: 0
كل استدعاء للدالة يبدأ بقيمة محلية جديدة وواضحة، ولهذا لا يعتمد البرنامج على حالة مخفية أو متغير عالمي.
خطوات تشخيص الخطأ بسرعة
- اقرأ اسم المتغير داخل رسالة الخطأ.
- ابحث داخل الدالة عن أي سطر يسند قيمة لهذا الاسم، مثل
=أو+=. - تحقق هل استخدمت المتغير قبل سطر الإسناد.
- إذا كان المتغير يعرف داخل
ifأوtry، افحص جميع مسارات التنفيذ. - اسأل: هل أحتاج متغيرًا محليًا، أم أريد فعلًا تعديل متغير خارجي؟
- اختر الحل الأنسب: تهيئة محلية، تمرير وإرجاع قيمة،
global، أوnonlocal.
روابط داخلية مفيدة من بايثون العرب
- مشكلة وحل 9: حل خطأ NameError name is not defined في بايثون
- أساسيات بايثون 31: شرح try و except في Python للمبتدئين
- أساسيات بايثون 34: شرح class وobject في Python للمبتدئين
- سلسلة مشكلة وحل في بايثون
الخلاصة
- خطأ
UnboundLocalErrorيعني أنك حاولت استخدام متغير محلي قبل أن يحصل على قيمة. - وجود إسناد إلى اسم داخل دالة يجعل Python تعتبره محليًا في الدالة كلها، حتى لو كان هناك متغير عام بالاسم نفسه.
- هيّئ المتغيرات مثل
total = 0وcount = 0قبل استخدام+=. - لا تعرف متغيرًا داخل شرط أو
tryفقط ثم تفترض أنه موجود في كل الحالات. - مرّر القيم إلى الدوال وأعد النتائج كلما كان ذلك ممكنًا؛ فهو أوضح من الاعتماد على
global. - استخدم
globalلتعديل متغير عام عند الحاجة المقصودة، وnonlocalلتعديل متغير في دالة خارجية محيطة.
{alertSuccess} قاعدة ذهبية: قبل كتابةcount += 1أوtotal += value، تأكد أين تم إنشاء المتغير وبأي قيمة. في أغلب الدوال، ابدأ بقيمة محلية واضحة ثم أعد النتيجة.
مصادر خارجية رسمية للتوسع
- UnboundLocalError — Python Built-in Exceptions
- Execution model and name resolution — Python Documentation
- Python FAQ: Why am I getting an UnboundLocalError?
أسئلة شائعة
ما معنى local variable referenced before assignment في Python؟
تعني أنك استخدمت متغيرًا اعتبرته Python محليًا داخل دالة، لكنك حاولت قراءته قبل إعطائه قيمة داخل الدالة.
لماذا يظهر UnboundLocalError رغم أن المتغير معرف خارج الدالة؟
لأن وجود عملية إسناد إلى الاسم نفسه داخل الدالة، مثل x = ... أو x += 1، يجعل Python تعتبره متغيرًا محليًا داخل تلك الدالة.
هل global هو الحل الأفضل دائمًا؟
لا. غالبًا يكون تمرير القيمة إلى الدالة ثم إرجاع القيمة الجديدة أوضح وأسهل في الصيانة. استخدم global فقط عندما تريد فعلًا تعديل متغير عام مشترك.
ما الفرق بين global وnonlocal؟
global يشير إلى متغير موجود في مستوى الملف خارج الدوال. أما nonlocal فيشير إلى متغير موجود داخل دالة خارجية تحيط بدالة متداخلة.
كيف أتجنب الخطأ داخل if أو try؟
هيئ المتغير بقيمة افتراضية قبل if أو try، أو أعد قيمة مناسبة من كل فرع ممكن حتى لا يبقى المتغير دون قيمة.
July 03, 2026
بايثون العرب
مشكلة وحل 20: حل خطأ IndexError: list index out of range في Python
قد تكتب قائمة في Python، ثم تحاول قراءة عنصر منها، فتظهر لك رسالة مزعجة مثل:
IndexError: list index out of range
هذا الخطأ من أكثر أخطاء القوائم شيوعًا عند المبتدئين. لا يعني أن القائمة غير صحيحة، بل يعني أن الكود طلب عنصرًا برقم فهرس index غير موجود داخلها. يحدث ذلك عادةً لأن الفهرسة تبدأ من الصفر، أو بسبب استخدام len() بطريقة غير صحيحة، أو بسبب حلقة for تتجاوز آخر عنصر، أو لأن القائمة أصبحت فارغة دون أن تنتبه.
في هذا الدرس ستفهم معنى الخطأ خطوة بخطوة، وستتعلم كيف تشخصه بسرعة وتمنعه من الظهور في مشاريعك بدل أن تكتفي بإخفائه.
{alertInfo} الفكرة ببساطة: إذا كانت القائمة تحتوي علىnعناصر، فإن آخر index صالح هوn - 1وليسn. لذلك إذا كان طول القائمة3، فالفهارس الصحيحة هي0و1و2فقط.
{getToc} $title={محتوى المقال}
ما معنى IndexError: list index out of range؟
كلمة IndexError تعني أن Python حاولت الوصول إلى موضع غير موجود داخل تسلسل مرتب مثل list أو tuple أو string. وفي هذه المشكلة تحديدًا، الجزء:
list index out of range
يخبرك أن الرقم الذي استخدمته بين الأقواس المربعة [] أكبر من آخر فهرس موجود في القائمة، أو أن القائمة فارغة.
مثال:
colors = ["red", "blue", "green"]
print(colors[3])
سيظهر الخطأ لأن القائمة تحتوي ثلاثة عناصر فقط، وفهارسها هي 0 و1 و2. لا يوجد عنصر في الموضع 3.
Traceback (most recent call last):
...
IndexError: list index out of range
لماذا تبدأ فهرسة القوائم من الصفر؟
في Python، أول عنصر في القائمة يأخذ الفهرس 0 وليس 1. هذا يسمى zero-based indexing.
languages = ["Python", "JavaScript", "Java", "C++"]
print(languages[0])
print(languages[1])
print(languages[2])
print(languages[3])
Python
JavaScript
Java
C++
| العنصر | الفهرس الصحيح |
|---|---|
"Python" |
0 |
"JavaScript" |
1 |
"Java" |
2 |
"C++" |
3 |
لذلك إذا حاولت الوصول إلى languages[4]، سيظهر الخطأ لأن الرقم 4 خارج نطاق القائمة.
الفرق بين len(list) وآخر index
الدالة len() تعطيك عدد العناصر داخل القائمة، لكنها لا تعطيك آخر index مباشرة.
names = ["Ali", "Sara", "Omar"]
print(len(names))
3
قد يظن البعض أن هذا يعني أن:
print(names[len(names)])
سيطبع العنصر الأخير، لكنه سيظهر خطأ. السبب أن len(names) تساوي 3، بينما آخر index صالح يساوي 2.
إذا أردت آخر عنصر باستخدام الطول، اكتب:
print(names[len(names) - 1])
لكن الطريقة الأبسط والأوضح غالبًا هي استخدام الفهرس السالب -1:
print(names[-1])
Omar
الفهرس -1 يعني آخر عنصر، و-2 يعني العنصر الذي قبله. لكن انتبه: إذا كانت القائمة فارغة، فإن names[-1] سيظهر أيضًا خطأ IndexError.
السبب الأول: طلب عنصر برقم أكبر من آخر index
هذا هو السبب المباشر والأكثر وضوحًا.
مثال خاطئ
fruits = ["apple", "banana"]
print(fruits[2])
الفهارس المتاحة هنا هي 0 و1 فقط.
الحل
استخدم فهرسًا موجودًا:
print(fruits[1])
أو افحص أن الفهرس ضمن المدى قبل الوصول إليه:
index = 2
if 0 <= index < len(fruits):
print(fruits[index])
else:
print("هذا الفهرس غير موجود داخل القائمة.")
السبب الثاني: استخدام range(len(list) + 1) داخل for loop
هذا الخطأ منتشر جدًا. الدالة range() لا تشمل الرقم الأخير، لذلك عندما تريد المرور على الفهارس الصحيحة لقائمة، تكتب:
for index in range(len(items)):
print(items[index])
لكن بعض المبتدئين يكتبون:
for index in range(len(items) + 1):
print(items[index])
دعنا نطبق ذلك على قائمة من ثلاثة عناصر:
items = ["A", "B", "C"]
for index in range(len(items) + 1):
print(items[index])
هنا ستنتج range(4) الأرقام 0 و1 و2 و3. والفهرس 3 غير موجود، لذلك يظهر الخطأ في آخر دورة.
الحل الأول: احذف + 1
for index in range(len(items)):
print(items[index])
الحل الأفضل في أغلب الحالات: مرّ على العناصر مباشرة
إذا كنت لا تحتاج إلى رقم الفهرس، لا تستخدمه من الأساس:
for item in items:
print(item)
وإذا احتجت العنصر ورقمه معًا: استخدم enumerate
for index, item in enumerate(items):
print(index, item)
0 A
1 B
2 C
السبب الثالث: القائمة فارغة
حتى لو استخدمت فهرسًا صحيحًا مثل 0 أو -1، سيظهر الخطأ إذا لم تكن القائمة تحتوي على أي عنصر.
tasks = []
print(tasks[0])
الحل هو التحقق من وجود عناصر أولًا:
if tasks:
print(tasks[0])
else:
print("لا توجد مهام داخل القائمة.")
في Python، القائمة الفارغة تعتبر قيمة False داخل شرط if، والقائمة التي تحتوي على عنصر واحد على الأقل تعتبر True.
السبب الرابع: استخدام pop على قائمة فارغة
الدالة pop() تزيل عنصرًا من القائمة وتعيده. إذا استدعيتها على قائمة فارغة، ستظهر IndexError.
cart = []
last_item = cart.pop()
اكتب التحقق أولًا:
if cart:
last_item = cart.pop()
print(f"تم حذف: {last_item}")
else:
print("السلة فارغة، لا يوجد عنصر لحذفه.")
كذلك، pop(index) سيظهر الخطأ إذا كان index خارج نطاق القائمة.
السبب الخامس: القائمة تغيرت داخل الحلقة
قد تحسب طول القائمة، ثم تحذف عناصر منها داخل الحلقة، ثم تحاول استخدام الفهارس القديمة. هذا قد يسبب تجاوزًا للنطاق.
مثال يحتاج حذرًا
numbers = [10, 20, 30, 40]
for index in range(len(numbers)):
if numbers[index] > 20:
numbers.pop(index)
عند حذف عنصر، تقصر القائمة وتتحرك العناصر. لذلك قد يصبح index الذي ستصل إليه في دورة لاحقة غير صالح.
حل واضح: أنشئ قائمة جديدة
numbers = [10, 20, 30, 40]
filtered_numbers = []
for number in numbers:
if number <= 20:
filtered_numbers.append(number)
print(filtered_numbers)
أو استخدم list comprehension عندما تصبح مرتاحًا معها:
filtered_numbers = [number for number in numbers if number <= 20]
الفرق بين indexing وslicing مهم جدًا
الوصول إلى عنصر واحد باستخدام indexing مثل:
letters = ["A", "B", "C"]
print(letters[10])
سيظهر خطأ لأن العنصر غير موجود.
لكن slicing مثل:
print(letters[10:])
لا يظهر خطأ عادة، بل يعيد قائمة فارغة:
[]
السبب أن slicing تتعامل بلطف مع الحدود خارج نطاق التسلسل، بينما indexing يتطلب عنصرًا موجودًا فعلًا في الموضع المطلوب.
هل أستخدم try وexcept لحل IndexError؟
يمكن استخدام try وexcept عندما يكون غياب العنصر احتمالًا طبيعيًا في برنامجك، لكن لا تستخدمها لإخفاء خطأ في منطق الكود.
مثال مناسب:
messages = []
try:
latest_message = messages[-1]
print(latest_message)
except IndexError:
print("لا توجد رسائل حتى الآن.")
لكن في كثير من الحالات، يكون شرط واضح أسهل في القراءة:
if messages:
print(messages[-1])
else:
print("لا توجد رسائل حتى الآن.")
{alertWarning}
نصيحة مهمة: لا تكتب except IndexError: pass فقط لإخفاء المشكلة. حاول أولًا معرفة لماذا وصل الكود إلى فهرس غير صالح، ثم ضع معالجة مفهومة للمستخدم أو صحح منطق الحلقة.
مثال عملي: اختيار عنصر من قائمة بطريقة آمنة
البرنامج التالي يعرض قائمة دورات، ثم يسمح للمستخدم باختيار دورة برقم يبدأ من 1. نطرح 1 لأن فهارس Python تبدأ من الصفر، ونتحقق من أن الرقم ضمن النطاق قبل الوصول إلى القائمة.
courses = [
"أساسيات بايثون",
"القوائم في Python",
"الدوال في Python",
"البرمجة كائنية التوجه"
]
for number, course in enumerate(courses, start=1):
print(f"{number}. {course}")
try:
user_choice = int(input("اختر رقم الدورة: "))
index = user_choice - 1
if 0 <= index < len(courses):
print(f"اخترت: {courses[index]}")
else:
print("اختر رقمًا من القائمة الظاهرة فقط.")
except ValueError:
print("الرجاء إدخال رقم صحيح.")
هذا المثال يعالج مشكلتين مختلفتين:
ValueErrorإذا أدخل المستخدم نصًا بدل رقم.- التحقق من index قبل الوصول إلى القائمة لتجنب
IndexError.
خطوات تشخيص الخطأ بسرعة
- اقرأ السطر الذي يشير إليه traceback؛ غالبًا ستجد فيه
list[index]أوlist.pop(index). - اطبع القائمة وطولها مؤقتًا باستخدام
print(items)وprint(len(items)). - اطبع قيمة index قبل استخدامه.
- تأكد أن القيمة تحقق الشرط
0 <= index < len(items). - إذا كانت داخل حلقة، راجع
range()وتأكد أنك لم تكتب+ 1دون حاجة. - إذا كنت تحذف عناصر، لا تعتمد على فهارس قديمة بعد أن يتغير طول القائمة.
أخطاء قريبة من IndexError
| الخطأ | المعنى المختصر | مثال شائع |
|---|---|---|
IndexError |
طلب موضع غير موجود داخل list أو string أو tuple. | items[10] مع قائمة قصيرة. |
KeyError |
طلب مفتاح غير موجود داخل dictionary. | user["email"] عندما لا يوجد المفتاح. |
ValueError |
القيمة غير مناسبة للعملية المطلوبة. | int("12a"). |
TypeError |
استخدمت نوعًا غير مناسب في عملية ما. | محاولة استخدام فهرس نصي مع list. |
يمكنك مراجعة مشكلة وحل 18: حل خطأ ValueError عند تحويل النص إلى رقم لفهم أحد الأخطاء القريبة التي تظهر كثيرًا مع إدخال المستخدم.
روابط داخلية مفيدة من بايثون العرب
- سلسلة مشكلة وحل في بايثون
- بايثون بالمثال 14: عدّ تكرار العناصر في List باستخدام Python
- مشكلة وحل 18: حل خطأ ValueError عند تحويل النص إلى رقم
- كورس أساسيات بايثون للمبتدئين
الخلاصة
- خطأ
IndexError: list index out of rangeيعني أنك طلبت عنصرًا غير موجود داخل القائمة. - الفهارس تبدأ من
0، لذلك آخر index في قائمة طولهاnهوn - 1. - لا تستخدم
items[len(items)]للوصول إلى العنصر الأخير؛ استخدمitems[-1]بعد التأكد أن القائمة ليست فارغة. - استخدم
range(len(items))بدون+ 1، أو مرّ على العناصر مباشرة باستخدامfor item in items. - تحقق من القوائم الفارغة قبل استخدام
[0]أو[-1]أوpop(). - استخدم
try/exceptلمعالجة حالة متوقعة، وليس لإخفاء خطأ في منطق البرنامج.
{alertSuccess} قاعدة ذهبية: قبل الوصول إلىitems[index]، تأكد أنindexموجود فعلًا داخل المدى:0 <= index < len(items). وعندما لا تحتاج إلى الفهرس، مرّ على العناصر مباشرة.
مصادر خارجية رسمية للتوسع
- IndexError — Python Built-in Exceptions
- An Informal Introduction to Python — Indexing and Slicing
- Data Structures — Python Documentation
أسئلة شائعة
ما معنى list index out of range في Python؟
تعني أن كود Python حاول الوصول إلى عنصر غير موجود داخل القائمة. يحدث ذلك عندما يكون رقم الفهرس أكبر من آخر index صالح، أو عندما تكون القائمة فارغة.
ما هو آخر index في قائمة طولها 5؟
آخر index هو 4، لأن الفهرسة تبدأ من الصفر: 0 و1 و2 و3 و4.
لماذا يظهر الخطأ عند استخدام len(list)؟
لأن len(list) يعيد عدد العناصر وليس آخر index. للوصول إلى آخر عنصر استخدم list[len(list) - 1] أو list[-1] بعد التحقق أن القائمة ليست فارغة.
هل يمكن أن يظهر IndexError مع String؟
نعم. أي تسلسل يمكن الوصول إلى عناصره بالفهرس قد يظهر معه الخطأ، مثل string وtuple. لكن هذا المقال يركز على القوائم لأن رسالة الخطأ هنا تقول list index out of range.
هل try/except هو أفضل حل دائمًا؟
لا. إذا كان لديك شرط واضح مثل فحص طول القائمة أو التحقق من index، فهو غالبًا أوضح. استخدم try/except عندما يكون عدم وجود العنصر حالة متوقعة أثناء تشغيل البرنامج.
أساسيات بايثون 36: شرح الوراثة Inheritance في Python للمبتدئين
بعد أن تعلمت في الدروس السابقة معنى class وobject والفرق بين خصائص الكلاس وخصائص الكائن، ستلاحظ أن بعض الكلاسات تتشابه كثيرًا. مثلًا: الطالب والمعلم كلاهما يملك اسمًا وبريدًا إلكترونيًا وطريقة لعرض معلوماته، لكن لكل واحد منهما بيانات وسلوك إضافي مختلف.
بدل أن تكرر نفس الكود في أكثر من class، يمكنك استخدام الوراثة Inheritance. الوراثة تسمح لك بإنشاء class جديد يرث الخصائص والدوال الأساسية من class موجود، ثم تضيف إليه ما يحتاجه أو تعدّل بعض سلوكه بطريقة منظمة.
في هذا الدرس ستتعلم معنى Parent Class وChild Class، وكيف تستخدم super()، وكيف تعدّل method موروثة، ومتى تكون الوراثة اختيارًا جيدًا ومتى لا تكون كذلك.
{alertInfo} الفكرة ببساطة: إذا كانStudentيعتبر نوعًا منPerson، فمن المنطقي أن يرث الطالب الاسم والبريد ودالة التعريف من class الشخص، ثم يضيف بياناته الخاصة مثل الدرجة أو التخصص.
{getToc} $title={محتوى المقال}
ما هي الوراثة Inheritance في Python؟
الوراثة هي آلية في البرمجة كائنية التوجه تسمح لـ class جديد بأن يستفيد من خصائص ودوال class موجود مسبقًا. نسمي class الأصلي عادة:
- Parent Class أو Base Class أو الكلاس الأب.
ونسمي class الذي يرث منه:
- Child Class أو Derived Class أو الكلاس الابن.
توضح وثائق Python أن الكلاس المشتق يستطيع أن يرث من كلاس أساسي، وأنه يمكنه إعادة تعريف methods موروثة أو استدعاء method موجودة في الكلاس الأب عند الحاجة.
لماذا نستخدم الوراثة؟
الفائدة الرئيسية من الوراثة هي إعادة استخدام الكود. بدل نسخ نفس الخصائص والدوال داخل كل class، تكتب الجزء المشترك مرة واحدة داخل الكلاس الأب.
| بدون وراثة | باستخدام الوراثة |
|---|---|
تكرر name وemail ودالة عرض البيانات في كل class. |
تضع البيانات المشتركة في Person وتورّثها إلى Student وTeacher. |
| تعديل الدالة المشتركة يحتاج تعديلها في أماكن متعددة. | تعدلها في class الأب، فتستفيد منها الكلاسات الأبناء. |
| يزداد احتمال وجود اختلافات وأخطاء بين الكلاسات المتشابهة. | الكود يصبح أوضح عندما توجد علاقة حقيقية من نوع “هو نوع من”. |
لكن لا تستخدم الوراثة فقط لأن كلاسَين فيهما بعض التشابه. اسأل: هل الكلاس الابن فعلًا نوع خاص من الكلاس الأب؟ الطالب هو شخص، والسيارة ليست شخصًا. إذا كانت العلاقة ليست “هو نوع من”، فقد يكون التركيب أو تمرير كائن داخل كائن خيارًا أوضح لاحقًا.
أول مثال: Person وStudent
سننشئ أولًا class باسم Person فيها الاسم والبريد الإلكتروني ودالة تعريف بسيطة:
class Person:
def __init__(self, name, email):
self.name = name
self.email = email
def introduce(self):
print(f"مرحبًا، أنا {self.name}.")
الآن ننشئ class باسم Student ترث من Person. نكتب اسم الأب داخل الأقواس بعد اسم class:
class Student(Person):
pass
حتى بدون كتابة أي دوال داخل Student، فإنها ترث ما هو موجود في Person. جرّب:
student = Student("علي", "ali@example.com")
print(student.name)
print(student.email)
student.introduce()
الناتج:
علي
ali@example.com
مرحبًا، أنا علي.
لم نكتب __init__ أو introduce() داخل Student، لكن الطالب استطاع استخدامها لأنها موروثة من Person.
إضافة خصائص جديدة إلى Child Class
الطالب لا يملك فقط الاسم والبريد؛ قد يملك تخصصًا ودرجة. لذلك نضيف خصائص خاصة به داخل class الابن.
هنا نحتاج إلى دالة __init__ داخل Student. لكن إذا كتبناها، يجب أن نهيئ بيانات الأب أيضًا. لهذا نستخدم super().
class Person:
def __init__(self, name, email):
self.name = name
self.email = email
def introduce(self):
print(f"مرحبًا، أنا {self.name}.")
class Student(Person):
def __init__(self, name, email, major, score):
super().__init__(name, email)
self.major = major
self.score = score
student = Student(
"سارة",
"sara@example.com",
"علوم الحاسوب",
92
)
print(student.name)
print(student.major)
print(student.score)
ما هي super() ولماذا نستخدمها؟
الدالة super() تمنحك طريقة للوصول إلى الكلاس الأب من داخل الكلاس الابن. في هذا المثال:
super().__init__(name, email)
تعني: “نفّذ دالة __init__ الموجودة في Person، وأرسل إليها الاسم والبريد الإلكتروني”. وبذلك لا تحتاج إلى كتابة السطرين التاليين مجددًا داخل class الابن:
self.name = name
self.email = email
بعد استدعاء الأب، تكمل class الابن بخصائصها الإضافية مثل major وscore.
{alertWarning} خطأ شائع: عندما تكتب__init__جديدة داخل class الابن، لا تُستدعى__init__الخاصة بالأب تلقائيًا. استخدمsuper().__init__(...)إذا كنت تحتاج تهيئة خصائص الكلاس الأب.
استخدام methods موروثة من Parent Class
الطالب ما زال قادرًا على استخدام introduce() الموروثة من Person:
student.introduce()
وسيطبع:
مرحبًا، أنا سارة.
ويمكننا إضافة method جديدة للطالب فقط، مثل التحقق من النجاح:
class Student(Person):
def __init__(self, name, email, major, score):
super().__init__(name, email)
self.major = major
self.score = score
def is_passed(self):
return self.score >= 60
هنا introduce() جاءت من الأب، بينما is_passed() تخص الطالب فقط.
تعديل Method موروثة: Method Overriding
أحيانًا تكون method الأب مناسبة كبداية، لكن الكلاس الابن يحتاج سلوكًا أكثر تحديدًا. يمكنك كتابة method بالاسم نفسه داخل class الابن؛ وهذا يسمى Method Overriding.
في المثال التالي، الكلاس الأب يعرّف الشخص بشكل عام، بينما الطالب يضيف التخصص إلى رسالة التعريف:
class Person:
def __init__(self, name, email):
self.name = name
self.email = email
def introduce(self):
print(f"أنا {self.name}.")
class Student(Person):
def __init__(self, name, email, major):
super().__init__(name, email)
self.major = major
def introduce(self):
print(
f"أنا {self.name}، طالب في تخصص {self.major}."
)
عند تشغيل:
student = Student(
"علي",
"ali@example.com",
"الذكاء الاصطناعي"
)
student.introduce()
سيستخدم Python دالة introduce() الموجودة في Student بدل الدالة الموجودة في Person.
كيف تضيف سلوك الابن إلى سلوك الأب؟
بدل نسخ رسالة الأب بالكامل، يمكنك استدعاء method الأب عبر super() ثم تضيف سطرًا جديدًا:
class Student(Person):
def __init__(self, name, email, major):
super().__init__(name, email)
self.major = major
def introduce(self):
super().introduce()
print(f"تخصصي هو: {self.major}")
بهذه الطريقة تستفيد من سلوك الأب وتضيف إليه سلوكًا خاصًا بالابن.
مثال عملي كامل: Person وStudent وTeacher
المثال التالي يوضح كيف يمكن لأكثر من class أن يرث من الأب نفسه:
class Person:
academy_name = "أكاديمية بايثون العرب"
def __init__(self, name, email):
self.name = name
self.email = email
def show_contact(self):
print(f"الاسم: {self.name}")
print(f"البريد: {self.email}")
class Student(Person):
def __init__(self, name, email, major, score):
super().__init__(name, email)
self.major = major
self.score = score
def show_role(self):
status = "ناجح" if self.score >= 60 else "يحتاج مراجعة"
print(f"طالب في: {self.major}")
print(f"الحالة: {status}")
class Teacher(Person):
def __init__(self, name, email, subject):
super().__init__(name, email)
self.subject = subject
def show_role(self):
print(f"مدرس مادة: {self.subject}")
student = Student(
"سارة",
"sara@example.com",
"علوم الحاسوب",
88
)
teacher = Teacher(
"خالد",
"khaled@example.com",
"Python"
)
print(Person.academy_name)
student.show_contact()
student.show_role()
print("-" * 30)
teacher.show_contact()
teacher.show_role()
في هذا المثال:
Personيحتوي على البيانات والسلوك المشترك.Studentيرث الاسم والبريد ويضيف التخصص والدرجة.Teacherيرث الاسم والبريد ويضيف المادة التي يدرسها.- الكل يستطيع الوصول إلى
academy_nameلأنها class attribute موروثة من الأب.
كيف تعرف هل كائن يرث من class معين؟
توفر Python دالة isinstance() للتحقق هل كائن معين ينتمي إلى class أو إلى class يرث منه.
print(isinstance(student, Student))
print(isinstance(student, Person))
الناتج سيكون:
True
True
السبب أن student هو كائن من Student، وStudent يرث من Person.
وتستطيع أيضًا التحقق من العلاقة بين classين باستخدام issubclass():
print(issubclass(Student, Person))
أخطاء شائعة عند استخدام الوراثة
1. نسيان كتابة Parent Class بين الأقواس
هذا الكود لا يجعل Student يرث من Person:
class Student:
pass
الصحيح:
class Student(Person):
pass
2. نسيان super().__init__ داخل Child Class
إذا أنشأت __init__ داخل الابن ثم استخدمت خصائص الأب لاحقًا، قد يظهر خطأ مثل:
AttributeError: 'Student' object has no attribute 'name'
لأنك لم تنفذ تهيئة الأب التي تنشئ self.name وself.email.
3. تكرار كود الأب بدل استدعائه
يمكنك كتابة خصائص الأب يدويًا داخل الابن، لكن هذا يجعل الصيانة أصعب. الأفضل غالبًا استخدام super().__init__() ما دام الابن يحتاج نفس التهيئة.
4. استخدام الوراثة لعلاقة غير منطقية
لا تجعل Car ترث من Person لمجرد أن كليهما يملك اسمًا. الوراثة تعبر عن علاقة “هو نوع من”، مثل Student هو نوع من Person.
5. تعديل method دون استدعاء الأب عند الحاجة
عندما تعيد تعريف method، تختفي نسخة الأب من الاستخدام المباشر على الكائن. إذا أردت الإبقاء على جزء من سلوك الأب، استدعها عبر super().method_name().
متى لا تحتاج إلى الوراثة؟
الوراثة مفيدة، لكنها ليست الحل لكل شيء. لا تستخدمها فقط لتقليل عدد الأسطر. إذا كانت العلاقة أقرب إلى “يحتوي على” بدل “هو نوع من”، فكر في وضع كائن داخل كائن بدل الوراثة.
مثلًا: الطالب ليس نوعًا من Course، لكنه يمكن أن يسجل في دورة. هنا يمكن أن تحتوي class الطالب على قائمة دورات أو كائنات دورات، ولا نحتاج أن يرث من Course.
تمرين عملي
أنشئ class أب باسم Vehicle يحتوي على:
- خصائص:
brandوyear. - دالة باسم
show_info()تعرض معلومات المركبة.
ثم أنشئ class ابن باسم Car يرث من Vehicle ويضيف:
- خاصية باسم
doors. - تعديل
show_info()حتى تعرض عدد الأبواب أيضًا.
حل مقترح
class Vehicle:
def __init__(self, brand, year):
self.brand = brand
self.year = year
def show_info(self):
print(f"الماركة: {self.brand}")
print(f"السنة: {self.year}")
class Car(Vehicle):
def __init__(self, brand, year, doors):
super().__init__(brand, year)
self.doors = doors
def show_info(self):
super().show_info()
print(f"عدد الأبواب: {self.doors}")
car = Car("Toyota", 2024, 4)
car.show_info()
روابط داخلية مفيدة من بايثون العرب
- أساسيات بايثون 34: شرح class وobject في Python للمبتدئين
- كورس أساسيات بايثون للمبتدئين
- شرح الدوال Functions في Python للمبتدئين
- أساسيات بايثون: شرح try وexcept في Python
الخلاصة
- الوراثة تسمح للكلاس الابن أن يرث خصائص ودوال الكلاس الأب.
- نكتب class الابن بهذا الشكل:
class Student(Person):. - استخدم
super().__init__(...)لتهيئة خصائص الأب من داخل الابن. - يمكن للابن إضافة خصائص ودوال خاصة به دون تعديل الكلاس الأب.
- يمكنك تعديل method موروثة عبر Method Overriding، مع استدعاء نسخة الأب عبر
super().method_name()عند الحاجة. - استخدم الوراثة عندما تكون العلاقة منطقية: الابن “هو نوع من” الأب.
{alertSuccess} خطوتك التالية: عدّل مثالPersonوأنشئ class جديدة باسمAdminترث منه، ثم أضف لها خاصيةpermissionsودالة تعرض صلاحيات المسؤول. هكذا ستتأكد أنك فهمت الفرق بين البيانات المشتركة والبيانات الخاصة بكل نوع.
مصادر خارجية رسمية للتوسع
- Inheritance — Python Tutorial
- super() — Python Built-in Functions
- isinstance() — Python Built-in Functions
أسئلة شائعة
ما هي الوراثة Inheritance في Python؟
هي طريقة لإنشاء class جديد يرث الخصائص والدوال من class موجود، ثم يضيف أو يغير ما يحتاجه. مثل Student الذي يرث من Person.
ما الفرق بين Parent Class وChild Class؟
Parent Class هو الكلاس الذي يحتوي على السلوك والبيانات الأساسية المشتركة. Child Class هو الكلاس الذي يرث منه ويضيف خصائص أو methods خاصة به.
لماذا نستخدم super في Python؟
نستخدم super() لاستدعاء method من الكلاس الأب، خصوصًا super().__init__() لتهيئة الخصائص المشتركة بدل تكرارها داخل الكلاس الابن.
هل يجب أن يرث كل class من class آخر؟
لا. استخدم الوراثة فقط عندما توجد علاقة منطقية من نوع “هو نوع من”. إذا كانت العلاقة “يحتوي على”، فكر في استخدام composition بدلًا من الوراثة.
هل يمكن للابن تعديل method موجودة في الأب؟
نعم. اكتب method بالاسم نفسه داخل child class. هذا يسمى Method Overriding، ويمكنك استدعاء نسخة الأب عبر super().method_name() إذا أردت الجمع بين السلوكين.
أساسيات بايثون 35: الفرق بين Class Attributes وInstance Attributes في Python للمبتدئين
في الدرس السابق تعلمت أن class هو قالب ننشئ منه كائنات objects، وأن كل كائن يستطيع امتلاك بياناته الخاصة باستخدام self. لكن عندما تبدأ بإنشاء أكثر من كائن، يظهر سؤال مهم: هل كل قيمة داخل class يجب أن تكون خاصة بكل كائن؟
الإجابة لا. بعض البيانات تخص كل كائن وحده، مثل اسم الطالب ودرجته. وبعض البيانات تكون مشتركة بين جميع الكائنات، مثل اسم المدرسة أو الحد الأدنى للنجاح أو نسبة الضريبة. هنا نحتاج إلى فهم الفرق بين Instance Attributes وClass Attributes.
هذا الدرس يكمل درس أساسيات بايثون 34: شرح class وobject في Python، وسيجعلك تكتب classes أكثر تنظيمًا وتتجنب أخطاء شائعة تظهر عند تغيير القيم المشتركة أو استخدام القوائم داخل class.
{alertInfo} الفكرة ببساطة:Instance Attributeهي قيمة تخص كائنًا واحدًا وتُنشأ غالبًا باستخدامself، مثلself.name. أماClass Attributeفهي قيمة مشتركة بين كل الكائنات وتُكتب مباشرة داخلclass، مثلschool_name.
{getToc} $title={محتوى المقال}
ما هي Attributes في Python؟
كلمة attribute تعني خاصية أو معلومة مرتبطة بكائن أو class. عندما تكتب student.name فإن name هي خاصية للكائن student. وقد تكون هذه الخاصية مخزنة داخل الكائن نفسه، أو موجودة على مستوى class ويستطيع الكائن الوصول إليها.
في هذا الدرس سنقسم الخصائص إلى نوعين:
- Instance Attributes: بيانات فريدة تخص كل كائن.
- Class Attributes: بيانات مشتركة بين كل الكائنات التابعة للكلاس نفسه.
مثال سريع: بيانات طالب
تخيل أننا نبني class تمثل طالبًا. الاسم والدرجة يختلفان من طالب إلى آخر، لكن اسم المدرسة واحد لجميع الطلاب.
class Student:
school_name = "مدرسة بايثون العرب"
def __init__(self, name, score):
self.name = name
self.score = score
student1 = Student("علي", 90)
student2 = Student("سارة", 95)
print(student1.name)
print(student2.name)
print(student1.school_name)
print(student2.school_name)
في هذا المثال:
nameوscoreخصائص خاصة بكل طالب.school_nameخاصية مشتركة بين كل الطلاب.
ما هي Instance Attributes؟
خصائص الكائن أو Instance Attributes هي البيانات التي تخص نسخة واحدة فقط من class. عادة ننشئها داخل __init__ باستخدام self.
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
employee1 = Employee("أحمد", 800)
employee2 = Employee("مريم", 950)
employee1.salary = 1000
print(employee1.salary)
print(employee2.salary)
سيظهر:
1000
950
تغيير راتب employee1 لم يغير راتب employee2، لأن الراتب قيمة تخص كل كائن منفصل.
ما هي Class Attributes؟
خصائص class أو Class Attributes تكتب مباشرة داخل class وخارج الدوال. تكون متاحة لجميع الكائنات التي تنشئها من class نفسه.
مثال: كل منتجات متجر معين تستخدم نسبة ضريبة واحدة:
class Product:
tax_rate = 0.15
def __init__(self, name, price):
self.name = name
self.price = price
def price_after_tax(self):
return self.price * (1 + self.tax_rate)
book = Product("كتاب Python", 20)
course = Product("دورة Python", 50)
print(book.price_after_tax())
print(course.price_after_tax())
لا نكتب self.tax_rate = 0.15 هنا لأننا لا نحتاج نسخة مستقلة من الضريبة لكل منتج. نحتاج قيمة واحدة مشتركة بين كل المنتجات.
كيف يصل الكائن إلى Class Attribute؟
يمكنك الوصول إلى class attribute بطريقتين:
print(Product.tax_rate)
print(book.tax_rate)
كلتا الطريقتين تعملان، لكن الأفضل عند القراءة والتعديل هو استخدام ClassName.attribute إذا كنت تريد توضيح أن القيمة تخص class كله. أما داخل methods فيمكنك الوصول إليها عبر self.attribute.
عندما لا تجد Python الخاصية داخل الكائن نفسه، تبحث عنها في class. لهذا يستطيع book.tax_rate الوصول إلى Product.tax_rate.
ماذا يحدث عند تعديل Class Attribute؟
إذا عدلت الخاصية من خلال class نفسه، ستتغير القيمة التي يصل إليها كل كائن لا يملك نسخة خاصة من الاسم نفسه.
class Student:
school_name = "مدرسة بايثون العرب"
def __init__(self, name):
self.name = name
student1 = Student("علي")
student2 = Student("سارة")
Student.school_name = "أكاديمية بايثون العرب"
print(student1.school_name)
print(student2.school_name)
الناتج:
أكاديمية بايثون العرب
أكاديمية بايثون العرب
هذا مفيد للقيم العامة مثل اسم المؤسسة، نسبة ضريبة، حد أدنى للنجاح، أو إعداد عام داخل المشروع.
فرق مهم: التعديل من class والتعديل من object
هذه النقطة تسبب ارتباكًا عند المبتدئين. انظر إلى المثال التالي:
class Student:
school_name = "مدرسة بايثون العرب"
def __init__(self, name):
self.name = name
student1 = Student("علي")
student2 = Student("سارة")
student1.school_name = "مدرسة خاصة بعلي"
print(student1.school_name)
print(student2.school_name)
print(Student.school_name)
الناتج:
مدرسة خاصة بعلي
مدرسة بايثون العرب
مدرسة بايثون العرب
لم تتغير الخاصية المشتركة الأصلية. عند كتابة student1.school_name = ... أنشأت Python instance attribute جديدة داخل student1 بالاسم نفسه، فأصبحت تخفي class attribute عند استخدام هذا الكائن.
{alertWarning} معلومة مهمة: عند كتابةobject.attribute = valueباسم يطابق class attribute، فأنت غالبًا تنشئ قيمة خاصة لهذا الكائن. لتغيير القيمة للجميع، استخدمClassName.attribute = value.
فحص خصائص الكائن وclass باستخدام __dict__
للتعلم فقط، تستطيع رؤية أين خزنت Python الخصائص باستخدام __dict__:
class Student:
school_name = "مدرسة بايثون العرب"
def __init__(self, name):
self.name = name
student = Student("علي")
print(Student.__dict__)
print(student.__dict__)
ستجد أن Student.__dict__ يحتوي على school_name والدوال، بينما student.__dict__ يحتوي على name لأنه خاص بهذا الكائن.
خطأ شائع جدًا: وضع List قابلة للتعديل كـ Class Attribute
القيم القابلة للتعديل مثل list وdict تحتاج انتباهًا. عندما تضع قائمة في class attribute، تصبح القائمة نفسها مشتركة بين كل الكائنات.
class Student:
courses = []
def __init__(self, name):
self.name = name
student1 = Student("علي")
student2 = Student("سارة")
student1.courses.append("Python")
print(student1.courses)
print(student2.courses)
قد تتفاجأ أن الناتج يظهر المادة عند الطالبين، لأنهما يشتركان في القائمة نفسها:
['Python']
['Python']
الحل الصحيح هو إنشاء قائمة مستقلة لكل طالب داخل __init__:
class Student:
def __init__(self, name):
self.name = name
self.courses = []
student1 = Student("علي")
student2 = Student("سارة")
student1.courses.append("Python")
print(student1.courses)
print(student2.courses)
['Python']
[]
مثال عملي كامل: نظام طلاب بسيط
المثال التالي يجمع المفهومين معًا. اسم الأكاديمية والحد الأدنى للنجاح مشتركان، بينما الاسم والدرجة والدروس المسجلة تخص كل طالب وحده.
class Student:
academy_name = "أكاديمية بايثون العرب"
passing_score = 60
def __init__(self, name, score):
self.name = name
self.score = score
self.lessons = []
def add_lesson(self, lesson_name):
self.lessons.append(lesson_name)
def is_passed(self):
return self.score >= self.passing_score
def show_info(self):
status = "ناجح" if self.is_passed() else "يحتاج مراجعة"
print(f"الأكاديمية: {self.academy_name}")
print(f"الطالب: {self.name}")
print(f"الدرجة: {self.score}")
print(f"الحالة: {status}")
print(f"الدروس: {', '.join(self.lessons)}")
student1 = Student("علي", 85)
student2 = Student("سارة", 55)
student1.add_lesson("أساسيات بايثون")
student1.add_lesson("القوائم")
student2.add_lesson("الشروط")
student1.show_info()
print("-" * 30)
student2.show_info()
في هذا المشروع الصغير:
academy_nameوpassing_scoreهما class attributes.nameوscoreوlessonsهي instance attributes.- كل طالب يملك قائمة دروس مستقلة لأنها أنشئت عبر
self.lessons = [].
متى تستخدم Class Attribute ومتى تستخدم Instance Attribute؟
| استخدم Class Attribute عندما... | استخدم Instance Attribute عندما... |
|---|---|
| القيمة يجب أن تكون مشتركة بين كل الكائنات. | القيمة تختلف من كائن إلى آخر. |
| القيمة تمثل إعدادًا عامًا مثل نسبة ضريبة أو اسم مؤسسة. | القيمة تمثل حالة أو بيانات خاصة مثل اسم أو سعر أو درجة. |
| تريد تغيير القيمة للجميع من مكان واحد. | تريد تعديل القيمة لكائن واحد دون التأثير على الآخرين. |
| القيمة ثابتة أو غير قابلة للتعديل غالبًا. | القيمة قائمة أو قاموس أو بيانات قابلة للتعديل لكل كائن. |
أخطاء شائعة يجب تجنبها
1. وضع بيانات خاصة في Class Attribute
لا تضع اسم المستخدم أو قائمة مهامه أو رصيده مباشرة داخل class إذا كان كل شخص يحتاج بياناته الخاصة.
2. تعديل Class Attribute من object دون قصد
تذكر أن student.school_name = "..." ينشئ غالبًا نسخة خاصة بهذا الطالب. استخدم Student.school_name = "..." إذا كنت تريد التغيير للجميع.
3. استخدام قائمة أو قاموس مشترك لكل الكائنات
إذا كانت القائمة يجب أن تكون مستقلة لكل كائن، أنشئها داخل __init__ باستخدام self.
4. استخدام Class Attribute لكل شيء
Class attributes مفيدة، لكنها ليست بديلًا عن instance attributes. اسأل دائمًا: هل هذه القيمة تخص كل الكائنات أم هذا الكائن فقط؟
تمرين عملي
أنشئ class باسم Book تحقق الشروط التالية:
- لديها class attribute باسم
library_nameوقيمتها"مكتبة بايثون العرب". - لديها instance attributes باسم
titleوauthor. - أنشئ كتابين مختلفين واطبع بياناتهما.
- غيّر
library_nameمن خلال class ثم اطبعها لكلا الكتابين.
حل مقترح
class Book:
library_name = "مكتبة بايثون العرب"
def __init__(self, title, author):
self.title = title
self.author = author
def show_info(self):
print(f"المكتبة: {self.library_name}")
print(f"العنوان: {self.title}")
print(f"المؤلف: {self.author}")
book1 = Book("تعلم Python", "إيليا علي")
book2 = Book("مشاريع Python", "فريق بايثون العرب")
Book.library_name = "مكتبة بايثون العرب الرقمية"
book1.show_info()
print("-" * 25)
book2.show_info()
روابط داخلية مفيدة من بايثون العرب
- أساسيات بايثون 34: شرح class وobject في Python للمبتدئين
- شرح القوائم List في Python للمبتدئين
- كورس أساسيات بايثون للمبتدئين
الخلاصة
Instance Attributesتخص كائنًا واحدًا، وتُنشأ غالبًا باستخدامselfداخل__init__.Class Attributesتكتب داخل class وخارج methods، وتكون مشتركة بين كل الكائنات.- غيّر class attribute عبر
ClassName.attributeعندما تريد تطبيق التغيير على الجميع. - عند التعديل عبر
object.attributeقد تنشئ قيمة خاصة بالكائن وتخفي القيمة المشتركة. - لا تستخدم
listأوdictكـ class attribute عندما تريد بيانات مستقلة لكل object.
{alertSuccess} قاعدة ذهبية: اسأل قبل إنشاء أي attribute: هل هذه القيمة مشتركة بين جميع الكائنات؟ ضعها داخلclass. هل تختلف من كائن إلى آخر؟ أنشئها باستخدامselfداخل__init__.
مصادر خارجية رسمية للتوسع
- Classes — Python Tutorial
- Class definitions — Python Language Reference
- Class variable — Python Glossary
أسئلة شائعة
ما الفرق بين class attribute وinstance attribute في Python؟
Class attribute قيمة مشتركة بين جميع الكائنات التابعة لنفس class. أما instance attribute فهي قيمة خاصة بكل كائن، مثل اسم الطالب أو درجته.
هل يمكن الوصول إلى class attribute من خلال object؟
نعم، مثل student.school_name. لكن من الأفضل استخدام Student.school_name عندما تريد توضيح أن الخاصية مشتركة وتابعة للكلاس كله.
لماذا لم تتغير القيمة عند تعديلها من object واحد؟
لأن كتابة student.school_name = "..." قد تنشئ instance attribute جديدة لهذا الكائن بدل تعديل class attribute الأصلية.
هل أضع list داخل class أم داخل init؟
إذا كانت كل نسخة تحتاج قائمة مستقلة، ضعها داخل __init__ كالتالي: self.items = []. وضعها مباشرة داخل class يجعل القائمة مشتركة بين جميع الكائنات.
هل class attributes مناسبة للقيم الثابتة فقط؟
هي مناسبة للقيم المشتركة عمومًا، سواء كانت ثابتة أو يمكن تغييرها للجميع مثل نسبة ضريبة أو عدد الكائنات. المهم ألا تكون بيانات خاصة بكائن واحد.
كيف تشغل ملف Python في CMD وTerminal وVS Code؟ دليل للمبتدئين خطوة بخطوة
بعد كتابة أول كود في Python وحفظه داخل ملف ينتهي بالامتداد .py، يظهر سؤال مهم عند أغلب المبتدئين: كيف أشغّل هذا الملف؟ هل أفتحه بالنقر المزدوج؟ هل أكتب أمرًا في CMD؟ هل أستخدم Terminal؟ أم أضغط زر التشغيل في VS Code؟
الإجابة تختلف قليلًا حسب نظام التشغيل والأداة التي تستخدمها، لكن الفكرة واحدة: افتح الطرفية داخل المجلد الصحيح، ثم اطلب من مفسر Python تشغيل الملف. في هذا الدرس ستتعلم الطريقة بوضوح على Windows وmacOS وLinux، بالإضافة إلى تشغيل ملفات Python داخل VS Code وحل أشهر الأخطاء التي قد تمنع الكود من العمل.
{alertInfo} الفكرة ببساطة: لتشغيل ملف Python تحتاج إلى ثلاثة أشياء: Python مثبت على جهازك، وملف محفوظ بامتداد.py، وطرفية مفتوحة داخل المجلد الذي يحتوي على الملف. بعدها تستخدم أمرًا مثلpython hello.pyأوpy hello.pyأوpython3 hello.pyحسب نظامك.
{getToc} $title={محتوى المقال}
قبل تشغيل ملف Python: ماذا تحتاج؟
لا تحتاج إلى برامج معقدة لتشغيل أول ملف Python، لكن تأكد من هذه النقاط:
- تم تثبيت Python على جهازك.
- حفظت الكود داخل ملف مثل
hello.pyوليسhello.py.txt. - تعرف مكان المجلد الذي يحتوي على الملف.
- فتحت CMD أو Terminal أو الطرفية المدمجة في VS Code.
للتأكد من وجود Python، افتح الطرفية واكتب أحد الأوامر التالية.
في Windows
python --version
وإذا لم يعمل الأمر، جرّب:
py --version
في macOS أو Linux
python3 --version
إذا ظهرت نسخة مثل Python 3.13.x أو أي إصدار Python 3 مثبت لديك، فأنت جاهز للانتقال إلى تشغيل الملف. أمّا إذا ظهرت رسالة تفيد بأن الأمر غير معروف، فانتقل إلى قسم حل المشكلة لاحقًا في هذا الدرس.
أنشئ أول ملف Python للتجربة
أنشئ مجلدًا على سطح المكتب باسم مثل python-demo. داخله أنشئ ملفًا باسم hello.py واكتب هذا الكود:
name = input("ما اسمك؟ ")
print(f"مرحبًا {name}، أهلاً بك في Python.")
احفظ الملف. تأكد أن اسمه الحقيقي هو:
hello.py
وليس:
hello.py.txt
{alertWarning} تنبيه: في Windows قد تكون امتدادات الملفات مخفية. إذا رأيت اسم الملفhello.pyداخل المحرر لكنه لا يعمل، افتح خصائصه أو فعّل عرض امتدادات الملفات من مستكشف الملفات للتأكد أنه ينتهي فعلًا بـ.py.
كيف تشغل ملف Python في CMD على Windows؟
CMD اختصار لـ Command Prompt، وهو نافذة أوامر موجودة في Windows. لتشغيل ملف Python، لا يكفي أن تكتب اسم الملف فقط؛ يجب أن تكون داخل المجلد الذي يحتوي عليه أو تكتب مساره الصحيح.
الخطوة 1: افتح CMD
اضغط زر Windows في لوحة المفاتيح، ثم اكتب cmd وافتح تطبيق Command Prompt.
الخطوة 2: انتقل إلى مجلد الملف باستخدام cd
إذا كان الملف داخل مجلد على سطح المكتب اسمه python-demo، اكتب أمرًا مشابهًا لهذا:
cd Desktop\python-demo
قد يختلف اسم سطح المكتب أو المسار حسب حسابك ولغة نظامك. يمكنك كتابة المسار كاملًا أيضًا:
cd /d "C:\Users\YourName\Desktop\python-demo"
ضع المسار بين علامات اقتباس إذا كان يحتوي على مسافات، مثل مجلد اسمه Python Projects.
للتأكد من وجود الملف داخل المجلد، اكتب:
dir
يجب أن ترى hello.py ضمن قائمة الملفات.
الخطوة 3: شغّل الملف
اكتب:
python hello.py
وفي بعض أجهزة Windows يكون Python Launcher متوفرًا، لذلك يمكنك استخدام:
py hello.py
بعدها سيظهر لك الطلب:
ما اسمك؟
اكتب اسمك واضغط Enter، ثم سيظهر ناتج البرنامج.
كيف تشغل ملف Python في Terminal على macOS وLinux؟
في macOS وLinux تستخدم Terminal بدل CMD. الخطوات قريبة جدًا، لكن غالبًا ستستخدم الأمر python3 بدل python.
الخطوة 1: افتح Terminal
- في macOS: افتح Terminal من التطبيقات أو باستخدام Spotlight.
- في Linux: افتح Terminal من قائمة التطبيقات، أو استخدم الاختصار المناسب لتوزيعتك.
الخطوة 2: انتقل إلى مجلد الملف
إذا كان الملف على سطح المكتب داخل مجلد python-demo، اكتب:
cd ~/Desktop/python-demo
ولعرض الملفات داخل المجلد اكتب:
ls
الخطوة 3: شغّل الملف
python3 hello.py
سبب استخدام python3 في كثير من بيئات macOS وLinux هو التمييز بين Python 3 وأوامر Python القديمة في بعض الأنظمة. استخدم الأمر الذي يعرض لك نسخة Python 3 عند فحص الإصدار.
كيف تشغل ملف Python في VS Code؟
VS Code يجعل تشغيل الملف أسهل، لكنه يعتمد على اختيار مفسر Python الصحيح وتثبيت إضافة Python الرسمية. بعد فتح مجلد المشروع داخل VS Code، اتبع الخطوات التالية:
- افتح المجلد الذي يحتوي على ملفك عبر
File → Open Folder. - افتح الملف
hello.py. - تأكد من تحديد مفسر Python من شريط الحالة في الأسفل، أو عبر الأمر
Python: Select Interpreter. - اضغط زر التشغيل ▶ في أعلى يمين المحرر، والذي يحمل عادة اسم Run Python File in Terminal.
سيقوم VS Code بفتح Terminal مدمجة، ثم يشغل الملف باستخدام المفسر المحدد. على Windows يكون الأمر غالبًا شبيهًا بـ python hello.py، وعلى macOS وLinux يكون قريبًا من python3 hello.py.
يمكنك أيضًا النقر بالزر الأيمن داخل الملف ثم اختيار:
Run Python > Run Python File in Terminal
لا تحتاج إلى تثبيت إضافة Code Runner لتشغيل ملفات Python الأساسية داخل VS Code؛ إضافة Python الرسمية توفر طريقة التشغيل في الطرفية.
كيف تشغل الملف من Terminal داخل VS Code يدويًا؟
أحيانًا تريد التحكم بالأمر أو تمرير قيم إضافية إلى البرنامج. افتح الطرفية المدمجة من القائمة:
Terminal > New Terminal
أو باستخدام الاختصار المناسب على جهازك. ثم تأكد من أن الطرفية موجودة داخل مجلد المشروع، واكتب:
python hello.py
وفي macOS وLinux:
python3 hello.py
هذه الطريقة مفيدة عندما تبني لاحقًا برنامج سطر أوامر CLI وتحتاج إلى تمرير خيارات، مثل:
python main.py add "مراجعة درس بايثون"
اقرأ أيضًا درس بناء برنامج سطر أوامر CLI بسيط في Python لفهم كيفية استقبال الأوامر والخيارات داخل مشاريعك.
حل مشكلة python is not recognized في Windows
قد تكتب:
python hello.py
ثم تظهر رسالة قريبة من:
'python' is not recognized as an internal or external command,
operable program or batch file.
المعنى ببساطة: Windows لا يجد مفسر Python عند كتابة الأمر python. لا يعني ذلك بالضرورة أن كودك فيه خطأ.
الحل الأول: جرّب py بدل python
py --version
py hello.py
في كثير من أجهزة Windows يكون Python Launcher متوفرًا حتى عندما لا يعمل أمر python.
الحل الثاني: تأكد من تثبيت Python
افتح إعدادات التطبيقات في Windows وابحث عن Python، أو أعد تشغيل مثبت Python. أثناء التثبيت، تأكد من تفعيل الخيار الذي يضيف Python إلى PATH إذا ظهر لك.
الحل الثالث: أعد فتح CMD أو VS Code
إذا ثبّت Python أو عدّلت PATH وأبقيت نافذة CMD القديمة مفتوحة، قد لا تلتقط التغيير. أغلق CMD وVS Code، ثم افتحهما من جديد وجرب أمر الإصدار مرة أخرى.
الحل الرابع: اعرف مكان Python المتاح
في CMD يمكنك محاولة معرفة مكان الأوامر المتاحة:
where python
where py
إذا ظهر مسار صالح لأحدهما، استخدم الأمر المتوفر. وإذا لم يظهر أي مسار، فغالبًا تحتاج إلى تثبيت Python أو إصلاح PATH.
خطأ: can't open file أو No such file or directory
إذا ظهرت رسالة مثل:
python: can't open file 'hello.py': [Errno 2] No such file or directory
فالمشكلة غالبًا ليست في Python نفسها، بل في مكان الطرفية أو اسم الملف. راجع هذه النقاط:
- هل كتبت
cdوانتقلت إلى المجلد الصحيح؟ - هل يظهر الملف عند كتابة
dirفي Windows أوlsفي macOS/Linux؟ - هل اسم الملف بالضبط
hello.py؟ - هل الملف محفوظ بامتداد
.pyوليس.txt؟
يمكنك تشغيله عبر المسار الكامل أيضًا، مثل:
python "C:\Users\YourName\Desktop\python-demo\hello.py"
خطأ: ModuleNotFoundError عند تشغيل الملف
إذا كان البرنامج يبدأ ثم يتوقف برسالة مثل:
ModuleNotFoundError: No module named 'requests'
فهذا يعني أن الملف يحاول استيراد مكتبة غير مثبتة في البيئة التي تشغله بها. الحل غالبًا يكون:
python -m pip install requests
وفي macOS أو Linux:
python3 -m pip install requests
استخدام python -m pip يساعد على ربط pip بمفسر Python الذي تستخدمه في الأمر نفسه. راجع دليل شرح pip في Python وتثبيت المكتبات لتتعلم إنشاء بيئة افتراضية وتثبيت الحزم بطريقة منظمة.
فرق مهم: تشغيل الملف بالنقر المزدوج أم من الطرفية؟
| الطريقة | متى تكون مناسبة؟ | الملاحظة |
|---|---|---|
النقر المزدوج على ملف .py |
تجربة سريعة في Windows إذا كان ربط Python بالملف صحيحًا. | قد تفتح النافذة وتغلق بسرعة، لذلك لا تكون الأفضل للتعلم والتشخيص. |
| CMD أو Terminal | أفضل طريقة لفهم التشغيل ورؤية الأخطاء وتمرير الأوامر. | تحتاج إلى معرفة المجلد الصحيح وأمر التشغيل. |
| VS Code | مناسبة للكتابة والتشغيل داخل بيئة واحدة. | تأكد من اختيار Python Interpreter الصحيح. |
مثال عملي: تشغيل برنامج آلة حاسبة صغير
أنشئ ملفًا جديدًا باسم calculator.py واكتب:
first_number = float(input("اكتب الرقم الأول: "))
second_number = float(input("اكتب الرقم الثاني: "))
result = first_number + second_number
print(f"الناتج: {result}")
ثم شغّله من الطرفية وأنت داخل المجلد نفسه:
Windows
python calculator.py
macOS / Linux
python3 calculator.py
أو شغله من زر Run Python File in Terminal داخل VS Code.
أخطاء شائعة عند تشغيل ملفات Python
- تشغيل الأمر في المجلد الخطأ: استخدم
dirأوlsقبل التشغيل للتأكد أن الملف موجود. - اسم ملف غير صحيح: تأكد من كتابة الاسم والامتداد كما هو، مثل
hello.py. - استخدام python بدل py أو python3: افحص إصدار Python أولًا وحدد الأمر المتاح على جهازك.
- حفظ الملف بامتداد txt: فعّل عرض الامتدادات في Windows.
- اختيار مفسر خاطئ في VS Code: اختر Interpreter الصحيح، خصوصًا إذا كنت تستخدم بيئة افتراضية.
- تثبيت مكتبة في Python مختلف: استخدم
python -m pip install package_nameبدل أمرpipوحده عندما تكون غير متأكد.
{alertSuccess} قاعدة عملية: عند ظهور خطأ تشغيل، لا تبدأ بتعديل الكود مباشرة. اقرأ أول سطر مهم من الخطأ، ثم افحص بالترتيب: هل Python موجود؟ هل أنت داخل المجلد الصحيح؟ هل اسم الملف صحيح؟ وهل المفسر المختار هو نفسه الذي ثبتت فيه المكتبات؟
روابط داخلية مفيدة من بايثون العرب
- تجهيز بيئة Python خطوة بخطوة للمبتدئين
- شرح pip في Python: تثبيت مكتبات بايثون وإدارة الحزم
- بايثون بعد الأساسيات 4: بناء برنامج سطر أوامر CLI بسيط
- كورس أساسيات بايثون للمبتدئين
الخلاصة
- لتشغيل ملف Python، احفظه بامتداد
.pyثم افتح الطرفية داخل المجلد الذي يحتويه. - في Windows استخدم غالبًا
python file.pyأوpy file.py. - في macOS وLinux استخدم غالبًا
python3 file.py. - داخل VS Code اختر Python Interpreter الصحيح ثم اضغط Run Python File in Terminal.
- إذا ظهر
python is not recognized، جرّبpy، وتأكد من تثبيت Python وPATH ثم أعد فتح الطرفية. - إذا لم يجد Python الملف، افحص المجلد واسم الملف والامتداد قبل تعديل الكود.
مصادر خارجية رسمية للتوسع
- Command line and environment — Python Documentation
- Running Python code in Visual Studio Code
- Getting Started with Python in VS Code
- Python on Windows for beginners — Microsoft Learn
أسئلة شائعة
كيف أشغل ملف Python في CMD؟
افتح CMD، وانتقل إلى مجلد الملف عبر cd، ثم اكتب python filename.py. إذا لم يعمل الأمر، جرّب py filename.py.
ما الفرق بين python وpython3 وpy؟
في Windows قد يتوفر python أو py. في كثير من بيئات macOS وLinux يستخدم الأمر python3. استخدم الأمر الذي يعرض لك نسخة Python 3 عند تنفيذ أمر الإصدار.
لماذا لا يظهر زر التشغيل في VS Code؟
تأكد من تثبيت إضافة Python الرسمية داخل VS Code، ومن فتح ملف بامتداد .py، ثم اختر Python Interpreter من قائمة الأوامر.
هل يجب أن أستخدم VS Code لتشغيل Python؟
لا. يمكنك تشغيل ملفات Python من CMD أو Terminal أو أي محرر يوفر طرفية. VS Code مجرد بيئة مريحة تجمع كتابة الكود وتشغيله في مكان واحد.
لماذا يغلق البرنامج بسرعة عند النقر على ملف py؟
لأن Windows قد ينفذ الملف ثم يغلق النافذة فور انتهاء البرنامج. شغله من CMD أو Terminal أو VS Code حتى تبقى المخرجات والأخطاء ظاهرة أمامك.
July 02, 2026
بايثون العرب
مشكلة وحل 19: حل خطأ AttributeError: 'str' object has no attribute 'append' في Python
قد تكتب برنامجًا بسيطًا في Python لجمع أسماء أو كلمات، ثم تحاول إضافة قيمة جديدة باستخدام append()، فتظهر لك رسالة تشبه الآتي:
AttributeError: 'str' object has no attribute 'append'
هذا الخطأ شائع جدًا عند المبتدئين لأنه لا يعني أن append() نفسها لا تعمل، بل يعني أنك حاولت استخدامها مع نص String بينما هي دالة خاصة بالقوائم List. في هذا الدرس ستفهم سبب المشكلة، وكيف تعرف نوع المتغير قبل استخدامه، ومتى تستخدم append() ومتى تستخدم + أو join().
{alertInfo} الفكرة ببساطة: الدالةappend()تضيف عنصرًا إلى نهايةList، لكنها لا تعمل معString. إذا كانت القيمة نصًا، استخدم دمج النصوص أو حوّل طريقة عملك إلى قائمة ثم استخدمjoin()عند الحاجة.
{getToc} $title={محتوى المقال}
شكل الخطأ في Python
قد تختلف الرسالة حسب اسم المتغير، لكن الجزء المهم منها يكون عادة:
AttributeError: 'str' object has no attribute 'append'
لنقسمها إلى أجزاء بسيطة:
| جزء الرسالة | معناه |
|---|---|
AttributeError |
استخدمت خاصية أو دالة غير موجودة لهذا النوع من البيانات. |
'str' |
القيمة الحالية للمتغير نص من نوع String. |
object has no attribute 'append' |
النص لا يملك دالة باسم append(). |
إذن الحل لا يبدأ من حذف append() عشوائيًا؛ بل من الإجابة عن سؤال واحد: هل المتغير الذي أستخدم معه append هو List فعلًا أم String؟
مثال بسيط يسبب الخطأ
انظر إلى هذا الكود:
name = "Ali"
name.append(" Ahmed")
print(name)
سيظهر الخطأ لأن name يحتوي على نص واحد، والنص لا يملك دالة append().
إذا كان المطلوب هو دمج نصين، استخدم + أو f-string:
name = "Ali"
name = name + " Ahmed"
print(name)
Ali Ahmed
لماذا تعمل append مع List ولا تعمل مع String؟
القائمة List مصممة لتخزين عدة عناصر، ويمكن تعديلها بإضافة عناصر أو حذفها أو تغيير ترتيبها. لذلك تحتوي على دوال مثل append() وextend() وremove().
أما النص String فهو قيمة نصية واحدة. عندما تريد تغييره، تنشئ Python نصًا جديدًا بدل تعديل النص نفسه في مكانه. لهذا لا تجد داخله append().
| النوع | مثال | هل append تعمل؟ | الاستخدام المناسب |
|---|---|---|---|
str |
"Python" |
لا | دمج النصوص باستخدام + أو f-string أو join(). |
list |
["Python", "Java"] |
نعم | إضافة عنصر جديد إلى نهاية القائمة. |
توثق Python أن list.append(value) تضيف قيمة إلى نهاية القائمة، وهذا يوضح أن append() مرتبطة بالقوائم وليست دالة نصية.
الحل الأول: استخدم List عندما تريد جمع عناصر متعددة
إذا كان هدفك هو تخزين أكثر من اسم أو أكثر من كلمة، أنشئ قائمة من البداية بدل نص واحد:
names = []
names.append("Ali")
names.append("Sara")
names.append("Omar")
print(names)
['Ali', 'Sara', 'Omar']
هنا تعمل append() لأن names قائمة فعلًا، وليست نصًا.
الحل الثاني: لا تخزن نتيجة input داخل متغير تتوقع أنه List
من أكثر الأسباب العملية للخطأ أن input() ترجع نصًا دائمًا. حتى لو كتب المستخدم كلمة تبدو كعنصر من قائمة، ستظل النتيجة من نوع str.
مثال خاطئ:
items = input("اكتب عنصرًا: ")
items.append("Python")
إذا كتب المستخدم Book، تصبح قيمة items هي النص "Book"، ولذلك يظهر الخطأ عند السطر الثاني.
الحل أن تحتفظ بقائمة منفصلة، ثم تضيف إليها الإدخال:
items = []
new_item = input("اكتب عنصرًا: ").strip()
if new_item:
items.append(new_item)
print(items)
في هذا المثال:
itemsقائمة تستقبل العناصر.new_itemنص يمثل الإدخال الجديد.- نستخدم
strip()لإزالة المسافات الزائدة حول الإدخال. - نضيف النص إلى القائمة باستخدام
append().
الحل الثالث: استخدم split لتحويل النص إلى قائمة
أحيانًا يكتب المستخدم عناصر متعددة في سطر واحد، مثل:
Python,Java,JavaScript
هذه القيمة ما زالت نصًا واحدًا. إذا أردت التعامل معها كقائمة عناصر، استخدم split():
text = "Python,Java,JavaScript"
languages = text.split(",")
languages.append("C++")
print(languages)
['Python', 'Java', 'JavaScript', 'C++']
هنا أعادت split(",") قائمة، ولذلك أصبح استخدام append() صحيحًا.
الحل الرابع: ابنِ النص عبر قائمة ثم استخدم join
إذا كنت تجمع أجزاء كثيرة من النص داخل حلقة، قد يكون من الأنظف أن تضع الأجزاء في قائمة ثم تجمعها في النهاية باستخدام join().
مثال خاطئ:
message = ""
for word in ["تعلم", "Python", "بالعربي"]:
message.append(word)
المتغير message هنا نص فارغ، لذلك ستظهر رسالة AttributeError.
الحل الأفضل:
words = []
for word in ["تعلم", "Python", "بالعربي"]:
words.append(word)
message = " ".join(words)
print(message)
تعلم Python بالعربي
لاحظ الفرق:
wordsقائمة، لذلك يمكن إضافة العناصر إليها.join()تجمع عناصر القائمة في نص واحد في النهاية.
سبب شائع آخر: تغيّر نوع المتغير دون أن تنتبه
قد تبدأ المتغير على أنه قائمة، ثم تعطيه قيمة نصية لاحقًا. عندها تصبح append() غير صالحة:
tasks = []
tasks.append("قراءة الدرس")
tasks = "إكمال التمرين"
tasks.append("مراجعة الكود")
في البداية tasks قائمة، لكن في السطر الثالث تحولت إلى نص. لهذا سيظهر الخطأ في السطر الأخير.
استخدم أسماء واضحة ومختلفة عندما تمثل القيم أشياء مختلفة:
tasks = ["قراءة الدرس"]
current_task = "إكمال التمرين"
tasks.append(current_task)
print(tasks)
كيف تشخص المشكلة بسرعة؟
قبل السطر الذي يظهر فيه الخطأ، اطبع القيمة ونوعها:
print(items)
print(type(items))
إذا ظهر لك:
Book
<class 'str'>
فهذا تأكيد أن items نص وليست قائمة. يمكنك أيضًا استخدام isinstance() عندما تريد التحقق داخل كود حقيقي:
if isinstance(items, list):
items.append("Python")
else:
print("المتغير ليس قائمة.")
أخطاء قريبة من هذه المشكلة
1. استخدام append مع tuple
القيم من نوع tuple لا تملك append() أيضًا، لأنها غير قابلة للتعديل.
colors = ("red", "blue")
colors.append("green")
إذا كنت تحتاج الإضافة، استخدم قائمة بدل tuple:
colors = ["red", "blue"]
colors.append("green")
2. تخزين ناتج append داخل المتغير نفسه
هذه مشكلة مختلفة لكنها شائعة جدًا:
names = ["Ali", "Sara"]
names = names.append("Omar")
print(names)
الدالة append() تعدل القائمة نفسها ولا ترجع قائمة جديدة، لذلك تصبح قيمة names هي None. اكتبها هكذا بدلًا من ذلك:
names = ["Ali", "Sara"]
names.append("Omar")
print(names)
توثق Python أن دوال القوائم التي تعدّل الكائن في مكانه لا ترجع القائمة نفسها؛ بل لا تعيد عنصرًا محددًا.
خطوات الحل السريع
- اقرأ الجزء
'str'داخل رسالة الخطأ؛ فهو يخبرك بنوع المتغير الحالي. - ابحث عن المتغير قبل سطر الخطأ، وتأكد أين حصل على قيمته.
- استخدم
type(variable)إذا لم تكن متأكدًا من نوعه. - إذا كنت تريد تخزين عناصر متعددة، ابدأ بقائمة
[]. - إذا كنت تريد دمج نصوص، استخدم
+أو f-string أوjoin(). - لا تكتب
items = items.append(value).
مثال عملي كامل: برنامج بسيط لجمع أسماء
هذا البرنامج يطلب من المستخدم كتابة أسماء، ويضيف كل اسم إلى قائمة. عندما يضغط المستخدم Enter بدون كتابة اسم، يعرض الأسماء في سطر واحد:
names = []
while True:
name = input("اكتب اسمًا، أو اضغط Enter للانتهاء: ").strip()
if not name:
break
names.append(name)
if names:
print("الأسماء:", ", ".join(names))
else:
print("لم تتم إضافة أسماء.")
في هذا المثال نستخدم كل أداة في مكانها الصحيح:
input()يعيد نصًا واحدًا.namesقائمة تخزن عدة نصوص.append()تضيف كل نص جديد إلى القائمة.join()تجمع القائمة في نص واحد للعرض النهائي.
روابط داخلية مفيدة من بايثون العرب
- سلسلة مشكلة وحل في بايثون
- مشكلة وحل 8: خطأ list object has no attribute lower في Python
- بايثون بالمثال 14: عدّ تكرار العناصر في List باستخدام Python
- بايثون بالمثال 1: حذف التكرار من قائمة في Python
- كورس أساسيات بايثون للمبتدئين
الخلاصة
- رسالة
'str' object has no attribute 'append'تعني أن المتغير نص وليس قائمة. - استخدم
append()لإضافة عنصر إلىListفقط. - استخدم
+أو f-string أوjoin()عندما تتعامل مع النصوص. - تذكر أن
input()ترجعstrدائمًا. - استخدم
type()أوisinstance()لمعرفة نوع المتغير عند تشخيص الخطأ. - لا تخزن ناتج
append()في المتغير نفسه لأنها لا ترجع قائمة جديدة.
{alertSuccess} قاعدة ذهبية: قبل كتابةvariable.append(...)، تأكد أنvariableقائمة فعلًا. وإذا كان نصًا، اسأل: هل أريد دمج نص؟ أم أريد جمع عدة عناصر داخل قائمة؟
مصادر خارجية رسمية للتوسع
- Python Tutorial: Data Structures
- Built-in Types — Python Documentation
- Common String Operations — Python Documentation
أسئلة شائعة
ما معنى str object has no attribute append في Python؟
يعني أن المتغير الذي استخدمت معه append() نص من نوع str، بينما append() دالة خاصة بالقوائم list.
هل يمكن إضافة نص إلى String باستخدام append؟
لا. استخدم دمج النصوص مثل text = text + "new" أو f-string. وإذا كنت تجمع أجزاء كثيرة، ضعها في List ثم استخدم join().
لماذا يظهر الخطأ بعد input؟
لأن input() ترجع نصًا دائمًا، حتى لو أدخل المستخدم رقمًا أو كلمة. أنشئ قائمة منفصلة ثم أضف ناتج input إليها.
هل append ترجع قائمة جديدة؟
لا. الدالة append() تعدل القائمة الحالية في مكانها وترجع None. لذلك لا تكتب items = items.append(value).
كيف أعرف هل المتغير String أو List؟
استخدم print(type(variable)). إذا ظهر <class 'str'> فهو نص، وإذا ظهر <class 'list'> فهو قائمة.
أساسيات بايثون 34: شرح class و object في Python للمبتدئين خطوة بخطوة
بعد أن تعلمت المتغيرات، الشروط، الحلقات، الدوال، القوائم، القواميس، الملفات، والتعامل مع الأخطاء، ستبدأ بملاحظة أن بعض البرامج تجمع بيانات مرتبطة مع وظائف تعمل على تلك البيانات. مثلًا: طالب له اسم ودرجة، ومنتج له اسم وسعر، ومهمة لها عنوان وحالة.
يمكنك تمثيل هذه البيانات بقاموس أو مجموعة متغيرات عندما يكون المثال صغيرًا. لكن عندما يتوسع البرنامج وتحتاج إلى إنشاء أكثر من طالب أو منتج أو مهمة، يصبح تنظيم البيانات والعمليات داخل Class خطوة مفيدة جدًا. لهذا سنتعلم في هذا الدرس معنى class وobject في Python بطريقة هادئة وعملية، دون الدخول في مفاهيم متقدمة قبل وقتها.
{alertInfo} الفكرة ببساطة: الـclassهي قالب أو مخطط يصف نوعًا من الأشياء، أما الـobjectفهو نسخة حقيقية تنشئها من هذا القالب. يمكن أن يحمل كل object بياناته الخاصة، ويستخدم الدوال الموجودة داخل class.
{getToc} $title={محتوى المقال}
ما معنى class في Python؟
الـ class هي طريقة لتعريف نوع جديد من الكائنات داخل برنامجك. يمكنك وضع بيانات مرتبطة مع دوال مرتبطة بها في مكان واحد، ثم إنشاء نسخ متعددة من هذا النوع.
تخيل أنك تريد تمثيل طلاب داخل برنامج. كل طالب لديه اسم وعمر ودرجة، وقد تحتاج إلى دالة تطبع بياناته أو تحدد هل اجتاز المادة. بدل أن تكتب متغيرات ودوال منفصلة لكل طالب، يمكنك إنشاء قالب واحد اسمه Student.
توثيق Python الرسمي يشرح أن class تجمع البيانات والوظائف معًا، وأن إنشاء class يسمح بإنتاج objects متعددة من النوع نفسه.
ما معنى object في Python؟
الـ object هو نسخة تنشئها من class. إذا كانت class هي مخطط سيارة، فالـ object هو سيارة فعلية تم تصنيعها من ذلك المخطط. يمكن أن يكون لديك عدة سيارات من النوع نفسه، لكن لكل واحدة لون ولوحة ورقم مختلف.
في Python، يمكننا إنشاء class فارغة للتجربة:
class Student:
pass
الكلمة pass تعني أن class موجودة، لكننا لم نضف لها أي محتوى بعد. ثم ننشئ object منها بوضع اسم class مع أقواس:
student_one = Student()
print(student_one)
سيظهر لك تمثيل للكائن ومكانه في الذاكرة، وقد يختلف النص الذي يظهر من جهاز لآخر. المهم أن السطر Student() أنشأ object جديدًا.
الفرق بين class و object
| المفهوم | المعنى | مثال |
|---|---|---|
class |
القالب أو التعريف العام الذي يصف نوع الشيء وخصائصه ودواله. | Student |
object |
نسخة حقيقية تم إنشاؤها من class، ولها بياناتها الخاصة. | student_one = Student() |
| Attribute | بيانات أو خاصية محفوظة داخل object. | student_one.name |
| Method | دالة داخل class تعمل غالبًا على بيانات object. | student_one.show_info() |
يمكنك إنشاء أكثر من object من class واحدة. كل object يحتفظ بقيمه الخاصة:
class Student:
pass
student_one = Student()
student_two = Student()
student_one.name = "Ali"
student_two.name = "Sara"
print(student_one.name)
print(student_two.name)
Ali
Sara
هذه الطريقة تعمل، لكن إضافة الخصائص يدويًا بعد كل object ليست منظمة عندما يكبر البرنامج. لذلك نستخدم غالبًا الدالة الخاصة __init__.
ما هي __init__ في Python؟
__init__ هي دالة خاصة تُنفذ تلقائيًا بعد إنشاء object. تستخدم غالبًا لوضع القيم الأساسية التي نريد أن يملكها كل object من هذا النوع.
في مثال الطالب، نريد أن يحصل كل طالب عند إنشائه على اسم وعمر ودرجة. لذلك نكتب:
class Student:
def __init__(self, name, age, score):
self.name = name
self.age = age
self.score = score
والآن ننشئ الطلاب بهذه الطريقة:
student_one = Student("Ali", 20, 88)
student_two = Student("Sara", 19, 94)
print(student_one.name)
print(student_two.score)
Ali
94
هنا مررت القيم عند إنشاء كل object، واحتفظ كل طالب بقيمه الخاصة. هذه طريقة أوضح وأقل عرضة للنسيان من إضافة البيانات يدويًا لاحقًا.
ما معنى self ولماذا نكتبها؟
كلمة self تشير إلى object الحالي الذي تعمل عليه الدالة. عندما تكتب self.name فأنت تقول: احفظ أو استخدم الاسم الخاص بهذا الطالب تحديدًا.
لا تمرر self بنفسك عند إنشاء object. Python تمرر object الحالي تلقائيًا عندما تستدعي method من object. ولهذا نكتب:
student_one = Student("Ali", 20, 88)
ولا نكتب هذا:
# خطأ: لا تمرر self يدويًا
student_one = Student(self, "Ali", 20, 88)
الاسم self هو الاسم المتعارف عليه في Python. تقنيًا يمكن كتابة اسم آخر، لكن الالتزام بـ self يجعل كودك مفهومًا لأي شخص يقرأه.
ما هي methods داخل class؟
الـ method هي دالة تُكتب داخل class. تستخدم عادةً لتنفيذ عملية مرتبطة ببيانات object. مثلًا، يمكن للطالب أن يطبع معلوماته أو يعرف هل درجته ناجحة.
class Student:
def __init__(self, name, age, score):
self.name = name
self.age = age
self.score = score
def show_info(self):
print("الاسم:", self.name)
print("العمر:", self.age)
print("الدرجة:", self.score)
def is_passed(self):
return self.score >= 60
بعد ذلك ننشئ object ونستخدم methods:
student_one = Student("Ali", 20, 88)
student_one.show_info()
if student_one.is_passed():
print("الطالب ناجح")
else:
print("الطالب يحتاج إلى مراجعة")
الاسم: Ali
العمر: 20
الدرجة: 88
الطالب ناجح
لاحظ أن show_info() لا تحتاج إلى طلب الاسم أو العمر مرة أخرى، لأنها تصل إلى بيانات object عبر self. أما is_passed() فترجع قيمة منطقية True أو False، لذلك استخدمناها داخل شرط if.
مثال عملي كامل: برنامج بسيط لإدارة الكتب
في المثال التالي سننشئ class تمثل كتابًا. كل كتاب له عنوان ومؤلف وعدد صفحات، وسنضيف methods لعرض البيانات ومعرفة هل الكتاب طويل أم لا.
class Book:
def __init__(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
def show_details(self):
print("العنوان:", self.title)
print("المؤلف:", self.author)
print("عدد الصفحات:", self.pages)
def is_long(self):
return self.pages >= 300
book = Book(
"Python للمبتدئين",
"بايثون العرب",
320
)
book.show_details()
if book.is_long():
print("هذا كتاب طويل.")
else:
print("هذا كتاب قصير أو متوسط.")
يمكنك تغيير بيانات الكتاب أو إنشاء كتب أخرى من class نفسها:
second_book = Book(
"تعلم القوائم",
"بايثون العرب",
120
)
second_book.show_details()
هذه هي الفائدة العملية: تكتب تعريفًا منظمًا مرة واحدة، ثم تنشئ objects متعددة دون إعادة كتابة نفس المنطق لكل كتاب.
متى أستخدم class ومتى يكفي Dictionary أو Function؟
لا تحتاج إلى class في كل كود تكتبه. في البرامج الصغيرة جدًا، قد يكون Dictionary أو Function أوضح وأسرع للفهم. استخدم class عندما تكون لديك بيانات وعمليات مرتبطة بها، أو عندما ستنشئ عدة objects من النوع نفسه.
| الحالة | الخيار المناسب غالبًا | مثال |
|---|---|---|
| قيمة بسيطة أو عملية قصيرة | متغير أو Function | دالة تحسب خصمًا لسعر واحد. |
| بيانات صغيرة بدون عمليات كثيرة | Dictionary | قاموس يحتوي اسم الطالب وعمره فقط. |
| عدة بيانات متشابهة مع عمليات خاصة بها | Class | طلاب، منتجات، كتب، مهام، حسابات. |
| مشروع يتوسع إلى ملفات ووظائف كثيرة | Classes منظمة عند الحاجة | برنامج إدارة مهام أو متجر صغير. |
{alertWarning} لا تستخدم class لمجرد أنها موجودة: إذا كان المثال بسيطًا جدًا ولا يحتوي على بيانات وعمليات مترابطة، قد يكون استخدام Function أو Dictionary أوضح. الهدف من class هو التنظيم، وليس زيادة تعقيد الكود.
أخطاء شائعة عند تعلم Classes في Python
1. نسيان الأقواس عند إنشاء object
هذا السطر يشير إلى class نفسها، ولا ينشئ object:
student_one = Student
الصحيح هو:
student_one = Student("Ali", 20, 88)
2. نسيان self داخل methods
عند تعريف method داخل class، يجب أن تستقبل self كأول parameter في methods العادية:
# خطأ
def show_info():
print("معلومات الطالب")
الصحيح:
def show_info(self):
print("معلومات الطالب")
3. كتابة اسم attribute بشكل مختلف
إذا أنشأت self.score داخل __init__ ثم كتبت لاحقًا self.scores، سيظهر خطأ لأنهما اسمان مختلفان. اختر أسماء واضحة وثابتة.
4. إعادة تعريف __init__ أكثر من مرة
داخل class واحدة، آخر دالة اسمها __init__ هي التي تبقى. لا تكتب عدة دوال __init__ وأنت تتوقع أن تعمل جميعها كأنها نسخ متعددة من constructor في لغات أخرى.
5. استخدام class variables قابلة للتعديل بلا فهم
في هذا الدرس ركزنا على بيانات كل object مثل self.name. لاحقًا ستتعلم الفرق بين instance attributes وclass attributes. لا تضع قائمة أو Dictionary قابلة للتعديل كقيمة مشتركة داخل class قبل أن تفهم كيف ستُشارك بين كل objects.
تمرين عملي
أنشئ class اسمها Product تحتوي على:
nameاسم المنتج.priceالسعر.- method اسمها
show_price()تطبع اسم المنتج وسعره. - method اسمها
discounted_price()تستقبل نسبة خصم وتعيد السعر بعد الخصم.
ابدأ بهذا القالب:
class Product:
def __init__(self, name, price):
# اكتب الكود هنا
pass
def show_price(self):
# اكتب الكود هنا
pass
def discounted_price(self, discount):
# اكتب الكود هنا
pass
حل مقترح
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
def show_price(self):
print(self.name, "-", self.price)
def discounted_price(self, discount):
return self.price - (self.price * discount / 100)
book = Product("كتاب Python", 20)
book.show_price()
print("السعر بعد الخصم:", book.discounted_price(10))
ماذا تتعلم بعد class و object؟
بعد أن تفهم فكرة class وobject و__init__ وmethods، لا تحتاج إلى القفز مباشرة إلى الوراثة inheritance أو decorators أو تصميمات متقدمة. ثبّت الأساس أولًا عبر مشاريع صغيرة فيها كائنات واضحة، مثل برنامج كتب، مهام، منتجات، أو طلاب.
ثم يمكنك الانتقال لاحقًا إلى مواضيع مثل class attributes والوراثة وتنظيم classes داخل ملفات منفصلة. وفي الوقت الحالي، راجع الدروس التي ستساعدك على فهم الأمثلة السابقة:
- كورس أساسيات بايثون للمبتدئين من الصفر
- أساسيات بايثون 13: شرح Functions في Python للمبتدئين
- أساسيات بايثون 11: شرح if و else في Python للمبتدئين
- أساسيات بايثون 33: شرح assert في Python للمبتدئين
- بايثون بعد الأساسيات 4: بناء برنامج سطر أوامر CLI بسيط
الخلاصة
- الـ
classهي قالب يصف نوعًا من الأشياء، والـobjectهو نسخة حقيقية من هذا القالب. - الـ attributes هي البيانات الخاصة بالكائن، مثل الاسم أو الدرجة أو السعر.
- الدالة
__init__تساعدك على تجهيز بيانات object عند إنشائه. selfتشير إلى object الحالي وتسمح للدوال بالوصول إلى بياناته.- الـ methods هي دوال داخل class تنفذ عمليات مرتبطة ببيانات object.
- لا تستخدم class في كل حالة؛ استخدمها عندما تجمع بيانات مترابطة وعمليات مرتبطة بها أو تحتاج نسخًا متعددة من النوع نفسه.
{alertSuccess}
خطوتك العملية: خذ مثالًا بسيطًا كتبته سابقًا باستخدام Dictionary، مثل بيانات طالب أو منتج، وحاول تحويله إلى class تحتوي على __init__ وmethod واحدة فقط. لا تضف مفاهيم كثيرة دفعة واحدة؛ افهم class صغيرة أولًا ثم وسّعها تدريجيًا.
مصادر خارجية موثوقة للتوسع
أسئلة شائعة
ما الفرق بين class و object في Python؟
class هي القالب العام الذي يحدد البيانات والدوال، بينما object هو نسخة منشأة من هذا القالب. يمكن إنشاء عدة objects من class واحدة، ولكل object بياناته الخاصة.
هل self كلمة محجوزة في Python؟
لا، لكنها الاسم المتعارف عليه للوسيط الأول في methods الخاصة بالـ instance. استخدام الاسم self يجعل الكود واضحًا وموافقًا لأسلوب Python المعتاد.
هل يجب أن تحتوي كل class على __init__؟
لا. يمكنك إنشاء class بدون __init__ إذا لم تكن تحتاج إلى تجهيز بيانات عند إنشاء object. لكن في أغلب الأمثلة العملية تساعدك __init__ على جعل الإنشاء منظمًا وواضحًا.
هل class أفضل دائمًا من Dictionary؟
لا. Dictionary مناسبة للبيانات الصغيرة والبسيطة. أما class تصبح مفيدة عندما تتكرر البيانات نفسها في عدة objects وتحتاج إلى methods مرتبطة بها أو تريد تنظيم المشروع بصورة أفضل.
هل أبدأ بالوراثة Inheritance بعد هذا الدرس؟
يمكنك قراءتها لاحقًا، لكن الأفضل أولًا أن تتدرب على إنشاء classes بسيطة واستخدام __init__ وself وmethods. الوراثة ستكون أوضح عندما يصبح هذا الأساس مريحًا بالنسبة لك.
بايثون بعد الأساسيات 5: شرح logging في Python لتسجيل أحداث البرنامج والأخطاء
عندما يكون لديك ملف Python صغير، قد يبدو استخدام print() كافيًا لمعرفة ما يحدث داخل البرنامج. لكن بعد أن يبدأ مشروعك بالنمو، ستحتاج إلى معرفة أشياء أكثر: متى بدأ البرنامج؟ هل تمت قراءة ملف بنجاح؟ لماذا فشلت عملية معينة؟ ما الخطأ الذي ظهر؟ وهل تكرر الخطأ لاحقًا؟
هنا تأتي مكتبة logging المدمجة مع Python. بدل أن تضع عشرات أوامر الطباعة داخل المشروع، يمكنك تسجيل الأحداث المهمة بمستويات واضحة، ثم عرضها في الطرفية أو حفظها داخل ملف للرجوع إليها لاحقًا.
{alertInfo} الفكرة ببساطة: استخدمprint()عندما تريد عرض نتيجة عادية للمستخدم، واستخدمloggingعندما تريد تسجيل ما يحدث داخل البرنامج لمتابعة الحالة أو تشخيص مشكلة أو حفظ سجل للأخطاء.
{getToc} $title={محتوى المقال}
ما هي مكتبة logging في Python؟
logging هي مكتبة قياسية تأتي مع Python، لذلك لا تحتاج إلى تثبيتها عبر pip. مهمتها تسجيل رسائل منظمة تصف أحداث البرنامج أثناء تشغيله، مثل بدء مهمة، نجاح قراءة ملف، ظهور إدخال غير مناسب، أو حدوث خطأ.
الميزة المهمة ليست مجرد كتابة رسالة، بل أنك تستطيع تحديد مستوى الرسالة، وإظهارها في المكان المناسب، وإضافة وقت التسجيل واسم الملف أو أي معلومات أخرى تحتاجها.
هذا مهم خصوصًا بعد أن بدأت في بناء مشاريع منظمة، مثل مشروع CSV أو أداة سطر أوامر CLI. في هذه المرحلة لم يعد السؤال فقط: “هل يعمل البرنامج؟” بل: “ماذا حدث داخله عندما لم يعمل كما توقعت؟”
الفرق بين print وlogging
| الحالة | استخدم print | استخدم logging |
|---|---|---|
| عرض نتيجة للمستخدم | نعم، مثل عرض اسم ملف تم إنشاؤه أو نتيجة عملية حسابية. | ليس الخيار الأساسي عادةً. |
| تتبع خطوات البرنامج | مفيد مؤقتًا أثناء التعلم فقط. | نعم، لأنه منظم ويمكن التحكم في مستواه ومكان حفظه. |
| حفظ سجل يمكن مراجعته لاحقًا | لا، إلا إذا نسخت مخرجات الطرفية يدويًا. | نعم، يمكن الكتابة تلقائيًا داخل ملف .log. |
| تسجيل خطأ داخل برنامج طويل | قد يوضح رسالة بسيطة، لكنه لا ينظم السياق جيدًا. | نعم، ويمكن تسجيل تفاصيل الاستثناء ووقت حدوثه. |
لا يعني هذا أنك يجب أن تحذف print() من كل مشروع. البرامج التي تتعامل مع المستخدم في الطرفية تحتاج رسائل واضحة مثل “تم الحفظ بنجاح”. أما logging فيعمل كدفتر ملاحظات تقني للمشروع والمطور.
مستويات الرسائل في logging
تستخدم logging مستويات مختلفة لتوضح مدى أهمية الحدث. الترتيب التالي يساعدك في اختيار المستوى المناسب:
| المستوى | متى تستخدمه؟ | مثال |
|---|---|---|
DEBUG | تفاصيل دقيقة تساعدك أثناء التطوير والتشخيص. | عدد الصفوف التي قرأها البرنامج من ملف CSV. |
INFO | حدث طبيعي مهم أثناء سير البرنامج. | تم حفظ التقرير بنجاح. |
WARNING | حدث غير مثالي، لكن البرنامج ما زال قادرًا على الاستمرار. | الملف غير موجود، لذلك سيتم إنشاء ملف جديد. |
ERROR | فشل حدث محدد ويحتاج إلى مراجعة. | تعذر قراءة البيانات بسبب تنسيق غير صالح. |
CRITICAL | مشكلة كبيرة قد تمنع البرنامج من العمل بشكل أساسي. | تعذر بدء التطبيق لأن ملف الإعدادات الرئيسي تالف. |
أول مثال: تسجيل رسالة بسيطة
ابدأ باستيراد المكتبة، ثم استخدم basicConfig() لتحديد أقل مستوى تريد إظهاره. في المثال التالي سنعرض رسائل INFO وما فوقها:
import logging
logging.basicConfig(
level=logging.INFO,
format="%(levelname)s - %(message)s"
)
logging.debug("هذه رسالة تفصيلية للتشخيص")
logging.info("تم تشغيل البرنامج بنجاح")
logging.warning("لم يتم العثور على صورة المستخدم")
لن تظهر رسالة DEBUG هنا، لأننا حددنا المستوى INFO. لكن رسالتي INFO وWARNING ستظهران.
INFO - تم تشغيل البرنامج بنجاح
WARNING - لم يتم العثور على صورة المستخدم
جرّب تغيير level=logging.INFO إلى level=logging.DEBUG ثم شغّل الكود مرة أخرى. ستظهر الرسالة التفصيلية أيضًا.
إضافة الوقت إلى السجل
عندما تتكرر عمليات البرنامج، تصبح معرفة وقت الحدث مهمة. يمكنك إضافة التاريخ والوقت إلى شكل السجل باستخدام %(asctime)s:
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(levelname)s | %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
logging.info("بدأت عملية إنشاء التقرير")
logging.info("انتهت عملية إنشاء التقرير")
قد يكون الناتج بهذا الشكل:
2026-07-02 20:15:03 | INFO | بدأت عملية إنشاء التقرير
2026-07-02 20:15:04 | INFO | انتهت عملية إنشاء التقرير
الوقت الظاهر يعتمد على وقت جهازك أو بيئة التشغيل التي يعمل عليها البرنامج.
كيف تحفظ السجلات داخل ملف؟
أحيانًا تحتاج أن تعرف ما حدث بعد أن يغلق المستخدم البرنامج أو بعد أن تتوقف الطرفية عن العرض. لذلك يمكنك توجيه الرسائل إلى ملف مثل app.log.
import logging
logging.basicConfig(
filename="app.log",
encoding="utf-8",
level=logging.INFO,
format="%(asctime)s | %(levelname)s | %(message)s"
)
logging.info("بدأ البرنامج")
logging.warning("تم استخدام قيمة افتراضية")
logging.error("تعذر حفظ التقرير")
بعد تشغيل الملف، سيظهر بجانبه ملف جديد باسم app.log. بشكل افتراضي، يضيف Python السجلات الجديدة إلى نهاية الملف بدل حذف السجل السابق.
{alertWarning}
ملاحظة مهمة: لا تضع basicConfig() في ملفات كثيرة داخل المشروع. الأفضل أن تضبط إعدادات logging مرة واحدة عند بداية تشغيل التطبيق، ثم تستخدم التسجيل في بقية الملفات.
استخدام logger باسم الملف
في المشروع الصغير يمكن استعمال logging.info() مباشرة. لكن عندما يصبح المشروع من عدة ملفات، من الأفضل إنشاء logger باسم كل ملف. الطريقة الشائعة هي استخدام __name__:
import logging
logger = logging.getLogger(__name__)
logger.info("تمت قراءة ملف البيانات")
logger.warning("بعض الصفوف تحتوي على قيم ناقصة")
وعند ضبط التنسيق، يمكنك إظهار اسم logger أيضًا:
format="%(asctime)s | %(name)s | %(levelname)s | %(message)s"
هذا يصبح مفيدًا عندما تريد معرفة أي جزء من المشروع سجل الرسالة: ملف قراءة البيانات، أو ملف الحسابات، أو ملف واجهة سطر الأوامر.
تسجيل الأخطاء مع try و except
تعلمت سابقًا استخدام try وexcept حتى لا يتوقف البرنامج فجأة. الآن يمكنك جعل التعامل مع الخطأ أكثر فائدة عبر تسجيله أيضًا.
في المثال التالي نحاول قراءة ملف JSON. إذا كان الملف غير موجود أو كان محتواه غير صالح، نعرض رسالة مناسبة للمستخدم ونسجل الخطأ للتشخيص:
import json
import logging
from pathlib import Path
logging.basicConfig(
filename="app.log",
encoding="utf-8",
level=logging.INFO,
format="%(asctime)s | %(levelname)s | %(message)s"
)
logger = logging.getLogger(__name__)
file_path = Path("data.json")
try:
content = file_path.read_text(encoding="utf-8")
data = json.loads(content)
logger.info("تمت قراءة البيانات بنجاح")
print(data)
except FileNotFoundError:
logger.warning("ملف data.json غير موجود")
print("لم يتم العثور على ملف البيانات.")
except json.JSONDecodeError:
logger.exception("محتوى ملف JSON غير صالح")
print("محتوى ملف البيانات غير صحيح.")
الدالة logger.exception() تستخدم داخل except عندما تريد تسجيل رسالة الخطأ مع تفاصيل الاستثناء. هذا مفيد للتشخيص، لكنه لا يعني أن تعرض تلك التفاصيل التقنية كاملة للمستخدم النهائي.
مشروع صغير: سجل عمليات برنامج مهام
لنطبق الفكرة في مثال قريب من المشاريع الواقعية. البرنامج التالي يضيف مهمة إلى ملف نصي، ثم يسجل نجاح العملية أو فشلها في ملف logs/app.log.
هيكل المشروع
task_manager/
│
├── main.py
├── tasks.txt
└── logs/
└── app.log
ملف main.py
import logging
from pathlib import Path
BASE_DIR = Path(__file__).parent
LOGS_DIR = BASE_DIR / "logs"
TASKS_FILE = BASE_DIR / "tasks.txt"
LOGS_DIR.mkdir(exist_ok=True)
logging.basicConfig(
filename=LOGS_DIR / "app.log",
encoding="utf-8",
level=logging.INFO,
format="%(asctime)s | %(levelname)s | %(message)s"
)
logger = logging.getLogger(__name__)
task = input("اكتب المهمة الجديدة: ").strip()
if not task:
logger.warning("حاول المستخدم إضافة مهمة فارغة")
print("لا يمكن إضافة مهمة فارغة.")
else:
try:
with TASKS_FILE.open("a", encoding="utf-8") as file:
file.write(task + "\n")
logger.info("تمت إضافة مهمة جديدة")
print("تمت إضافة المهمة بنجاح.")
except OSError:
logger.exception("فشلت عملية حفظ المهمة")
print("تعذر حفظ المهمة. حاول مرة أخرى.")
لاحظ أن البرنامج لا يسجل نص المهمة نفسها. هذه عادة جيدة، لأن بعض البيانات التي يدخلها المستخدم قد تكون خاصة أو حساسة. سجّل ما تحتاجه للتشخيص فقط، ولا تجعل السجل مكانًا لحفظ كلمات المرور أو الرموز السرية أو البيانات الشخصية غير الضرورية.
أخطاء شائعة عند استخدام logging
1. توقع ظهور رسائل DEBUG دون تغيير المستوى
المستوى الافتراضي لا يعرض رسائل DEBUG. إذا كنت تحتاجها أثناء التشخيص، اضبط level=logging.DEBUG.
2. استدعاء basicConfig أكثر من مرة دون تنظيم
في أغلب المشاريع، اضبط الإعدادات عند بداية البرنامج فقط. ثم أنشئ loggers في الملفات الأخرى باستخدام getLogger(__name__).
3. استخدام logging بدل الاستثناءات
تسجيل الخطأ لا يحل المشكلة وحده. عندما تكون العملية لا تستطيع الاستمرار بشكل صحيح، قد تحتاج إلى رفع استثناء أو التعامل معه بوضوح. التسجيل يوثق ما حدث، لكنه لا يغني عن منطق معالجة الأخطاء.
4. تسجيل بيانات حساسة
تجنب تسجيل كلمات المرور، رموز API، ملفات تعريف الارتباط، الأرقام البنكية، أو نصوص المستخدمين كاملة دون حاجة. افترض أن ملف السجل قد يراه شخص آخر داخل بيئة العمل أو أثناء الدعم الفني.
5. جعل السجل مليئًا برسائل غير مفيدة
لا تسجل كل سطر من البرنامج. سجل القرارات والعمليات المهمة: بداية مهمة، نجاح عملية، تحذير يستحق المراجعة، أو خطأ يحتاج تشخيصًا.
متى تستخدم logging في مشاريعك؟
- عند بناء أداة سطر أوامر CLI تحتاج إلى تتبع الأوامر أو الأخطاء.
- عند قراءة ملفات CSV أو JSON وتريد معرفة سبب فشل عملية القراءة أو الحفظ.
- عند إنشاء تقارير أو ملفات تلقائيًا وتريد حفظ تاريخ العمليات.
- عند وجود أكثر من ملف داخل المشروع وتحتاج معرفة مصدر كل رسالة.
- عند تشغيل برنامج متكرر أو طويل لا تكون موجودًا أمامه طوال الوقت.
روابط داخلية مفيدة من بايثون العرب
- بايثون بعد الأساسيات 4: بناء برنامج سطر أوامر CLI بسيط
- بايثون بعد الأساسيات 2: تنظيم ملفات مشروع Python بطريقة صحيحة
- بايثون بعد الأساسيات 3: قراءة وكتابة ملفات CSV داخل مشروع منظم
- أساسيات بايثون 31: شرح try و except في Python
- أساسيات بايثون 33: شرح assert واختبار صحة الشروط
الخلاصة
print()مناسب لعرض نتائج عادية للمستخدم، بينماloggingمناسب لتسجيل أحداث البرنامج وتشخيص المشكلات.- استخدم مستويات الرسائل لتوضيح أهمية الحدث:
DEBUGوINFOوWARNINGوERRORوCRITICAL. - يمكنك حفظ السجلات داخل ملف
.logومراجعته عند حدوث مشكلة لاحقًا. - استخدم
logger.exception()داخلexceptعندما تريد تسجيل تفاصيل الاستثناء. - اضبط إعدادات التسجيل مرة واحدة عند بداية المشروع، وتجنب كتابة بيانات حساسة في ملفات السجل.
{alertSuccess}
خطوتك التالية: افتح مشروعًا صغيرًا كتبته سابقًا، وأضف سجلًا واحدًا لبدء البرنامج، وسجلًا لنجاح عملية أساسية، وسجلًا للأخطاء داخل except. بهذه الخطوة يتحول مشروعك من مثال تعليمي إلى برنامج أسهل في المتابعة والصيانة.
مصادر خارجية رسمية للتوسع
- Logging HOWTO — Python Documentation
- logging — Python Standard Library
- Logging handlers — Python Documentation
أسئلة شائعة
هل أحتاج إلى تثبيت logging باستخدام pip؟
لا. مكتبة logging جزء من مكتبة Python القياسية، لذلك يكفي أن تكتب import logging.
هل أستبدل print بالكامل بـ logging؟
لا. استخدم print() للرسائل والنتائج التي يجب أن يراها مستخدم البرنامج مباشرة. واستخدم logging لتسجيل أحداث التشغيل والتفاصيل التقنية والأخطاء.
أين أضع basicConfig في المشروع؟
ضعه عادةً في الملف الذي يبدأ تشغيل التطبيق، مثل main.py. لا تكرر الإعدادات في كل ملف، ثم استخدم getLogger(__name__) داخل الملفات الأخرى.
هل يمكن أن أحفظ السجلات في ملف وعرضها في الطرفية معًا؟
نعم، لكن ذلك يحتاج استخدام handlers متعددة. ابدأ أولًا بالكتابة إلى ملف أو العرض في الطرفية، ثم انتقل إلى handlers عندما تحتاج إعدادًا أكثر تنظيمًا.
هل logger.exception مختلف عن logger.error؟
نعم. داخل كتلة except، تستخدم logger.exception() عندما تريد تسجيل الرسالة مع تفاصيل الاستثناء. أما logger.error() فيسجل رسالة خطأ فقط ما لم تمرر له معلومات إضافية.
بايثون بالمثال 14: عدّ تكرار العناصر في List باستخدام Python بطريقة عملية
في الدروس السابقة من سلسلة بايثون بالمثال تعاملنا مع القوائم List بطرق عملية، مثل حساب مجموع العناصر والبحث عن عنصر داخل القائمة. الآن سنأخذ مهارة مفيدة جدًا في برامج كثيرة: معرفة عدد مرات تكرار عنصر داخل List في Python.
ستحتاج هذه الفكرة عند تحليل درجات الطلاب، معرفة أكثر منتج تكرر في طلبات العملاء، عد الكلمات المتكررة، مراجعة أسماء داخل قائمة، أو فحص بيانات بسيطة قبل استخدامها في مشروع أكبر.
{alertInfo} الفكرة ببساطة: الدالةcount()تخبرك كم مرة ظهر عنصر معين داخل القائمة. وإذا احتجت إلى عدّ كل العناصر دفعة واحدة، يمكنك استخدام حلقةforأوCounter.
{getToc} $title={محتوى المقال}
ما الذي سنتعلمه في هذا المثال؟
- استخدام
count()لمعرفة تكرار عنصر محدد. - عدّ تكرار كل عنصر داخل القائمة باستخدام
for. - استخدام
Counterعندما تكون القائمة أكبر أو عندما تريد ملخصًا جاهزًا. - معرفة أكثر عنصر متكرر داخل List.
- تجنب أخطاء شائعة، مثل اختلاف الحروف الكبيرة والصغيرة أو استخدام
count()بدون عنصر.
مثال البداية: قائمة منتجات متكررة
لنفترض أن لدينا قائمة تحتوي على منتجات طلبها العملاء خلال يوم واحد:
products = [
"Laptop",
"Mouse",
"Laptop",
"Keyboard",
"Mouse",
"Laptop"
]
نريد الآن معرفة: كم مرة ظهر المنتج "Laptop"؟
الطريقة الأولى: استخدام count مع List
الطريقة الأسهل هي استخدام الدالة count() الخاصة بالقوائم. اكتب اسم القائمة، ثم نقطة، ثم count، وضع العنصر الذي تريد عدّه بين القوسين.
products = [
"Laptop",
"Mouse",
"Laptop",
"Keyboard",
"Mouse",
"Laptop"
]
laptop_count = products.count("Laptop")
print("عدد مرات ظهور Laptop:", laptop_count)
الناتج:
عدد مرات ظهور Laptop: 3
تعد count() عنصرًا واحدًا محددًا وتعيد عدد مرات وجوده في القائمة. إذا لم يكن العنصر موجودًا، فستعيد 0 بدل ظهور خطأ.
شرح count خطوة بخطوة
في السطر التالي:
laptop_count = products.count("Laptop")
productsهي القائمة التي نبحث داخلها.count()هي الدالة التي تعدّ التكرار."Laptop"هو العنصر الذي نريد معرفة عدد مرات ظهوره.- الناتج يُحفظ داخل المتغير
laptop_count.
يمكنك تغيير العنصر بسهولة:
print(products.count("Mouse"))
print(products.count("Keyboard"))
print(products.count("Monitor"))
الناتج سيكون:
2
1
0
مثال عملي: عدّ الحضور والغياب
يمكنك استخدام الفكرة نفسها لمعرفة عدد مرات حضور الطالب أو غيابه داخل قائمة:
attendance = [
"حاضر",
"حاضر",
"غائب",
"حاضر",
"غائب"
]
present_count = attendance.count("حاضر")
absent_count = attendance.count("غائب")
print("عدد الحضور:", present_count)
print("عدد الغياب:", absent_count)
هذه الطريقة مناسبة جدًا عندما تعرف مسبقًا العناصر التي تريد عدّها، مثل "حاضر" و"غائب" أو "ناجح" و"راسب".
متى لا تكفي count وحدها؟
إذا كانت لديك قائمة طويلة وتريد معرفة تكرار كل عنصر، فكتابة count() يدويًا لكل عنصر ليست عملية. هنا نحتاج إلى طريقة تجمع النتائج في جدول أو قاموس.
لنستخدم قائمة ألوان:
colors = [
"red",
"blue",
"red",
"green",
"blue",
"red"
]
الطريقة الثانية: عدّ كل العناصر باستخدام for وcount
سنمر على العناصر بحلقة for، لكننا لا نريد طباعة العنصر أكثر من مرة. لذلك ننشئ قائمة فارغة باسم counted_colors لحفظ العناصر التي انتهينا من عدّها.
colors = [
"red",
"blue",
"red",
"green",
"blue",
"red"
]
counted_colors = []
for color in colors:
if color not in counted_colors:
total = colors.count(color)
print(color, =, total)
counted_colors.append(color)
الناتج:
red = 3
blue = 2
green = 1
هذه الطريقة مفيدة تعليميًا لأنها تجمع بين القوائم وحلقة for والشرط if والدالة count().
طريقة أقصر باستخدام Dictionary
عندما تبدأ بالتعامل مع القواميس Dictionary، تستطيع بناء قاموس يحفظ كل عنصر كـمفتاح وعدد تكراره كقيمة:
colors = [
"red",
"blue",
"red",
"green",
"blue",
"red"
]
counts = {}
for color in colors:
if color in counts:
counts[color] += 1
else:
counts[color] = 1
print(counts)
الناتج:
{'red': 3, 'blue': 2, 'green': 1}
هذه الفكرة مهمة لأنها تمهد لك لاستخدام Counter، وهو حل جاهز من مكتبة Python القياسية.
الطريقة الثالثة: استخدام Counter لعدّ عناصر List
تحتوي Python على أداة جاهزة اسمها Counter داخل مكتبة collections. وهي نوع خاص من القواميس مصمم لعد العناصر القابلة للاستخدام كمفاتيح.
from collections import Counter
colors = [
"red",
"blue",
"red",
"green",
"blue",
"red"
]
counts = Counter(colors)
print(counts)
الناتج سيكون قريبًا من:
Counter({'red': 3, 'blue': 2, 'green': 1})
ولمعرفة أكثر عنصر متكرر، استخدم most_common(1):
most_common_color = counts.most_common(1)
print("أكثر لون متكرر:", most_common_color)
الناتج:
أكثر لون متكرر: [('red', 3)]
أي طريقة أختار؟
| الطريقة | متى تستخدمها؟ | ملاحظات |
|---|---|---|
list.count() |
عندما تريد عدّ عنصر واحد أو عدد قليل من العناصر المعروفة. | الأبسط للمبتدئ. |
for + count() |
للتدريب على القوائم والحلقات والشروط. | مفيد للفهم، لكنه ليس الأفضل للقوائم الكبيرة جدًا. |
| Dictionary | عندما تريد بناء الفكرة بنفسك ومعرفة آلية العدّ. | ينمي فهمك للقواميس. |
Counter |
عندما تريد تكرار كل العناصر وملخصًا جاهزًا. | واضح ومناسب للمشاريع العملية. |
مثال تطبيقي: معرفة أكثر كلمة تكررت
يمكنك تطبيق الفكرة على قائمة كلمات. لاحظ أن Python تعتبر "Python" و"python" قيمتين مختلفتين بسبب اختلاف حالة الحروف.
from collections import Counter
words = [
"Python",
"python",
"List",
"Python",
"loop",
"python"
]
normalized_words = [word.lower() for word in words]
word_counts = Counter(normalized_words)
print(word_counts.most_common(1))
هنا استخدمنا lower() لتوحيد حالة الحروف قبل العد. بهذه الطريقة تُحسب "Python" و"python" ككلمة واحدة.
أخطاء شائعة عند استخدام count مع List
1. استخدام count بدون عنصر
هذا الكود غير صحيح:
items.count()
لأن count() تحتاج إلى عنصر محدد كي تعرف ما الذي ستعده.
الصحيح:
items.count("Python")
2. تجاهل الحروف الكبيرة والصغيرة
names = ["Ali", "ali", "ALI"]
print(names.count("Ali"))
الناتج هنا 1 فقط. إذا أردت تجاهل حالة الحروف، وحّد النصوص أولًا باستخدام lower() أو casefold() عند الحاجة.
3. استخدام Counter على عناصر غير قابلة للعد كمفاتيح
Counter يعمل مع عناصر يمكن استخدامها كمفاتيح قاموس، مثل النصوص والأرقام وTuples. لكنه لا يعمل مباشرة مع List داخل List، لأن القوائم قابلة للتعديل ولا تصلح كمفاتيح.
{alertWarning} مهم: إذا كانت البيانات قادمة من المستخدم أو من ملف، نظف النصوص قبل عدّها. قد توجد مسافات زائدة أو اختلافات في حالة الحروف تجعل العنصر نفسه يظهر كأنه عناصر مختلفة.
تدريب عملي
جرّب تنفيذ التمارين التالية بنفسك:
- أنشئ قائمة فيها 10 أرقام، واحسب عدد مرات ظهور الرقم
5. - أنشئ قائمة أسماء فيها تكرار، ثم اعرض عدد تكرار كل اسم.
- استخدم
Counterلمعرفة أكثر اسم تكرر. - أضف
lower()إلى قائمة كلمات إنجليزية قبل العد. - اكتب برنامجًا يطلب من المستخدم اسمًا ثم يخبره كم مرة ظهر داخل قائمة ثابتة.
مثال حل مختصر للتمرين الأخير
names = ["Ali", "Sara", "Ali", "Mona"]
search_name = input("اكتب الاسم الذي تريد البحث عنه: ")
total = names.count(search_name)
print("عدد مرات التكرار:", total)
يمكنك تطويره لاحقًا ليقبل البحث دون التأثر بحالة الحروف أو المسافات الزائدة.
روابط داخلية مفيدة من بايثون العرب
- بايثون بالمثال 13: البحث داخل List في Python بطريقة عملية
- بايثون بالمثال: حساب مجموع عناصر List في Python بأكثر من طريقة
- الفرق بين append وextend في Python للمبتدئين
- مشكلة وحل: IndexError list index out of range في Python
مصادر موثوقة للتوسع
- Python Lists and list methods — Python Documentation
- Counter — Python Documentation
- More Control Flow Tools — Python Documentation
الخلاصة
- استخدم
list.count(item)عندما تريد معرفة عدد تكرار عنصر محدد. - استخدم
forمعcount()لتفهم فكرة عدّ جميع العناصر. - استخدم Dictionary أو
Counterعندما تريد ملخصًا لتكرار كل العناصر. - وحّد النصوص باستخدام
lower()أوstrip()عندما تكون البيانات غير منظمة. - ابدأ بقوائم صغيرة، ثم طبّق الفكرة على أسماء أو منتجات أو كلمات في مشروع بسيط.
أسئلة شائعة
ما الفرق بين count وCounter في Python؟
count() تعدّ عنصرًا واحدًا داخل قائمة أو نص. أما Counter فتنشئ ملخصًا لعدد تكرار كل العناصر في iterable مثل List أو String.
هل count تعمل مع النصوص أيضًا؟
نعم. يمكن استخدام count() مع النصوص لمعرفة عدد مرات ظهور حرف أو كلمة أو جزء من النص، لكن هذا الدرس ركز على القوائم.
ماذا تعيد count إذا لم يكن العنصر موجودًا؟
تعيد 0، لذلك يمكنك استخدامها بأمان لمعرفة هل العنصر تكرر دون الحاجة إلى شرط إضافي.
ما أسرع طريقة لعد كل عناصر قائمة؟
في التطبيقات العملية غالبًا يكون Counter واضحًا ومناسبًا، لأنه ينشئ قاموسًا من العناصر وعدد تكرارها مباشرة.
هل يمكن استخدام Counter مع قائمة من القوائم؟
ليس مباشرة، لأن القائمة نفسها غير قابلة للاستخدام كمفتاح قاموس. يمكنك تحويل القوائم الداخلية إلى Tuple إذا كان ذلك مناسبًا لطبيعة بياناتك.
ما هو نموذج تعلم الآلة؟ وكيف يتعلم من البيانات خطوة بخطوة
بعد أن تعرفت على أساسيات تحليل البيانات ببايثون، تأتي الخطوة التالية: فهم نموذج تعلم الآلة. كثير من المبتدئين يسمعون كلمة “Model” ويتخيلون شيئًا غامضًا أو معقدًا، بينما فكرته الأساسية بسيطة جدًا: نعطي الكمبيوتر أمثلة سابقة، فيبحث عن علاقة أو نمط، ثم يستخدم ما تعلمه لإعطاء توقع أو تصنيف لبيانات جديدة.
في هذا الدرس ستفهم ما هو نموذج تعلم الآلة، وما الفرق بين البيانات والنموذج، وما معنى Features وTarget، ولماذا نقسم البيانات إلى تدريب واختبار، وكيف تكتب أول مثال عملي باستخدام Python وscikit-learn.
{alertInfo} الفكرة الأساسية: نموذج تعلم الآلة لا يحفظ إجابة واحدة فقط. هو يحاول تعلّم نمط من أمثلة سابقة، ثم يستخدم ذلك النمط للتعامل مع أمثلة جديدة لم يرها من قبل.
{getToc} $title={محتوى المقال}
ما هو نموذج تعلم الآلة؟
نموذج تعلم الآلة هو برنامج رياضي أو خوارزمية تتعلم علاقة بين مدخلات ونتائج من بيانات تدريب. بعد التدريب، يمكن استخدام النموذج لتوقع قيمة، أو تصنيف حالة، أو اتخاذ قرار محدود حسب المهمة التي صُمم لها.
مثال بسيط: لدينا بيانات طلاب تشمل عدد ساعات الدراسة ودرجة الاختبار. نعطي النموذج أمثلة سابقة، ثم نطلب منه توقع درجة طالب جديد بناءً على عدد ساعات دراسته. النموذج لا “يعرف” الطالب، لكنه يبحث عن نمط تقريبي داخل البيانات التي تدرب عليها.
تدعم مكتبة scikit-learn مهام تعلم خاضع للإشراف وغير خاضع للإشراف، وتوفر طريقة موحدة نسبيًا لتدريب النماذج باستخدام fit() ثم إجراء التوقعات باستخدام predict().
الفرق بين البرنامج التقليدي ونموذج تعلم الآلة
في البرنامج التقليدي تكتب القاعدة بنفسك. أما في تعلم الآلة فتقدم أمثلة، ويستنتج النموذج العلاقة التقريبية من البيانات.
| الجانب | برنامج تقليدي قائم على قواعد | نموذج تعلم آلة |
|---|---|---|
| من يكتب القواعد؟ | المبرمج يكتب القواعد بوضوح. | النموذج يستنتج نمطًا من الأمثلة. |
| مثال | إذا كانت الدرجة أقل من 60، اطبع “يحتاج مراجعة”. | توقع الدرجة بناءً على ساعات الدراسة وبيانات سابقة. |
| أفضل استخدام | عندما تكون القواعد واضحة وثابتة. | عندما تكون العلاقة غير واضحة وتتوفر بيانات مفيدة. |
| المشكلة المحتملة | صعوبة كتابة قاعدة لكل حالة. | نتيجة ضعيفة إذا كانت البيانات ضعيفة أو غير ممثلة. |
ما هي البيانات التي يتعلم منها النموذج؟
في تعلم الآلة الخاضع للإشراف، تكون لديك أمثلة تحتوي على مدخلات ونتيجة معروفة. المدخلات تسمى غالبًا Features أو الميزات، أما النتيجة التي تريد من النموذج توقعها فتسمى Target أو الهدف.
مثال بيانات طلاب:
| ساعات الدراسة | الحضور | الدرجة النهائية |
|---|---|---|
| 2 | 70% | 55 |
| 4 | 85% | 72 |
| 6 | 92% | 88 |
في هذا المثال:
- Features: ساعات الدراسة والحضور.
- Target: الدرجة النهائية.
بعد التدريب، يمكننا إعطاء النموذج ساعات دراسة وحضور لطالب جديد، فيعطينا توقعًا تقريبيًا للدرجة.
هل يتعلم النموذج مثل الإنسان؟
لا. كلمة “يتعلم” هنا لا تعني أن النموذج يفهم العالم أو يمتلك وعيًا. المقصود أنه يضبط قيمًا داخلية في الخوارزمية لتقليل الفرق بين توقعاته والنتائج الفعلية الموجودة في بيانات التدريب.
في الانحدار الخطي مثلًا، يحاول النموذج العثور على خط تقريبي يربط المدخلات بالنتيجة. وفي التصنيف، يحاول العثور على حدود أو أنماط تساعده على اختيار الفئة المناسبة.
لهذا السبب لا توجد نتيجة جيدة من دون بيانات مناسبة. النموذج لا يصنع معلومات صحيحة من فراغ؛ هو يتأثر بجودة الأمثلة، وتمثيلها للمشكلة، وطريقة تجهيزها.
خطوات عمل نموذج تعلم الآلة
معظم المشاريع الصغيرة تمر بمراحل متشابهة:
- حدد المشكلة: هل تريد توقع رقم أم تصنيف شيء إلى فئة؟
- اجمع البيانات: اختر بيانات مرتبطة بالسؤال الذي تريد الإجابة عنه.
- نظف البيانات: راجع القيم الناقصة والتكرار والأنواع الخاطئة.
- اختر Features وTarget: حدد المدخلات والنتيجة المطلوبة.
- قسّم البيانات: جزء للتدريب وجزء للاختبار.
- درّب النموذج: استخدم
fit()على بيانات التدريب. - اختبر النموذج: اطلب توقعات من بيانات لم يرها أثناء التدريب.
- قيّم النتيجة: افحص هل أداء النموذج مفيد أم يحتاج تحسينًا.
ما الفرق بين التوقع والتصنيف؟
من أول الأمور التي تحتاج فهمها: هل النتيجة التي تريدها رقم أم فئة؟
| نوع المهمة | ما الذي يتوقعه النموذج؟ | مثال |
|---|---|---|
| Regression الانحدار | قيمة رقمية مستمرة. | توقع سعر منزل أو درجة طالب أو مبيعات شهر. |
| Classification التصنيف | فئة أو تصنيف محدد. | رسالة مزعجة أو غير مزعجة، ناجح أو يحتاج مراجعة. |
في هذا الدرس سنستخدم الانحدار لأن مثال توقع الدرجة سهل الفهم، لكن المبدأ العام للتدريب والاختبار موجود أيضًا في التصنيف.
لماذا نقسم البيانات إلى تدريب واختبار؟
لو دربت النموذج ثم اختبرته على البيانات نفسها، قد يبدو أداؤه ممتازًا لأنه رأى هذه الأمثلة مسبقًا. هذا لا يخبرك هل يستطيع التعامل مع بيانات جديدة فعلًا.
لذلك نقسم البيانات غالبًا إلى:
- Training Data: بيانات يتعلم منها النموذج.
- Test Data: بيانات لا يراها أثناء التدريب، ونستخدمها لفحص الأداء.
توضح مواد Google التعليمية أن الاختبار العادل يجب أن يكون على أمثلة جديدة وغير مكررة عن أمثلة التدريب، لأن تكرار المثال في التدريب والاختبار يجعل التقييم مضللًا.
تقسيم البيانات باستخدام Python
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
X,
y,
test_size=0.25,
random_state=42
)
في هذا المثال خصصنا 25% من البيانات للاختبار. القيمة random_state تساعد على إعادة الحصول على التقسيم نفسه عند تشغيل المثال مرة أخرى، وهو مفيد في الدروس والتجارب.
أول نموذج تعلم آلة باستخدام Python
المثال التالي تعليمي ومصطنع. يوضح فقط شكل العمل: تجهيز بيانات، تقسيمها، تدريب نموذج، ثم طلب توقع وتقييم أولي.
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error
from sklearn.model_selection import train_test_split
data = pd.DataFrame({
"hours": [1, 2, 3, 4, 5, 6, 7, 8],
"score": [44, 51, 58, 67, 73, 81, 86, 92]
})
X = data[["hours"]]
y = data["score"]
X_train, X_test, y_train, y_test = train_test_split(
X,
y,
test_size=0.25,
random_state=42
)
model = LinearRegression()
model.fit(X_train, y_train)
predictions = model.predict(X_test)
mae = mean_absolute_error(y_test, predictions)
print("متوسط الخطأ المطلق:", mae)
new_student = pd.DataFrame({"hours": [6.5]})
predicted_score = model.predict(new_student)
print("الدرجة المتوقعة:", predicted_score[0])
الدالة LinearRegression في scikit-learn تبني نموذجًا خطيًا يحاول تقليل مجموع مربعات الفروق بين القيم الفعلية والأهداف التي يتوقعها النموذج.
{alertWarning} لا تستخدم بيانات مصطنعة أو نموذجًا صغيرًا لاتخاذ قرارات حقيقية عن أشخاص. الأمثلة هنا للتعلم فقط، ولا تصلح لتقييم الطلاب أو التوظيف أو الصحة أو أي قرار حساس.
ماذا يعني تقييم النموذج؟
تدريب النموذج ليس نهاية العمل. بعد التدريب تحتاج إلى سؤال بسيط: هل توقعاته مفيدة على بيانات جديدة؟
في مسائل الانحدار يمكن استخدام مقاييس مثل MAE، أي متوسط الفرق المطلق بين التوقع والقيمة الفعلية. كلما كان الرقم أقل، كانت التوقعات أقرب في المتوسط، لكن لا ينبغي تفسير الرقم وحده من دون معرفة طبيعة المشكلة ووحدة القياس.
وفي مسائل التصنيف توجد مقاييس أخرى مثل accuracy وprecision وrecall. اختيار المقياس الصحيح يعتمد على المهمة، لذلك لا تعتمد على رقم واحد دائمًا.
ما هو Overfitting ولماذا يجب أن تعرفه؟
الإفراط في التخصيص أو Overfitting يحدث عندما ينجح النموذج جدًا مع بيانات التدريب، لكنه يفشل مع أمثلة جديدة. كأنه حفظ الإجابات بدل أن يفهم النمط العام.
توضح Google أن النموذج الذي يعمل جيدًا على التدريب لكنه يعمل بشكل سيئ مع بيانات جديدة غير مرئية يعد نموذجًا يعاني من overfitting، بينما النموذج الجيد يجب أن يعمم بشكل مفيد على أمثلة جديدة.
من أسباب المشكلة:
- بيانات تدريب قليلة جدًا أو غير ممثلة للواقع.
- نموذج معقد أكثر من اللازم مقارنة بحجم البيانات.
- وجود تكرار بين بيانات التدريب وبيانات الاختبار.
- اختيار ميزات غير مفيدة أو تسرب معلومات من الاختبار إلى التدريب.
أخطاء شائعة عند بناء أول نموذج
- البدء بالنموذج قبل فحص البيانات: البيانات غير النظيفة تضعف أي نموذج.
- اختبار النموذج على بيانات التدريب نفسها: يعطيك انطباعًا أفضل من الواقع.
- إضافة أعمدة بلا معنى: ليست كل البيانات مفيدة كميزات.
- توقع نتيجة دقيقة من بيانات قليلة: النموذج البسيط لا يعوض نقص البيانات.
- نسيان تعريف هدف المشروع: يجب أن تعرف هل تريد توقع رقم أم تصنيف فئة.
- الثقة في الرقم دون مراجعة: يجب النظر إلى الأمثلة والأخطاء والسياق، وليس مقياسًا واحدًا فقط.
ما الذي تتعلمه بعد هذا الدرس؟
بعد فهم فكرة النموذج، لا تنتقل مباشرة إلى التعلم العميق. ابدأ بمشاريع تعلم آلة صغيرة وواضحة، مثل:
- توقع درجة تقريبية من ساعات الدراسة.
- توقع مبيعات بسيطة من بيانات سابقة.
- تصنيف رسائل قصيرة إلى فئات محددة.
- التعرف على أعمدة مؤثرة في ملف بيانات جدولي.
ثم انتقل إلى دروس مشاريع الذكاء الاصطناعي ببايثون لتطبيق ما تعلمته، وارجع إلى صفحة تعلم الآلة ببايثون لمتابعة المسار.
ملخص الدرس
- نموذج تعلم الآلة يتعلم نمطًا من أمثلة سابقة ثم يستخدمه للتوقع أو التصنيف.
- Features هي المدخلات، وTarget هو النتيجة التي تريد توقعها.
- بيانات التدريب تستخدم للتعلم، وبيانات الاختبار تستخدم لفحص الأداء على أمثلة جديدة.
fit()تدرب النموذج، وpredict()تستخدمه لتوقع نتائج جديدة.- التقييم مهم لأن النموذج قد ينجح على التدريب ويفشل مع بيانات جديدة.
- ابدأ بنموذج بسيط وبيانات مفهومة قبل الانتقال إلى مشاريع أكبر.
{alertSuccess} نصيحة للمبتدئ: لا تقاس جودة أول مشروع AI بحجم النموذج، بل بقدرتك على شرح البيانات والمدخلات والهدف وطريقة التقييم والنتيجة.
مصادر موثوقة للتوسع
- Getting Started — scikit-learn
- Supervised Learning — scikit-learn
- LinearRegression — scikit-learn
- Metrics and scoring — scikit-learn
- Dividing the original dataset — Google for Developers
- Overfitting — Google for Developers
أسئلة شائعة
هل نموذج تعلم الآلة هو نفسه الذكاء الاصطناعي؟
نموذج تعلم الآلة هو جزء من الذكاء الاصطناعي. الذكاء الاصطناعي مفهوم أوسع يشمل أنظمة قائمة على قواعد، وتعلم الآلة، والتعلم العميق، وأساليب أخرى.
هل أحتاج إلى بيانات كثيرة لبناء أول نموذج؟
للتعلم يمكنك البدء ببيانات صغيرة ومصطنعة لفهم الخطوات. لكن في المشاريع الحقيقية، تحتاج بيانات مناسبة وممثلة للمشكلة حتى تكون النتائج موثوقة.
ما الفرق بين fit وpredict في scikit-learn؟
تستخدم fit لتدريب النموذج على بيانات التدريب، ثم تستخدم predict لطلب توقعات من بيانات جديدة بنفس شكل المدخلات التي تدرب عليها النموذج.
هل يمكن أن ينجح النموذج في التدريب ويفشل في الواقع؟
نعم. يحدث ذلك عند الإفراط في التخصيص أو عندما لا تمثل بيانات التدريب الحالات الجديدة. لهذا نستخدم بيانات اختبار منفصلة ونراجع الأداء.
ما أفضل مشروع تعلم آلة للمبتدئ؟
ابدأ بتوقع قيمة بسيطة من بيانات جدولية، مثل الدرجة أو المبيعات، أو بتصنيف نصوص قصيرة إلى فئات قليلة. اختر مشروعًا تعرف معنى أعمدته ونتائجه.
لماذا Python هي أفضل لغة لتعلم الذكاء الاصطناعي للمبتدئين؟ 7 أسباب عملية
عندما يبدأ شخص طريقه في الذكاء الاصطناعي، يسمع أسماء لغات كثيرة: Python وJava وC++ وR وJavaScript. لكن عندما يكون الهدف هو التعلم العملي للمبتدئ، تظهر Python غالبًا كأفضل نقطة بداية؛ ليس لأنها اللغة الوحيدة القادرة على بناء تطبيقات AI، بل لأنها تقلل التعقيد في أول الطريق وتوفر أدوات قوية للنمو خطوة بخطوة.
في هذا الدرس من بايثون العرب ستفهم لماذا اختار ملايين المتعلمين Python لمسار الذكاء الاصطناعي، وما الذي يجب أن تتعلمه منها أولًا، وما الذي لا تحتاج إلى تعلمه في البداية، وكيف تنتقل من كتابة كود بسيط إلى أول مشروع تحليل بيانات أو تعلم آلة.
{alertInfo} الخلاصة قبل التفاصيل: اختر Python لأنك تستطيع البدء بها بسرعة، ثم استخدام نفس اللغة لاحقًا لتحليل البيانات، وتعلم الآلة، والشبكات العصبية، وبناء أدوات ومشاريع فعلية.
{getToc} $title={محتوى المقال}
هل Python هي اللغة الوحيدة للذكاء الاصطناعي؟
لا. يمكن بناء أنظمة ذكاء اصطناعي بلغات عديدة، ولكل لغة نقاط قوة في سياقات مختلفة. بعض الأنظمة التي تحتاج أداءً عاليًا جدًا أو تكاملًا مع أنظمة كبيرة قد تستخدم C++ أو Java أو لغات أخرى. لكن هذا لا يجعلها الخيار الأفضل للمبتدئ.
اختيار لغة البداية لا يعتمد على قدرتها فقط، بل على ثلاثة أسئلة عملية:
- هل أستطيع قراءة الكود وكتابته دون تشتيت؟
- هل توجد مكتبات جاهزة للمجال الذي أريد تعلمه؟
- هل يمكنني الانتقال من الأمثلة الصغيرة إلى مشاريع حقيقية دون تغيير اللغة؟
Python تجيب عن هذه الأسئلة بشكل جيد جدًا بالنسبة للمبتدئ. موقع Python الرسمي يصفها بأنها سهلة التعلم للمبتدئين وفعالة في إنجاز العمل والتكامل مع الأنظمة الأخرى، بينما توثق مكتباتها الأساسية مسارات واسعة من البيانات إلى تعلم الآلة.
السبب الأول: Python سهلة القراءة والكتابة
في بداية تعلم الذكاء الاصطناعي لا تريد أن تضيع طاقتك في الأقواس المعقدة أو التعريفات الطويلة أو تفاصيل لا تخدم الفكرة التي تتعلمها. Python تتميز بصياغة واضحة نسبيًا، لذلك تستطيع التركيز على المنطق والبيانات بدل شكل الكود.
قارن الفكرة التالية: حساب متوسط درجات.
scores = [65, 78, 84, 91]
average = sum(scores) / len(scores)
print("متوسط الدرجات:", average)
الكود قصير، ويمكن للمبتدئ فهمه بعد تعلم القوائم والدوال والعمليات الحسابية. هذه البساطة مهمة لأن معظم مشاريع الذكاء الاصطناعي تبدأ بأفكار عادية: قراءة بيانات، حسابات، شروط، ودوال.
السبب الثاني: Python ترافقك في كل مراحل مسار AI
الميزة الأكبر ليست أن Python سهلة فقط، بل أنها لا تتوقف فائدتها عند الأساسيات. يمكنك استخدامها في كل مرحلة تقريبًا من مسار تعلم الذكاء الاصطناعي:
| المرحلة | ماذا تفعل باستخدام Python؟ | أدوات شائعة |
|---|---|---|
| أساسيات البرمجة | المتغيرات، الشروط، الحلقات، الدوال، الملفات. | Python Standard Library |
| تحليل البيانات | قراءة CSV، تنظيف الجداول، استخراج ملخصات وإحصاءات. | pandas وNumPy |
| التمثيل البصري | رسم المخططات لفهم الاتجاهات والقيم الشاذة. | Matplotlib |
| تعلم الآلة | تدريب نماذج للتوقع أو التصنيف وتقييمها. | scikit-learn |
| التعلم العميق | العمل مع شبكات عصبية ونماذج صور أو نصوص أو صوت. | TensorFlow أو PyTorch |
| المشاريع | تحويل النموذج أو التحليل إلى أداة أو واجهة أو خدمة بسيطة. | مكتبات متعددة حسب المشروع |
وجود هذا المسار المتدرج يجعلك لا تحتاج إلى تغيير لغتك كلما تقدمت. تبدأ ببرنامج بسيط، ثم تقرأ جدولًا، ثم تدرب نموذجًا، وكل ذلك في البيئة نفسها تقريبًا.
السبب الثالث: مكتبات Python تجعل التعلم عمليًا
الذكاء الاصطناعي ليس مجرد تعريفات. تحتاج إلى أدوات تساعدك على قراءة البيانات وتجهيزها وتدريب نموذج وفهم النتيجة. هنا تظهر قوة منظومة مكتبات Python.
NumPy: البداية مع المصفوفات والأرقام
NumPy توفر بنية مصفوفات متعددة الأبعاد وعمليات عددية فعالة، وهي فكرة مهمة لأن كثيرًا من البيانات في الذكاء الاصطناعي تمثل في النهاية كأرقام أو مصفوفات. لا تحتاج إلى التعمق فيها منذ اليوم الأول، لكن فهم الأساسيات سيفيدك مع الوقت.
import numpy as np
hours = np.array([2, 3, 4, 5])
print(hours.mean())
pandas: التعامل مع الجداول والملفات
pandas مناسبة عندما تتعامل مع بيانات جدولية مثل ملفات CSV وExcel وقواعد البيانات. يمكنك من خلالها قراءة البيانات إلى DataFrame، ثم فحصها وتنظيفها وتحديد الأعمدة المهمة قبل أي نموذج تعلم آلة.
import pandas as pd
data = pd.read_csv("students.csv")
print(data.head())
scikit-learn: أول خطوة منظمة في تعلم الآلة
تساعد scikit-learn على بناء نماذج تعلم آلة تقليدية بطريقة موحدة نسبيًا: جهز البيانات، درّب النموذج، اختبر النتيجة، ثم قيّم الأداء. هذا يجعلها مناسبة جدًا للمبتدئ بعد فهم أساسيات Python والبيانات.
from sklearn.linear_model import LinearRegression
model = LinearRegression()
model.fit(X_train, y_train)
السبب الرابع: مجتمع كبير وأمثلة كثيرة
عند التعلم ستواجه أخطاء وأسئلة وأفكارًا جديدة. وجود مجتمع كبير حول Python لا يلغي الحاجة إلى التفكير، لكنه يجعل الوصول إلى التوثيق والأمثلة والمناقشات التعليمية أسهل.
الأهم من كثرة المحتوى هو أن تبدأ بالمصادر الصحيحة: التوثيق الرسمي للمكتبة، أمثلة صغيرة قابلة للتشغيل، ثم مشروع بسيط. لا تبدأ من فيديو طويل ينسخ مشروعًا ضخمًا دون أن تفهم البيانات أو كل سطر.
السبب الخامس: Python مناسبة للبيانات قبل النماذج
كثير من المبتدئين يعتقد أن الذكاء الاصطناعي يبدأ من النموذج. لكن في الواقع، جزء كبير من العمل يكون قبل التدريب: قراءة ملف، تصحيح نوع عمود، معالجة قيم ناقصة، إزالة تكرار، أو اختيار أعمدة مفيدة.
Python، مع pandas وNumPy، تجعل هذه المرحلة مفهومة وعملية. وهذا أحد الأسباب المهمة لاختيارها: فهي لا تجبرك على القفز إلى “النموذج الذكي” قبل أن تفهم البيانات التي سيتعلم منها.
{alertWarning} خطأ شائع: تثبيت مكتبة تعلم عميق وبدء مشروع ضخم قبل تعلم Python وCSV وpandas. الطريق الأسرع غالبًا هو الطريق المتدرج، لا القفز فوق الأساسيات.
هل تحتاج إلى رياضيات قوية قبل البدء؟
لا تحتاج إلى إتقان الجبر الخطي أو الاحتمالات قبل كتابة أول كود في AI. تحتاج في البداية إلى فهم منظم للأرقام والجداول والمتوسطات، ثم تتوسع تدريجيًا عندما تبدأ دراسة النماذج بعمق أكبر.
في البداية، ركز على هذه المهارات:
- المتغيرات وأنواع البيانات والقوائم والقواميس.
- الشروط والحلقات والدوال.
- قراءة ملفات CSV وكتابة كود منظم.
- المتوسط والقيم الأكبر والأصغر والرسوم البسيطة.
- فكرة التدريب والاختبار بدل حفظ تفاصيل الخوارزميات.
بعد ذلك ستصبح الرياضيات أكثر معنى؛ لأنك ستراها مرتبطة بمشكلة أو نموذج تعمل عليه فعلًا.
ما الذي يجب أن تتعلمه من Python قبل AI؟
لا تنتظر حتى تصبح خبير Python في كل شيء، لكن يجب أن تكون مرتاحًا مع أساسيات محددة. هذا هو الحد العملي الذي أنصح به قبل الدخول الجدي في البيانات وتعلم الآلة:
| المهارة | لماذا تحتاجها في AI؟ | مثال عملي |
|---|---|---|
| المتغيرات وأنواع البيانات | لتخزين القيم والنتائج والإعدادات. | حفظ عدد الصفوف أو نتيجة النموذج. |
| القوائم والقواميس | للتعامل مع مجموعات من القيم والبيانات المنظمة. | قائمة درجات أو قاموس إعدادات. |
| الشروط | لفحص النتائج ومعالجة الحالات المختلفة. | إظهار تنبيه عند وجود قيم ناقصة. |
| الحلقات | لفهم التكرار ومعالجة البيانات البسيطة. | المرور على قائمة سجلات. |
| الدوال | لتقسيم المشروع إلى خطوات سهلة الاختبار. | دالة لتنظيف البيانات أو تقييم النتيجة. |
| الملفات وpip | لقراءة البيانات وتثبيت المكتبات. | قراءة CSV وتثبيت pandas. |
مثال: من Python العادية إلى أول فكرة AI
لنفترض أنك بدأت ببرنامج عادي يحسب متوسط ساعات الدراسة:
study_hours = [2, 3, 4, 5]
average = sum(study_hours) / len(study_hours)
print("متوسط ساعات الدراسة:", average)
ثم تتقدم خطوة إلى تحليل جدول درجات وساعات دراسة باستخدام pandas. بعد ذلك، يمكنك تدريب نموذج صغير يحاول توقع درجة تقريبية من عدد الساعات. هذا هو الانتقال الطبيعي: Python الأساسية → بيانات → تحليل → نموذج.
متى لا تكون Python الخيار الوحيد أو الأفضل؟
من المهم أن تكون واقعيًا: Python ممتازة للتعلم والتحليل وبناء النماذج والعديد من المشاريع، لكنها ليست الحل الوحيد لكل جزء من أي نظام AI.
- قد تستخدم فرق كبيرة لغة أخرى في أجزاء تحتاج أداءً منخفض المستوى أو زمن استجابة شديد الحساسية.
- قد تكون لغة مختلفة مطلوبة إذا كنت تعمل داخل نظام قائم أصلًا على تقنيات محددة.
- قد تستخدم Python للتدريب والتحليل، ثم تربط النتيجة بتطبيق ويب أو هاتف بلغة أخرى.
لكن هذه اعتبارات تأتي لاحقًا. بالنسبة للمبتدئ، اختيار Python لا يغلق الأبواب؛ بل يعطيك أساسًا عمليًا يمكنك البناء عليه.
خطة بداية عملية لتعلم AI باستخدام Python
- الأسبوع الأول: راجع أساسيات Python: المتغيرات، الشروط، الحلقات، القوائم، الدوال.
- الأسبوع الثاني: تعلم التعامل مع الملفات وCSV، ثم اقرأ بيانات بسيطة باستخدام pandas.
- الأسبوع الثالث: نظف البيانات، احسب المتوسطات، واستخدم
groupbyوdescribe. - الأسبوع الرابع: ابدأ أول نموذج scikit-learn بسيط للتوقع أو التصنيف.
- بعد ذلك: نفذ مشروعًا صغيرًا، ثم انتقل تدريجيًا إلى موضوع أكثر تخصصًا.
أخطاء شائعة عند اختيار Python لمسار AI
- توقع أن Python وحدها تصنع نموذجًا ذكيًا: اللغة أداة، بينما البيانات والمنهج والتجربة جزء أساسي من النتيجة.
- تثبيت مكتبات كثيرة دفعة واحدة: ابدأ بما تحتاجه في الدرس الحالي فقط.
- حفظ أوامر pandas وscikit-learn دون فهم: افهم السؤال والبيانات قبل حفظ الدوال.
- البدء بنموذج ضخم: ابدأ بملف CSV صغير ومشروع يمكن شرحه في دقائق.
- الاعتقاد أن مسار AI يلغي أساسيات Python: كلما كانت أساسياتك أقوى، أصبح تقدمك أسرع وأقل إرباكًا.
{alertSuccess} نصيحة مهمة: لا تسأل: ما أكبر مكتبة AI يجب أن أتعلمها؟ اسأل: ما أصغر مشروع أستطيع فهم بياناته وبناؤه وتحسينه باستخدام Python؟
ماذا بعد هذا الدرس؟
بعد أن تعرفت على سبب اختيار Python لمسار الذكاء الاصطناعي، لا تنتقل مباشرة إلى الشبكات العصبية. ابدأ بالمرحلة التي تجعل كل شيء بعدها أسهل: تحليل البيانات.
ارجع إلى خريطة طريق تعلم الذكاء الاصطناعي ببايثون، ثم تابع دروس تحليل البيانات ببايثون لتتعلم قراءة ملفات CSV وتنظيفها وفهم الأعمدة والصفوف قبل تدريب أول نموذج.
الخلاصة
- Python ليست اللغة الوحيدة للذكاء الاصطناعي، لكنها من أفضل لغات البداية للمبتدئ.
- بساطة الصياغة تساعدك على التركيز في المنطق والبيانات بدل التعقيد البرمجي.
- مكتبات مثل NumPy وpandas وscikit-learn تجعل الانتقال من البيانات إلى أول نموذج عمليًا.
- لا تحتاج إلى تعلم كل Python أو كل الرياضيات قبل البدء، لكنك تحتاج إلى أساسيات ثابتة.
- أفضل طريق هو التدرج: Python ثم البيانات ثم التحليل ثم تعلم الآلة ثم المشاريع.
مصادر موثوقة للتوسع
- About Python — Python.org
- NumPy: the absolute basics for beginners
- Getting started — pandas documentation
- How do I read and write tabular data? — pandas
- Getting Started — scikit-learn
أسئلة شائعة
هل يجب أن أتعلم Python بالكامل قبل الذكاء الاصطناعي؟
لا. تحتاج إلى أساسيات عملية مثل المتغيرات والقوائم والشروط والدوال والملفات وpip. بعد ذلك يمكنك تعلم البيانات وAI بالتوازي مع تطوير مهاراتك في Python.
هل Python مناسبة فقط للمبتدئين؟
لا. سهولة البداية لا تعني أنها لغة محدودة. يمكن استخدامها في تحليل البيانات وتعلم الآلة والتعلم العميق والبحث العلمي والأتمتة وبناء أدوات ومشاريع متعددة.
ما أول مكتبة Python أتعلمها للذكاء الاصطناعي؟
بعد أساسيات اللغة، ابدأ غالبًا بـ pandas لفهم البيانات الجدولية وملفات CSV، ثم NumPy للمصفوفات والحسابات، وبعدها scikit-learn لتعلم الآلة التقليدي.
هل أحتاج إلى TensorFlow أو PyTorch من البداية؟
لا. هذه أدوات مهمة للتعلم العميق، لكنها ليست نقطة البداية الأفضل غالبًا. ابدأ بتحليل البيانات ونماذج scikit-learn البسيطة أولًا.
هل أستطيع بناء مشروع AI صغير ببايثون كمبتدئ؟
نعم. ابدأ بمشروع بسيط مثل توقع درجة تقريبية، تصنيف نصوص قصيرة، تحليل ملف مبيعات CSV، أو توصية مبسطة قائمة على قواعد واضحة.





































































































