الفرق بين المراجعتين ل"Python/exceptions"

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث
ط (استبدال النص - 'Python/defining-functions' ب'Python/defining_functions')
 
(7 مراجعات متوسطة بواسطة مستخدمين اثنين آخرين غير معروضة)
سطر 1: سطر 1:
 
<noinclude>{{DISPLAYTITLE:الاستثناءات في بايثون}}</noinclude>
 
<noinclude>{{DISPLAYTITLE:الاستثناءات في بايثون}}</noinclude>
قد تكون التعابير البرمجية في الشيفرة صحيحة من ناحية الصيغة، إلا أنّها قد تتسبب في حدوث أخطاء عند محاولة تنفيذها. تسمّى الأخطاء المُكتشفة أثناء تنفيذ الشيفرة بالاستثناءات (exceptions) وقد تتسبب في إيقاف عمل البرنامج (fatal) في بعض الأحيان.
+
قد تكون التعابير البرمجية في الشيفرة صحيحة من ناحية الصيغة، لكن قد يؤدي تنفيذ تلك الشيفرة إلى التسبب في حدوث الأخطاء. تسمّى الأخطاء المُكتشفة أثناء تنفيذ الشيفرة بالاستثناءات (exceptions) وقد تتسبب في إيقاف عمل البرنامج (fatal) في بعض الأحيان.
  
تُنشئ الاستثناءات رسائل خطإٍ مماثلة لما يلي:<syntaxhighlight lang="python3">
+
== صيغة الاستثناءات ==
 +
تُنشئ الاستثناءات رسائل خطإٍ مماثلة لما يلي:
 +
 
 +
<syntaxhighlight lang="python3">
 
>>> 10 * (1/0)
 
>>> 10 * (1/0)
 
Traceback (most recent call last):
 
Traceback (most recent call last):
سطر 15: سطر 18:
 
  File "<stdin>", line 1, in <module>
 
  File "<stdin>", line 1, in <module>
 
TypeError: Can't convert 'int' object to str implicitly
 
TypeError: Can't convert 'int' object to str implicitly
</syntaxhighlight>هناك أنواع مختلفة من الاستثناءات، ويصف السطر الأخير في رسالة الخطأ نوع الاستثناء، ويعرض المثال السابق ثلاثة أنواع منها هي <code>ZeroDivisionError</code> و <code>NameError</code> و <code>TypeError</code>.
+
</syntaxhighlight>
 +
 
 +
