الأخطاء شائعة جدًا في البرمجة. في بايثون، نستخدم مصطلح “استثناء” للأخطاء التي تحدث أثناء تنفيذ التعليمات البرمجية. سواء كان ذلك خطأ مطبعيًا أو تشتيتًا في التعليمات البرمجية أو لأن التعليمات البرمجية وصلت إلى حالة غير صالحة، فهذا أمر لا مفر منه: فكلما كتبت برامج بايثون أكثر، كلما اضطررت إلى مواجهة استثناءات بايثون والتعامل معها.
قد تبدو معالجة استثناءات بايثون صعبة أو غامضة في البداية، لكنها أساسية لمنحنى تعلم بايثون. من خلال توقع المشكلات المحتملة ومعالجة الاستثناءات بشكل صحيح، يمكننا التحايل على المشكلة ومنع تعطل الكود – مع الحفاظ على رضا المستخدمين وإعلامهم. إن القدرة على معالجة استثناءات بايثون بشكل صحيح ستجعل الكود الخاص بك أكثر موثوقية وفهمًا على المدى الطويل.
في هذه المقالة، سنستكشف ماهية استثناءات بايثون، وكيف يمكننا التعامل معها، وكيفية الاستفادة منها من خلال إنشاء استثناءات خاصة بنا واستخدامها للحفاظ على تنظيم الكود الخاص بنا. نفترض أنك تمتلك بالفعل فهمًا أساسيًا لبايثون – إذا لم تكن كذلك، فنوصيك بالاطلاع إلى هذه المقالة حول مصطلحات بايثون للمبتدئين. وإلا، فلنبدأ على الفور!
ما هو استثناء بايثون؟
في بايثون، نقول إن الاستثناءات تنشأ عند تنفيذ عملية غير صالحة، مثل محاولة جمع نص وأرقام معًا، أو قسمة رقم على صفر. كلما حدث هذا، سيعرض بايثون رسالة الاستثناء وسيتوقف تنفيذ الكود عند هذه النقطة.
على سبيل المثال، ضع في اعتبارك البرنامج النصي code.py، الذي يحتوي على السطر الفردي أدناه:
int('a')
نتوقع بالفعل أن يثير هذا الكود استثناءً في بايثون – ففي النهاية، لا يمكننا تحويل السلسلة "a"
إلى عدد صحيح. إذا حاولت تنفيذ البرنامج النصي، فسترى رسالة استثناء مشابهة لما يلي:
Traceback (most recent call last):
File "code.py", line 1, in <module>
int('a')
ValueError: invalid literal for int() with base 10: 'a'
</module>
قد تبدو رسالة الاستثناء مخيفة، لكنها في الواقع مفيدة للغاية. فهي تخبرنا بما يلي:
- ما هو نوع استثناء بايثون الذي تم إثارته (في هذه الحالة،
ValueError
). - أي سطر أدى إلى حدوث الاستثناء (السطر 1 من البرنامج النصي code.py).
- لماذا تم إثارة الاستثناء. يتم نقل هذه المعلومات من خلال رسالة استثناء بايثون
invalid literal for int() with base 10: 'a'
، مما يعني أنه لا يمكن تحويل السلسلة'a'
إلى عدد صحيح.
أنواع الاستثناءات الشائعة في بايثون
كما قد تتوقع، هناك العديد من أنواع استثناءات بايثون. دعنا نلقي نظرة على بعض استثناءات بايثون التي ستواجهها كثيرًا، إلى جانب عينات التعليمات البرمجية التي ستثيرها.
خطأ في بناء الجملة SyntaxError
يظهر هذا الاستثناء عند كتابة كود بايثون باستخدام بناء جملة خاطئ. الأسباب الشائعة لهذا هي نسيان استخدام النقطتين في عبارات if …
: أو ترك أقواس/أقواس متقاطعة غير متطابقة في الكود. انظر هذه الأمثلة:
# Missing a colon at the end of the if statement
value = 10
if value < 5 # SyntaxError: invalid syntax
# Different types of braces/parentheses on each side
values = [2, 5, 4, 9, 10) # SyntaxError: closing parenthesis ')' does not match opening parenthesis '['
إذا واجهت هذا الاستثناء في بايثون، فإن الحل هو البحث عن أخطاء نحوية في الكود الخاص بك وإصلاحها.
خطأ في النوع TypeError
يشير هذا الاستثناء إلى أنك حاولت تنفيذ عملية باستخدام نوع بيانات خاطئ. وكما يوضح المثال التالي، لا يمكنك “إضافة” رقم وسلسلة:
value = 5
name = 'John'
print(value + name) # TypeError: unsupported operand type(s) for +: 'int' and 'str
غالبًا ما يظهر هذا الاستثناء عند محاولة تنفيذ عملية بقيمة None – وهو نوع لا يقبل أي عمليات.
خطأ القيمة ValueError
يظهر هذا الاستثناء عند تقديم نوع البيانات الصحيح لعملية ما ولكن بقيمة غير صالحة. على سبيل المثال، لا يمكننا حساب لوغاريتم رقم سالب:
import math
math.log(-1) # ValueError: math domain error
ZeroDivisionError
كما يوحي الاسم، يحدث هذا الاستثناء عندما تحاول قسمة رقم على الصفر:
2 / 0 # ZeroDivisionError: division by zero
يمكنك التفكير فيه باعتباره إصدارًا “متخصصًا” من ValueError.
خطأ في الفهرس IndexError
يحدث هذا الاستثناء عندما تقدم فهرسًا غير صالح لتسلسل – على سبيل المثال، إذا كانت القائمة تحتوي على 5 عناصر فقط ولكنك تحاول الحصول على العنصر عند الفهرس 100:
values = [1, 5, 11, 17, 22]
print(values[0])
print(values[2])
print(values[100]) # IndexError: list index out of range
خطأ في المفتاح KeyError
هذا هو نظير IndexError
في القاموس. يتم رفع الاستثناء عند تقديم مفتاح غير موجود لقاموس بايثون:
grades = {'John': 10, 'Mary': 8, 'Steve': 9}
print(grades['John'])
print(grades['Steve'])
print(grades['Mark']) # KeyError: 'Mark'
كيفية التعامل مع الاستثناءات في بايثون
بحلول هذا الوقت، ربما تكون لديك فكرة جيدة عن كيفية عمل استثناءات بايثون وما تعنيه. ولكن كيف نتعامل معها؟
بالتأكيد، بعض الاستثناءات واضحة: إذا واجهت خطأ في بناء الجملة، فإن الحل هو ببساطة إصلاح بناء الجملة في الكود. ولكن ماذا لو لم أكن أعرف المفاتيح الموجودة في القاموس؟ ماذا لو كنت بحاجة إلى حساب اللوغاريتم لرقم تم استلامه من إدخال المستخدم، والذي قد يكون أو لا يكون سلبيًا؟
أدخل كتلة try/except
. وهي كتلة تعليمات برمجية خاصة تحاول تشغيل سطر (أو بعض الأسطر) من التعليمات البرمجية التي نعلم أنها قد تثير استثناءً في بايثون. إذا تم إثارة الاستثناء بالفعل، فإنه يدخل كتلة except
، التي تتعامل مع الموقف وتمنع التعليمات البرمجية من التعطل.
فيما يلي مثال لكيفية ظهور ذلك:
import math
number = 5
print('Calculating the logarithm of', number)
try:
print(math.log(number))
except ValueError:
print('Unable to calculate logarithm of', number, 'because it is a negative number. Please provide another number.')
قم بتشغيل الكود أعلاه وقم بتغيير السطر number = 5
إلى قيمة سالبة. وكما رأينا من قبل، فإن لوغاريتم الرقم السالب غير موجود، مما يؤدي إلى حدوث خطأ ValueError
في الكود. ومع ذلك، من خلال وضع السطر math.log(number)
أسفل كتلة try:
، يمكننا توقع هذه المشكلة ومعالجتها.
كلما تم تشغيل ValueError
، ينتقل الكود ببساطة إلى كتلة except ValueError:
. في تلك الكتلة، نبلغ المستخدم بأنه يجب عليه تقديم رقم آخر للنص البرمجي. وبدلاً من التعطل، يتعامل برنامجنا الآن مع الموقف بسلاسة ويبلغ المستخدم. لقد تم تجنب الأزمة!
التعامل مع استثناءات بايثون كالمحترفين
لدينا الآن فهم أساسي لاستخدام كتل try/except
لمعالجة استثناءات بايثون. ولكن يمكن توسيع كتل التعليمات البرمجية هذه بعدة طرق، والتي سنستكشفها أدناه.
يمكنك استخدام كتل except
متعددة للتعامل مع أنواع مختلفة من الاستثناءات. في الكود أدناه، نحسب لوغاريتم x
مقسومًا على y
أثناء التعامل مع الاستثناءات المحتملة ZeroDivisionError
وValueError
داخل كتل except
الخاصة بكل منها. حاول تشغيل الكود بعد تغيير قيمة x
أو y
وسترى الاستثناء المقابل قيد المعالجة:
x = 1
y = -2
try:
value = x / y
print(math.log(value))
except ZeroDivisionError:
print('Cannot divide by zero')
except ValueError:
print('Cannot calculate log of negative value')
من الممكن أيضًا تجميع استثناءين أو أكثر في كتلة except
واحدة باستخدام الأقواس:
x = 1
y = -2
try:
value = x / y
print(math.log(value))
except (ZeroDivisionError, ValueError) as e:
print('Got an error:', e)
نظرًا لأننا لا نعرف مسبقًا ما إذا كان الكود سيؤدي إلى حدوث خطأ ZeroDivisionError
أو ValueError
، فإننا نستخدم البنية except … as e
لطباعة الرسالة الواردة في الاستثناء الذي يتم التعامل معه. على سبيل المثال، إذا تسبب الكود في حدوث خطأ ZeroDivisionError
، تصبح الرسالة المطبوعة “Got an error: division by zero
“.
خيار آخر هو استخدام except Exception
من أجل التعامل (افتراضيًا) مع أي استثناء بايثون في كتلة واحدة:
x = 1
y = -2
try:
value = x / y
print(math.log(value))
except Exception as e:
print('Got an error:', e)
لكن احذر: على الرغم من أن معالجة الاستثناءات “الشاملة” هذه قد تكون مفيدة في مواقف معينة، إلا أنها قد تجعل فهم الكود وتصحيح أخطائه أكثر صعوبة. قد ينتهي الأمر بإخفاء استثناءات لم تفكر فيها مسبقًا!
أخيرًا، هناك كتلتان اختياريتان لكتلة try/except:
- كتلة
else
، والتي يتم تنفيذها فقط إذا لم يتم إثارة أي استثناءات في كتلةtry
السابقة. - كتلة
finally
، والتي يتم تنفيذها دائمًا – بغض النظر عما إذا تم رفع استثناء بايثون أم لا.
هكذا يبدو مثالنا بعد إضافة الكتل أعلاه إليه. (مرة أخرى، غيّر قيم x
وy
لرؤية رسائل مختلفة تتم طباعتها.)
x = 1
y = -2
try:
value = x / y
log_value = math.log(value)
except ZeroDivisionError:
print('Cannot divide by zero')
except ValueError:
print('Cannot calculate log of negative value')
else:
print('Log value is', log_value)
finally:
print('Code has finished')
كما ترى، يتم حساب قيمة log_value
داخل كتلة try
. أثناء هذه العملية، سيتم التقاط أي استثناءات نتوقعها بواسطة كتلة except
المقابلة. إذا لم يتم إثارة أي استثناءات، يدخل الكود كتلة else
ويعرض قيمة log_value
، التي تم حسابها بنجاح. بعد ذلك، يتم تشغيل كتلة finally
وعرض رسالة النهاية – بغض النظر عما إذا كان الكود السابق قد أثار استثناءً أم لا.
إنشاء استثناءات خاصة بك وطرحها في بايثون
إذا كنت بحاجة إلى مزيد من المرونة في التعامل مع الأخطاء، فإن بايثون يسمح لك بتحديد أنواع استثناءات مخصصة. ما عليك سوى إنشاء فئة باسم الاستثناء المخصص الخاص بك:
class NumberTooLargeError(Exception):
pass
بعد ذلك، يمكنك رفعها باستخدام الكلمة الرئيسية raise
وستتصرف مثل أي استثناء آخر في بايثون. على سبيل المثال، يقوم الكود أدناه برفع استثناء يعرض معلومات كلما كانت قيمة x
أعلى من حد معين:
x = 10
limit = 100
if x > limit:
message = f"Value {x} is above the limit {limit}"
raise NumberTooLargeError(message)
إذا قمنا بتغيير القيمة x إلى 200، فسنحصل على رسالة الخطأ التالية:
NumberTooLargeError: Value 1000 is above the limit 200
الآن، ربما تتساءل لماذا أرغب في تعطل برنامجي؟ الإجابة تعتمد حقًا على ما تحاول تحقيقه باستخدام الكود الخاص بك.
إذا كنت تكتب كودًا قد يستخدمه أشخاص آخرون، فمن المفيد إثارة استثناءات تشير بوضوح إلى سبب عدم صلاحية عملية معينة. يمكن لأي شخص يستخدم هذا الكود أن يقرر بعد ذلك ما إذا كان يحتاج إلى تغيير شيء ما في الكود الخاص به أو ما إذا كان يريد استخدام كتلة try/except
للالتفاف على استثناءاتك المخصصة. في كل الأحوال، من المفيد دائمًا أن تحافظ على كودك واضحًا ومنظمًا!
اكتشاف المزيد من بايثون العربي
اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.