شرح try و except في Python: كيفية التعامل مع الاستثناءات باحتراف

دقائق القراءة: 7

ما المقصود بالاستثناءات في Python؟

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

توفر Python آلية واضحة لمعالجة هذه المشكلات عبر الكتل try وexcept، بحيث يمكن للتطبيق الاستمرار أو إنهاء التنفيذ بصورة منظمة بدلاً من الانهيار المفاجئ.

شرح آلية try و except في Python لمعالجة الأخطاء والاستثناءات أثناء تنفيذ البرامج

صيغة try و except في Python

قبل استعراض الأمثلة العملية، من المهم فهم البنية العامة لاستخدام try وexcept في Python:

try:
    # There can be errors in this block
except <error type>:
    # Do this to handle exception;
    # executed if the try block throws an error
else:
    # Do this if try block executes successfully without errors
finally:
    # This block is always executed

وظيفة كل كتلة برمجية

  • try: تحتوي على الكود الذي قد يُنتج خطأ أثناء التنفيذ.
  • except: تُنفّذ عند وقوع استثناء محدد داخل كتلة try.
  • else: تُنفّذ فقط إذا انتهت try بنجاح ومن دون أي خطأ.
  • finally: تُنفّذ دائماً سواء حدث خطأ أم لا، وتُستخدم غالباً لتحرير الموارد مثل إغلاق الملفات أو الاتصالات.

من الأفضل دائماً تحديد نوع الخطأ داخل except بدقة، لأن استخدام except بشكل عام من دون تحديد الاستثناء قد يُخفي أخطاء منطقية مهمة ويصعّب عملية التصحيح لاحقاً.

هل يمكن معالجة أكثر من خطأ؟

نعم، في كثير من السيناريوهات قد يحتوي الكود داخل try على أكثر من عملية قد تفشل. على سبيل المثال، قد تتعامل في الوقت نفسه مع قائمة list، وقاموس dictionary، وملف خارجي. في هذه الحالة يمكنك إضافة أكثر من كتلة except، بحيث تعالج كل نوع خطأ بالطريقة المناسبة له.

كيفية التعامل مع ZeroDivisionError في Python

من أكثر الأخطاء شيوعاً في Python محاولة القسمة على الصفر. لنأخذ الدالة التالية:

def divide(num, div):
    return num/div

عند تمرير قيم صحيحة، ستعمل الدالة كما هو متوقع:

res = divide(100, 8)
print(res) # Output 12.5

res = divide(568, 64)
print(res) # Output 8.875

لكن عند محاولة القسمة على 0 سيتوقف البرنامج بسبب ZeroDivisionError:

divide(27, 0)
# Output
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-19-932ea024ce43> in <module>()
----> 1 divide(27, 0)

<ipython-input-1-c98670fd7a12> in divide(num, div)
      1 def divide(num,div):
----> 2     return num/div

ZeroDivisionError: division by zero

الحل هو وضع الاستدعاء داخل try ومعالجة الخطأ داخل except:

try:
    res = divide(num, div)
    print(res)
except ZeroDivisionError:
    print("You tried to divide by zero :(")

وعند إدخال قيم صحيحة سيستمر التنفيذ بشكل طبيعي:

divide(10, 2)
# Output 5.0

أما عند القسمة على الصفر فستظهر رسالة واضحة بدلاً من انهيار التطبيق:

divide(10, 0)
# Output You tried to divide by zero :(

كيفية التعامل مع TypeError في Python

يحدث TypeError عندما تُجرى عملية على نوع بيانات غير مناسب. تأمل المثال التالي:

def add_10(num):
    return num + 10

إذا مرّرت رقماً، فسيكون الناتج صحيحاً:

result = add_10(89)
print(result) #Output 99

لكن إذا مرّرت نصاً مثل "five" بدلاً من قيمة رقمية:

add_10("five")

فسيظهر الخطأ التالي:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-15-9844e949c84e> in <module>()
----> 1 add_10("five")