هناك أنواع مختلفة من الاستثناءات، ويصف السطر الأخير في رسالة الخطأ نوع الاستثناء، ويعرض المثال السابق ثلاثة أنواع منها هي <code>[[Python/built-in exceptions#ZeroDivisionError|ZeroDivisionError]]</code> و <code>[[Python/built-in exceptions#NameError|NameError]]</code> و <code>[[Python/built-in exceptions#TypeError|TypeError]]</code>.
  
نوع الاستثناء المطبوع في رسالة الخطأ هو نفسه اسم الاستثناء الداخلي الذي جرى إطلاقه، وهذا ينطبق على جميع الاستثناءات الداخلية، ولكن قد لا ينطبق على [[Python/user-defined-exceptions|الاستثناءات المعرّفة من قبل المستخدم]] (مع أنّ ذلك أمر يُنصح باتباعه)، وتجدر الإشارة هنا إلى أنّ أسماء الاستثناءات القياسية هي معرّفات داخلية وليست كلمات مفتاحية محجوزة.
+
نوع الاستثناء المطبوع في رسالة الخطأ هو نفسه اسم الاستثناء الداخلي الذي جرى إطلاقه، وهذا ينطبق على جميع الاستثناءات الداخلية، ولكن قد لا ينطبق على [[Python/user-defined_exceptions|الاستثناءات المعرّفة من قبل المستخدم]] (مع أنّ ذلك أمر يُنصح باتباعه)، وتجدر الإشارة هنا إلى أنّ أسماء الاستثناءات القياسية هي [[Python/Basic Syntax#.D8.A7.D9.84.D9.85.D8.B9.D8.B1.D9.91.D9.81.D8.A7.D8.AA .D9.88.D8.A7.D9.84.D9.83.D9.84.D9.85.D8.A7.D8.AA .D8.A7.D9.84.D9.85.D9.81.D8.AA.D8.A7.D8.AD.D9.8A.D8.A9|معرّفات داخلية]] وليست [[Python/Basic Syntax#.D8.A7.D9.84.D9.83.D9.84.D9.85.D8.A7.D8.AA .D8.A7.D9.84.D9.85.D9.81.D8.AA.D8.A7.D8.AD.D9.8A.D8.A9|كلمات مفتاحية]] محجوزة.
  
للاطلاع على قائمة بالاستثناءات الداخلية يمكن مراجعة قسم الاستثناءات الداخلية.
+
للاطلاع على قائمة بالاستثناءات الداخلية يمكن مراجعة قسم [[Python/built-in exceptions|الاستثناءات المضمنة داخليًا في بايثون]].
  
== التعامل مع الاستثناءات ==
+
== التعامل مع الاستثناءات باستخدام عبارة <code>try-except</code>==
يمكن التعامل مع الاستثناءات في بايثون باستخدام عبارة <code>try-except</code> والموضّحة في المثال التالي الذي يستمر في طلب المدخلات من المستخدم إلى أن تكون القيمة المدخلة [[Python/int|عددًا صحيحًا]]، ولكنّه يسمح للمستخدم بمقاطعة البرنامج (بالضغط على مفتاحي <code>Control-C</code> أو أي طريقة أخرى يدعمها نظام التشغيل). لاحظ أنّ مقاطعة البرنامج من قبل المستخدم تؤدي إلى إطلاق استثناء من نوع <code>KeyboardInterrupt</code>.<syntaxhighlight lang="python3">
+
يمكن التعامل مع الاستثناءات في بايثون باستخدام عبارة <code>try-except</code> والموضّحة في المثال التالي الذي يستمر في طلب المدخلات من المستخدم إلى أن تكون القيمة المدخلة [[Python/int|عددًا صحيحًا]]، ولكنّه يسمح للمستخدم بمقاطعة البرنامج (بالضغط على مفتاحي <code>Control-C</code> أو أي طريقة أخرى يدعمها نظام التشغيل). لاحظ أنّ مقاطعة البرنامج من قبل المستخدم تؤدي إلى إطلاق استثناء من نوع <code>[[Python/built-in exceptions#KeyboardInterrupt|KeyboardInterrupt]]</code>.<syntaxhighlight lang="python3">
 
>>> while True:
 
>>> while True:
 
...     try:
 
...     try:
سطر 43: سطر 48:
 
... except (RuntimeError, TypeError, NameError):
 
... except (RuntimeError, TypeError, NameError):
 
...     pass
 
...     pass
</syntaxhighlight>يكون [[Python/class|الصنف]] في عبارة <code>except</code> متوافقًا مع الاستثناء إن كان [[Python/class|الصنف]] نفسه أو صنفًا أساسيًّا منه (ولكن ليس بالعكس، فعبارة <code>except</code> التي تسرد صنفًا مشتقًّا لا تكون متوافقة مع الصنف الأساس). فعلى سبيل المثال ستطبع الشيفرة التالية <code>B, C, D</code> بهذا الترتيب:
+
</syntaxhighlight>تكون [[Python/class|الأصناف]] في عبارة <code>except</code> متوافقةً مع الاستثناء إن كانت من [[Python/class|أصناف]] الاستثناء أو صنفًا أساسيًّا base class منه (ولكن ليس بالعكس، فعبارة <code>except</code> التي تسرد صنفًا مشتقًّا ليست متوافقة مع صنف أساسي). فعلى سبيل المثال ستطبع الشيفرة التالية <code>B, C, D</code> بهذا الترتيب:<syntaxhighlight lang="python3">
 
 
لاحظ أنّه لو قلبنا عبارات except (أي بجعل except B في البداية) فستطبع الشيفرة السابقة B, B, B، إذ تعمل أول عبارة except مطابقة فقط.<syntaxhighlight lang="python3">
 
 
class B(Exception):
 
class B(Exception):
 
     pass
 
     pass
سطر 64: سطر 67:
 
     except B:
 
     except B:
 
         print("B")
 
         print("B")
</syntaxhighlight>قد تقوم عبارة <code>except</code> الأخيرة بحذف اسم الاستثناء، ويجب استخدامها بحذر شديد، لأنّها قد تغطّي على خطأ برمجي بهذه الطريقة. ويمكن أن تستخدم كذلك لطباعة رسالة خطأ ثم إعادة إطلاق الاستثناء (لتسمح لمستدعي الاستثناء بأن يعالجه أيضًا):<syntaxhighlight lang="python3">
+
</syntaxhighlight>لاحظ أنّه لو قلبنا عبارات <code>except</code> (أي بجعل <code>except B</code> في البداية) فستطبع الشيفرة السابقة <code>B, B, B</code>، إذ ستعمل عبارة <code>except</code> الأولى لأنّ الصنفين <code>C</code> و <code>D</code> مشتّقّان من الصنف <code>B</code>.
 +
 
 +
يمكن حذف اسم الاستثناء من عبارة <code>except</code> الأخيرة، ولكن يجب توخّي الحذر عندئذٍ، إذ يمكن وبكل سهولة التغطية على خطأ برمجي حقيقي بهذه الطريقة.
 +
 
 +
يمكن أيضًا استخدام عبارة <code>except</code> الأخيرة لطباعة رسالة خطأ ثم إعادة إطلاق الاستثناء (لتسمح لمستدعي الاستثناء بأن يعالجه أيضًا):<syntaxhighlight lang="python3">
 
import sys
 
import sys
  
سطر 95: سطر 102:
 
...    raise Exception('spam', 'eggs')
 
...    raise Exception('spam', 'eggs')
 
... except Exception as inst:
 
... except Exception as inst:
...    print(type(inst))    # the exception instance
+
...    print(type(inst))    # نسخة الاستثناء
...    print(inst.args)    # arguments stored in .args
+
...    print(inst.args)    # .args الوسائط المخزّنة في
...    print(inst)          # __str__ allows args to be printed directly,
+
...    print(inst)          # __str__ تتيح طباعة الوسائط بصورة مباشرة
...                          # but may be overridden in exception subclasses
+
...                          # ولكن يمكن إعادة تعريفها في الأصناف الفرعية للاستثناء
...    x, y = inst.args    # unpack args
+
...    x, y = inst.args    # فك تحزيم الوسائط
 
...    print('x =', x)
 
...    print('x =', x)
 
...    print('y =', y)
 
...    print('y =', y)
سطر 110: سطر 117:
 
</syntaxhighlight>تطبع الوسائط - إن كانت موجودة في الاستثناء - في الجزء الأخير من رسالة الخطأ الخاصة بالاستثناءات غير المعالَجة.
 
</syntaxhighlight>تطبع الوسائط - إن كانت موجودة في الاستثناء - في الجزء الأخير من رسالة الخطأ الخاصة بالاستثناءات غير المعالَجة.
  
لا تقتصر وظيفة معالجات الاستثناءات على معالجة الاستثناءات لحظة حدوثها في عبارة <code>try</code>، بل تتجاوز ذلك إلى معالجة تلك التي تحدث داخل [[Python/defining-functions|الدوال]] التي تستدعى (ولو بصورة غير مباشرة) في عبارة <code>try</code>. فعلى سبيل المثال:<syntaxhighlight lang="python3">
+
لا تقتصر وظيفة معالجات الاستثناءات على معالجة الاستثناءات لحظة حدوثها في عبارة <code>try</code>، بل تتجاوز ذلك إلى معالجة تلك التي تحدث داخل [[Python/defining_functions|الدوال]] التي تستدعى (ولو بصورة غير مباشرة) في عبارة <code>try</code>. فعلى سبيل المثال:<syntaxhighlight lang="python3">
 
>>> def this_fails():
 
>>> def this_fails():
 
...    x = 1/0
 
...    x = 1/0
سطر 142: سطر 149:
 
NameError: HiThere
 
NameError: HiThere
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 +
== انظر أيضًا ==
 +
* [[Python/syntax errors|أخطاء الصيغة في بايثون]].
 +
* [[Python/built-in exceptions|الاستثناءات المضمنة داخليًا في بايثون]].
 +
* [[Python/user-defined_exceptions|الاستثناءات المعرفة من طرف المستخدم]].
 +
* [[Python/defining_clean-up_actions|تعريف أحداث التنظيف clean-up actions]].
  
 
== مصادر ==
 
== مصادر ==

المراجعة الحالية بتاريخ 14:30، 29 مايو 2018

قد تكون التعابير البرمجية في الشيفرة صحيحة من ناحية الصيغة، لكن قد يؤدي تنفيذ تلك الشيفرة إلى التسبب في حدوث الأخطاء. تسمّى الأخطاء المُكتشفة أثناء تنفيذ الشيفرة بالاستثناءات (exceptions) وقد تتسبب في إيقاف عمل البرنامج (fatal) في بعض الأحيان.

صيغة الاستثناءات

تُنشئ الاستثناءات رسائل خطإٍ مماثلة لما يلي:

>>> 10 * (1/0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>> 4 + spam*3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't convert 'int' object to str implicitly

هناك أنواع مختلفة من الاستثناءات، ويصف السطر الأخير في رسالة الخطأ نوع الاستثناء، ويعرض المثال السابق ثلاثة أنواع منها هي ZeroDivisionError و NameError و TypeError.

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

للاطلاع على قائمة بالاستثناءات الداخلية يمكن مراجعة قسم الاستثناءات المضمنة داخليًا في بايثون.

التعامل مع الاستثناءات باستخدام عبارة try-except

يمكن التعامل مع الاستثناءات في بايثون باستخدام عبارة try-except والموضّحة في المثال التالي الذي يستمر في طلب المدخلات من المستخدم إلى أن تكون القيمة المدخلة عددًا صحيحًا، ولكنّه يسمح للمستخدم بمقاطعة البرنامج (بالضغط على مفتاحي Control-C أو أي طريقة أخرى يدعمها نظام التشغيل). لاحظ أنّ مقاطعة البرنامج من قبل المستخدم تؤدي إلى إطلاق استثناء من نوع KeyboardInterrupt.

>>> while True:
...     try:
...         x = int(input("Please enter a number: "))
...         break
...     except ValueError:
...         print("Oops!  That was no valid number.  Try again...")
...

تعمل عبارة try بالطريقة التالية:

  • في البداية، يجري تنفيذ الشيفرة التي تقع ضمن كتلة try (أي الشيفرة التي تقع بين الكلمتين المفتاحيتين try و except).
  • في حال عدم حدوث أي استثناء، تتجاوز اللغة كتلة except وتنتهي بذلك عملية تنفيذ عبارة try.
  • في حال حدوث استثناء أثناء تنفيذ عبارة try، تتجاوز اللغة بقية الكتلة، فإن كان نوع الاستثناء الحاصل مطابقًا للاستثناء المسمّى بعد الكلمة المفتاحية except تنفّذ اللغة هذه العبارة ويستمر التنفيذ بعد عبارة try.
  • أما إن كان الاستثناء الحاصل غير مطابقٍ للاستثناء المسمّى بعد الكلمة المفتاحية except، يُمرَّر الاستثناء إلى عبارات try الخارجية، وفي حال عدم وجود أي عبارة خارجية يتحول الاستثناء إلى استثناء غير معالج unhandled exception وتتوقف عملية التنفيذ.

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

يمكن تسمية استثناءات متعددة في عبارة except على هيئة صف tuple محاط بالأقواس الهلالية، فمثلًا: 

... except (RuntimeError, TypeError, NameError):
...     pass

تكون الأصناف في عبارة except متوافقةً مع الاستثناء إن كانت من أصناف الاستثناء أو صنفًا أساسيًّا base class منه (ولكن ليس بالعكس، فعبارة except التي تسرد صنفًا مشتقًّا ليست متوافقة مع صنف أساسي). فعلى سبيل المثال ستطبع الشيفرة التالية B, C, D بهذا الترتيب:

class B(Exception):
    pass

class C(B):
    pass

class D(C):
    pass

for cls in [B, C, D]:
    try:
        raise cls()
    except D:
        print("D")
    except C:
        print("C")
    except B:
        print("B")

لاحظ أنّه لو قلبنا عبارات except (أي بجعل except B في البداية) فستطبع الشيفرة السابقة B, B, B، إذ ستعمل عبارة except الأولى لأنّ الصنفين C و D مشتّقّان من الصنف B.

يمكن حذف اسم الاستثناء من عبارة except الأخيرة، ولكن يجب توخّي الحذر عندئذٍ، إذ يمكن وبكل سهولة التغطية على خطأ برمجي حقيقي بهذه الطريقة.

يمكن أيضًا استخدام عبارة except الأخيرة لطباعة رسالة خطأ ثم إعادة إطلاق الاستثناء (لتسمح لمستدعي الاستثناء بأن يعالجه أيضًا):

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except OSError as err:
    print("OS error: {0}".format(err))
except ValueError:
    print("Could not convert data to an integer.")
except:
    print("Unexpected error:", sys.exc_info()[0])
    raise

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

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except OSError:
        print('cannot open', arg)
    else:
        print(arg, 'has', len(f.readlines()), 'lines')
        f.close()

إنّ استخدام عبارة else هنا أفضل من إضافة شيفرة إضافية إلى عبارة try؛ وذلك لأنّها تمنع من التقاط استثناء - عن طريق الخطأ - لم تطلقه الشيفرة المحميّة بعبارة try ... except.

قد يمتلك الاستثناء قيمًا مرتبطة به عند حدوثه، وتعرف هذه القيم أيضًا بوسائط الاستثناء (exception's arguments)، ويعتمد وجود هذه الوسائط وطبيعتها على طبيعة الاستثناء.

يمكن لعبارة except أن تعيّن متغيّرًا بعد اسم الاستثناء، ويرتبط هذا المتغيّر بنسخة من الاستثناء  تخزن فيها قيم الوسائط في instance.args. تعرّف نسخة الاستثناء التابع ‎__str__()‎ لتسهيل طباعة الوسائط دون الحاجة للإشارة إلى ‎.args. ويمكن أيضًا تهيئة الاستثناء قبل إطلاقه وإضافة الخاصيات المطلوبة حسب الحاجة.

>>> try:
...     raise Exception('spam', 'eggs')
... except Exception as inst:
...     print(type(inst))    # نسخة الاستثناء
...     print(inst.args)     # .args الوسائط المخزّنة في
...     print(inst)          # __str__ تتيح طباعة الوسائط بصورة مباشرة
...                          # ولكن يمكن إعادة تعريفها في الأصناف الفرعية للاستثناء
...     x, y = inst.args     # فك تحزيم الوسائط
...     print('x =', x)
...     print('y =', y)
...
<class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs

تطبع الوسائط - إن كانت موجودة في الاستثناء - في الجزء الأخير من رسالة الخطأ الخاصة بالاستثناءات غير المعالَجة. لا تقتصر وظيفة معالجات الاستثناءات على معالجة الاستثناءات لحظة حدوثها في عبارة try، بل تتجاوز ذلك إلى معالجة تلك التي تحدث داخل الدوال التي تستدعى (ولو بصورة غير مباشرة) في عبارة try. فعلى سبيل المثال:

>>> def this_fails():
...     x = 1/0
...
>>> try:
...     this_fails()
... except ZeroDivisionError as err:
...     print('Handling run-time error:', err)
...
Handling run-time error: division by zero

إطلاق الاستثناءات

تتيح عبارة raise إمكانية إطلاق الاستثناء المحدّد، فعلى سبيل المثال:

>>> raise NameError('HiThere')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: HiThere

يشير الوسيط الوحيد الذي تأخذه هذه العبارة إلى نوع الاستثناء المطلوب إطلاقه، ويجب أن يكون هذا الوسيط نسخة استثناء (exception instance) أو صنف استثناء (صنف مشتق من الصنف Exception). وفي حال تمرير صنف استثناء فإنّه سيهيّئ ضمنيًّا وذلك باستدعاء تابع المشيِّد دون تمرير أي وسائط:

raise ValueError  # 'raise ValueError()' اختصار للتعبير

إن كنت بحاجة إلى معرفة ما إذا كان الاستثناء قد أطلق أو لا ولكن دون معالجته، فيمكن استخدام صيغة أبسط من عبارة raise لإعادة إطلاق الاستثناء:

>>> try:
...     raise NameError('HiThere')
... except NameError:
...     print('An exception flew by!')
...     raise
...
An exception flew by!
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
NameError: HiThere

انظر أيضًا

مصادر