<ipython-input-13-2e506d74d919> in add_10(num)
      1 def add_10(num):
----> 2     return num + 10

TypeError: can only concatenate str (not "int") to str

يمكنك معالجة هذا السيناريو على النحو التالي:

my_num = "five"

try:
    result = add_10(my_num)
    print(result)
except TypeError:
    print("The argument `num` should be a number")

وبذلك يحصل المستخدم على رسالة مفهومة توضّح أن نوع البيانات المُدخل غير مناسب:

The argument `num` should be a number

كيفية التعامل مع IndexError في Python

يظهر IndexError عند محاولة الوصول إلى عنصر داخل list أو أي كائن قابل للفهرسة باستخدام موقع غير موجود.

لنفترض أن لدينا القائمة التالية:

my_list = ["Python", "C", "C++", "JavaScript"]
print(my_list[2]) #Output C++

في هذا المثال، الفهرس 2 صالح، لذلك ستتم طباعة C++. لكن عند محاولة الوصول إلى فهرس خارج النطاق:

print(my_list[4])

سيظهر الخطأ:

---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-7-437bc6501dea> in <module>()
      1 my_list = ["Python", "C", "C++", "JavaScript"]
----> 2 print(my_list[4])

IndexError: list index out of range

يمكن معالجة الخطأ باستخدام try وexcept بالشكل التالي:

search_idx = 3

try:
    print(my_list[search_idx])
except IndexError:
    print("Sorry, the list index is out of range")

إذا كانت قيمة search_idx صحيحة، فسيُطبع العنصر المطلوب:

JavaScript

أما إذا كانت خارج المجال المسموح:

search_idx = 4

try:
    print(my_list[search_idx])
except IndexError:
    print("Sorry, the list index is out of range")

فستظهر رسالة مخصصة بدلاً من رسالة الخطأ الطويلة:

Sorry, the list index is out of range

كيفية التعامل مع KeyError في Python

عند العمل مع dictionary في Python، يظهر KeyError عندما تحاول الوصول إلى مفتاح غير موجود.

my_dict = {
    "key1": "value1",
    "key2": "value2",
    "key3": "value3"
}

search_key = "non-existent key"
print(my_dict[search_key])

بما أن المفتاح المطلوب غير موجود داخل القاموس، فستحصل على الخطأ التالي:

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-2-2a61d404be04> in <module>()
      1 my_dict = {"key1": "value1", "key2": "value2", "key3": "value3"}
      2 search_key = "non-existent key"
----> 3 my_dict[search_key]

KeyError: 'non-existent key'

يمكن التعامل معه بسهولة كالتالي:

try:
    print(my_dict[search_key])
except KeyError:
    print("Sorry, that's not a valid key!")
Sorry, that's not a valid key!

وإذا رغبت في عرض اسم المفتاح غير الصحيح لمساعدة المستخدم على اكتشاف الخطأ الإملائي أو المنطقي، فاستخدم الصيغة التالية:

try:
    print(my_dict[search_key])
except KeyError as error_msg:
    print(f"Sorry, {error_msg} is not a valid key!")

وسيكون الناتج:

Sorry, 'non-existent key' is not a valid key!

كيفية التعامل مع FileNotFoundError في Python

يُعد FileNotFoundError من الأخطاء الشائعة عند التعامل مع الملفات، ويحدث عندما تحاول فتح ملف غير موجود في المسار المحدد.

في المثال التالي نحاول فتح ملف وقراءة محتواه:

my_file = open("/content/sample_data/my_file.txt")
contents = my_file.read()
print(contents)

إذا لم يكن الملف موجوداً، فستظهر الرسالة التالية:

---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
<ipython-input-4-4873cac1b11a> in <module>()
----> 1 my_file = open("my_file.txt")

FileNotFoundError: [Errno 2] No such file or directory: 'my_file.txt'

الأسلوب الاحترافي هنا هو الجمع بين try وexcept وelse وfinally:

try:
    my_file = open("/content/sample_data/my_file.txt")
except FileNotFoundError:
    print(f"Sorry, the file does not exist")
else:
    contents = my_file.read()
    print(contents)
finally:
    my_file.close()

هذا النمط يوضح عدة نقاط مهمة:

  • داخل try نحاول فتح الملف.
  • داخل except نعالج غياب الملف برسالة واضحة.
  • داخل else نقرأ المحتوى فقط إذا نجح فتح الملف.
  • داخل finally نغلق الملف لتحرير الموارد.

عند غياب الملف، سينتهي البرنامج بسلاسة مع الرسالة التالية:

Sorry, the file does not exist

وفي حال كان الملف موجوداً، سيتم تنفيذ else وقراءة محتواه كما هو متوقع.

لقطة توضح وجود ملف my_file.txt في المسار المحدد قبل قراءته باستخدام Pythonمحتوى ملف my_file.txt المستخدم في مثال قراءة الملفات ومعالجة FileNotFoundError في Pythonنتيجة تشغيل كود Python بعد العثور على الملف وقراءة محتواه بنجاح

أفضل ممارسات استخدام try و except في Python

1) حدّد نوع الاستثناء بدقة

استخدام except Exception أو except بشكل عام قد يكون مناسباً في حالات محدودة، لكنه ليس الخيار الأفضل في أغلب المشاريع. كلما حدّدت نوع الخطأ بدقة، كان الكود أوضح وأسهل في التتبع.

2) اجعل كتلة try صغيرة قدر الإمكان

من الأفضل ألا تضع كمية كبيرة من التعليمات داخل try. ضع فقط الأسطر التي تتوقع فعلاً أنها قد تُسبب الاستثناء، حتى تعرف مصدر الخطأ بسرعة.

3) استخدم else للعمليات التابعة للنجاح

إذا كنت تريد تنفيذ خطوة إضافية فقط بعد نجاح العملية الأساسية، فإن else تمنحك تنظيماً أفضل للكود وتفصل بين منطق التنفيذ ومنطق المعالجة.

4) لا تتجاهل الأخطاء بصمت

معالجة الاستثناء لا تعني إخفاءه تماماً. ينبغي إظهار رسالة مفيدة أو تسجيل الخطأ في السجلات logs إذا كان التطبيق أكبر حجماً.

5) استخدم finally عند الحاجة إلى تنظيف الموارد

إذا كنت تتعامل مع ملفات، أو اتصالات شبكية، أو جلسات قاعدة بيانات، فإن finally مهمة لضمان الإغلاق السليم مهما كانت نتيجة التنفيذ.

ملخص سريع لأشهر الاستثناءات المذكورة

نوع الاستثناء متى يحدث؟ مثال شائع
ZeroDivisionError عند القسمة على صفر 10 / 0
TypeError عند استخدام نوع بيانات غير مناسب جمع str مع int
IndexError عند الوصول إلى فهرس غير موجود my_list[10]
KeyError عند طلب مفتاح غير موجود في dictionary my_dict["x"]
FileNotFoundError عند محاولة فتح ملف غير موجود open("missing.txt")

لماذا يُعد التعامل مع الاستثناءات مهماً في تطوير البرمجيات؟

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

الخلاصة التقنية

استخدام try وexcept في Python يمنحك طريقة عملية وآمنة للتعامل مع أخطاء التشغيل المتوقعة من دون إفساد تدفق البرنامج. والأفضل دائماً هو معالجة الاستثناءات المحددة مثل ZeroDivisionError وTypeError وFileNotFoundError بدلاً من التقاط كل شيء بشكل عام. كلما كان التعامل مع الأخطاء أكثر دقة وتنظيماً، كان الكود أسهل في الصيانة وأكثر جاهزية للاستخدام في التطبيقات الحقيقية.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *