موارد استثنا در پایتون — راهنمای کاربردی
در هر زبان برنامهنویسی شاهد حضور دو نوع خطا هستیم که یکی خطاهای کامپایل و دیگری خطاهای زمان اجرا است. خطاهای کامپایل در زمان کامپایل کردن کد منبع و در نتیجهی ساختار یا معناشناسی نادرست رخ میدهند. در این مقاله به بررسی موارد استثنا در پایتون می پردازیم. به مثال زیر توجه کنید:
1print(‘hello there!!’
در کد فوق پرانتز انتهایی فراموش شده است و در نتیجه خطای ساختاری رخ داده که در زمان تلاش برای اجرای کد بروز مییابد. این خطا در زمان کامپایل کردن کد ظاهر میشود و از این رو خطای کامپایل نام دارد (به طور خاص خطای ساختاری یا نحوی نامیده میشود) برای اصلاح این خطا در خط فوق، کافی است یک پرانتز انتهایی در آخر خط قرار دهیم، یعنی باید ساختار/معناشناسی کد را به صورت زیر اصلاح کنیم:
1print(‘hello there!!’)
از سوی دیگر خطاهای زمان اجرا زمانی رخ میدهند که برنامه در حال اجرا است. این خطاها در نتیجه زمینه/ ورودی غیر معمول در یک قطعه کد اجرایی حاصل میشوند. یعنی یک برنامه که از نظر ساختاری و معناشناختی صحیح است بسته به زمینه اجرایی و ورودی برنامه ممکن است خطا داشته باشد یا نداشته باشد. به مثال زیر توجه کنید:
1res = 40 / 2
کد فوق از نظر ساختاری و معناشناختی صحیح است و هیچ خطایی تولید نمیکند. نتیجه پس از اجرای برنامه res=20 خواهد بود. اینک به کد زیر توجه کنید:
1res = 40 / 0
با این که کد از نظر ساختاری و معناشناختی صحیح است، اما در زمان اجرا خطایی صادر میکند. دلیل خطا تلاش برای تقسیم بر صفر است که تعریف نشده است. این نمونهای از خطای زمان اجرا به دلیل ورودی نامعمول است. این خطاهای زمان اجرا به نام «استثنا» (Exception) نیز شناخته میشوند.
نمونههایی از خطاهای زمان اجرا که به دلیل زمینه یا چارچوب پدید میآیند، اجرا کردن یک برنامه (که از کارکردهای خاص سیستم عامل استفاده میکند) روی یک سیستم ناسازگار است.
چرا خطاهای زمان اجرا بد هستند؟
دلیل بد بودن خطاهای زمان اجرا این است که در زمان وقوع، موجب خاتمه یافتن برنامه میشوند.
موقعیتی را تصور کنید که یک برنامه ماشین حساب ساختهایم که عملیات ابتدایی حساب (+، -، * و /) را روی دو عدد اجرا میکند. این برنامه از کاربر دو عدد میخواهد و عملیات مورد نظر را نیز میپرسد تا روی اعداد اجرا کند. فرض کنید برنامه در حلقه اجرا میشود، یعنی زمانی که 3 ورودی را درخواست کرد (دو عملوند و یک عملگر) محاسبه را اجرا کرده و نتیجه را نمایش میدهد، و سپس منتظر وارد کردن 3 ورودی بعدی میماند و نتایج آنها را نیز محاسبه و نمایش میدهد و همین طور تا آخر ادامه میدهد.
این برنامه کار خود را به درستی انجام میدهد تا این که عملیات تقسیم بر صفر پیش بیاید (مثلاً 25/0). در این ورودی، برنامه یک خطای زمان اجرا/استثنا صادر میکند، چون نمیتواند نتیجه را تحلیل کند و بیدرنگ خاتمه مییابد. شما باید برنامه را از نو آغاز کنید و مجدداً مقادیر را وارد نمایید تا چنین ورودی پیش بیاید. این فقط یک برنامه ماشین حساب ساده است که راهاندازی مجدد آن چند ثانیه طول میکشد، اما نرمافزاری که راهاندازی مجدد آن دقایق یا ساعتها طول میکشد چطور؟ گاهی اوقات نیز کلاً امکان راهاندازی مجدد وجود ندارد و این وضعیت نامناسبی ایجاد میکند.
آیا راهی برای جلوگیری از وقوع استثنا وجود دارد؟ نه، ما نمیتوانیم از وقوع استثناها جلوگیری کنیم، اما میتوانیم آنها را طوری مدیریت کنیم که موجب خاتمه یافتن برنامه نشوند.
منظور از مدیریت استثنا چیست؟ معنی مدیریت استثنا این است که وقتی اتفاق میافتد نگذاریم برنامه را پایان دهد و برنامه بتواند به اجرای خود ادامه دهد.
استثنا چگونه مدیریت میشود؟ در بخشهای بعدی این مقاله به بررسی همین موضوع خواهیم پرداخت.
هر آنچه تاکنون مطرح کردیم در مورد همه زبانهای برنامهنویسی که از استثنا و مدیریت آنها پشتیبانی میکنند، قابل تعمیم است. اینک نوبت آن رسیده است که بحث را به یک زبان خاص یعنی پایتون محدود کنیم. از این بخش به بعد همه مواردی که مطرح میشوند مختص زبان برنامهنویسی پایتون هستند مگر این که صراحتاً چیز دیگری قید شده باشد.
استثناها در پایتون
اینک به بررسی روش مدیریت استثناها در پایتون میپردازیم.
مدیریت استثناها
برای مدیریت استثناها در پایتون چند روش وجود دارد که در ادامه هر یک از آنها را به تفصیل مورد بررسی قرار خواهیم داد.
بلوک try-except ابتدایی
استثناها از طریق یک گزاره try مدیریت میشوند که ساختار مقدماتی آن به صورت زیر است:
1try:
2 # code which might throw/raise exception
3except <exception_to_handle>:
4 # code to be executed once the exception is handled.
کدی که مشکوک به ایجاد استثنا است زیر بند try یک گزاره try قرار میگیرد. کدی که باید در زمان بروز استثنا برای مدیریت آن اجرا شود نیز زیر بند except قرار میگیرد. به مثال زیر توجه کنید:
1def divide(dividend, divisor):
2 try:
3 quotient = dividend / divisor
4 print('Result = {0}'.format(quotient))
5 except ZeroDivisionError:
6 print('Divisor is zero; Division is impossible')
خروجی برای ورودی متفاوت:
>>> divide(50، 2) Result = 25 >>> divide(50، 0) Divisor is zero; Division is impossible
گزاره try به صورت زیر عمل میکند:
بندهای چندگانه except
این امکان هست که یک کد بیش از یک نوع استثنا ایجاد کند. مثلاً استثناها ممکن است از انواع ValueError ،AttributeError ،KeyError و غیره باشند. اینها مواردی از استثناهای داخلی هستند یعنی در خود پایتون موجود هستند. برای مشاهده لیست کامل آنها به این لینک (+) مراجعه کنید. شما میتوانید استثناها را خودتان نیز تولید و صادر کنید! در ادامه در این مورد بیشتر توضیح خواهیم داد.
اما اینک سؤال این است که چگونه میتوان استثناهای چندگانه (هم داخلی و هم سفارشی) که از کد موجود در بلوک try ناشی میشوند را مدیریت کرد؟ آیا پایتون پشتیانی خاصی از این وضعیت دارد؟ بله چنین است. بدین منظور باید از بندهای چندگانه except استفاده کنیم. به مثال زیر توجه کنید:
>>> int_value = int(a)
این گزاره ممکن است موجب بروز ValueError یا TypeError شود یا کلاً هیچ نوع استثنایی ایجاد نکند و همه اینها به نوع و مقدار متغیر a بستگی دارد. فرض کنید a= 3.2 یا 'a = ‘1200 باشد در این صورت هیچ استثنایی تولید نمیشود؛ اما اگر 'a=’12k باشد، در این صورت ValueError رخ میدهد. همچنین اگر [a= [1، 2 باشد در این صورت استثنای TypeError بروز مییابد.
روش مدیریت هر دوی آنها به صورت زیر است:
1def dummy(a):
2 try:
3 int_value = int(a)
4 except ValueError:
5 print('Value of "a" cannot be deduced to integer')
6 except TypeError:
7 print('Type of "a" is incompatible; should either be a number or a string')
کد فوق صرفاً نردبانی از بندهای except است و استثناهایی دارد که قرار است مدیریت شوند. نکتهای که باید توجه داشت این است که ترتیب مدیریت باید صحیح باشد.
Exception در بند except به مدیریت استثناهایی از یک نوع یا مشتق از آن میپردازد. اما معکوس آن صحیح نیست یعنی Exception در بند except به مدیریت خطای پایه از نوعی که مشتق شده نمیپردازد. برای نمونه کد زیر به ترتیب مقادیر B ،C و D را نمایش میدهد:
1class B(Exception):
2 pass
3
4class C(B):
5 pass
6
7class D(C):
8 pass
9
10for cls in [B, C, D]:
11 try:
12 raise cls()
13 except D:
14 print("D")
15 except C:
16 print("C")
17 except B:
18 print("B")
توجه کنید که اگر بندهای except معکوس بودند (یعنی except به نام B اول بود، موارد نمایش یافته به صورت B ،B ،B بودند و نخستین بند except منطبق تحریک میشد. برای بصریسازی بهتر به تصویر زیر توجه کنید:
در این تصویر در سمت راست نوعی استثنا رخ داده است، سمت چپ مدیران استثنا هستند که D از C مشتق میشود و C نیز از B مشتق میشود.
استثناهای چندگانه در یک بند except منفرد
یک حالت کوتاهتر (البته نه همیشه) برای ساختار فوق نیز وجود دارد. ساختار کلی آن به صورت زیر است:
1try:
2 # exception prone code here
3except (exception_1, exception_2, .... ,exception_n):
4 # code to be executed once the exception is handled
و مثالی از آن به صورت زیر است:
1def dummy(a):
2 try:
3 int_value = int(a)
4 except (ValueError, TypeError):
5 print('Error occurred. Either "a" is not an integer or type of "a" is incompatible')
یعنی همه استثناهایی که قصد داشتیم مدیریت کنیم در یک چندتایی ترکیب میکنیم. استثناهایی که در این چندتاییها ستاند با ترتیب ظهورشان در چندتایی مطابقت دارند. قاعده بخش قبلی در این بخش نیز صدق میکند. میتوان هر دو این رویکردها را با هم ترکیب نیز کرد یعنی یک نردبان از بندهای except داشت که هر یک بیش از یک استثنا (در چندتایی) را مدیریت میکنند.
در نهایت سؤال این است که تفاوت بین دو رویکرد فوق چیست و هر کدام در چه موقعیتی بهتر هستند؟
تفاوت دو رویکرد فوق در شیوه پاسخدهی در زمان مدیریت استثناها است.
- اگر میخواهید یک قطعه کد را برای همه استثناهای مدیریتشده اجرا کنید، در این صورت از رویکرد دوم استفاده کنید. این رویکرد از تکرار قطعه کد در بندهای except چندگانه جلوگیری میکند.
- اگر میخواهید بسته به نوع استثناهایی که مدیریت میکنید، قطعه کدهای متفاوتی داشته باشید، در این صورت از رویکرد اول استفاده کنید. البته اجرای این کار با استفاده از رویکرد دوم نیز میسر است و مثالی از روش اجرای آن به صورت زیر است:
1try:
2 # …
3except (exception_1, exception_2, ..) as e:
4 if type(e).__name__ == 'exception_1':
5 # code specific to exception_1
6 elif type(e).__name__ == 'exception_2':
7 # code specific to exception_2
8 .
9 .
10 .
اما بهتر است که از رویکرد اول برای چنین حالتهایی استفاده کنید، زیرا درک آن آسانتر است و از پشتیبانی ارائه شده از سوی خود زبان به جای یک راهحل مانند فوق بهره میگیرد.
تعیین نام مستعار
به خط زیر در کد بخش قبل توجه کنید:
except (exception_1، exception_2،..) as e:
این استثنایی است (هستند) که یک نام برای آن تعیین شده است. این کار از نظر فنی «aliasing» یا تعیین نام مستعار نامیده میشود.
در زمان تعیین نام مستعار، میتوانیم با استفاده از یک نام رایج به استثنا (ها) دسترسی داشته باشیم. این حالت در زمانی مفید خواهد بود که بخواهیم از خصوصیتهای استثنایی که مدیریت میشود بهره بگیریم. تعیین نام مستعار از طریق کلیدواژه as صورت میگیرد. ساختار آن به صورت زیر است:
1try:
2 # try code
3except (exception_1, exception_2, ..) as <alias>:
4 # exception handling here
5 # you can access attributes of exception(s) being handled through it’s alias <alias>;
در کد فوق <alias> را باید با نام مستعار مورد نظر خود جایگزین کنید. به مثال زیر توجه کنید:
1try:
2 raise NameError('Undefined name')
3 # raise ValueError('Invalid name')
4except (NameError, ValueError) as err:
5 print('Got exception with msg {0}'.format(err.args))
خروجی:
('Invalid name')
شما میتوانید از هر عنوانی برای نام مستعار استفاده کنید. بدیهی است که امکان استفاده از کلیدواژههای داخلی میسر نیست. به طور معمول از e استفاده میشود.
نکته: اگر میخواهد برای یک استثنای منفرد نام مستعار تعیین کنید در این صورت استفاده از پرانتزهای پیرامونی اختیاری است. اما در صورتی که مانند مثال فوق از استثناهای چندگانه استفاده میکنید، پرانتزها الزامی هستند.
بند else
برخی اوقات با حالتی مواجه میشویم که میخواهیم یک قطعه کد را تنها و تنها در صورتی اجرا کنیم که کد زیر بند try هیچ استثنایی ایجاد نکرده باشد. به مثال زیر توجه کنید:
مسئله: تابعی به نام divide بنویسید که a را بر b تقسیم کند (به عنوان پارامتر ارسال میشود) و موارد زیر را در خروجی نمایش دهد:
- اگر b برابر با صفر بود، در این صورت عبارت «امکان تقسیم بر صفر وجود ندارد.» را نمایش دهد.
- در غیر این صورت <Output = <quotient را نمایش دهد که quotient حاصل تقسیم است.
راهحل
1def divide(a, b):
2 try:
3 output = a / b
4 except ZeroDivisionError:
5 print('Cannot divide by zero')
6 else:
7 print('Output = {0}'.format(output))
خروجی
>>> divide(20، 10) Output = 2.0 >>> divide(20، 0) Cannot divide by zero
بند else اختیاری است و در صورت حضور، همواره از بند except تبعیت میکند. کد زیر بند else تنها زمانی اجرا خواهد شد که بند زیر try هیچ استثنایی صادر نکند. اگر استثنایی در بلوک try صادر شود، در این صورت بلوک else اجرا نخواهد شد بلکه بند except آن را مدیریت خواهد کرد.
بند Finally
گزاره try یک بند دیگر به نام Finally نیز دارد که اساساً برای پاکسازی اقدامات استفاده میشود. این بند پس از همه بندهای دیگر میآید. بند Finally در هر حالتی صرف نظر از این که استثنایی رخ داده یا نداده باشد اجرا خواهد شد. بند Finally لزوماً نیازی به وجود بندهای else یا except ندارد. به مثال زیر توجه کنید:
1def divide(a, b):
2 try:
3 output = a / b
4 except ZeroDivisionError:
5 print('Cannot divide by zero')
6 else:
7 print('Output = {0}'.format(output))
8 finally:
9 print('Executing finally clause')
خروجی
>>> divide(2، 1) Output = 2.0 Executing finally clause >>> divide(2، 0) Cannot divide by zero Executing finally clause >>> divide("2"، "1") Executing finally clause Traceback (most recent call last): File "<stdin>"، line 1، in <module> File "<stdin>"، line 3، in divide TypeError: unsupported operand type(s) for /: 'str' and 'str'
بند Finally همواره پیش از ترک گزاره try اجرا خواهد شد و مهم نیست که استثنایی رخ داده است یا نه. زمانی که یک استثنا در بند try رخ دهد و از سوی بند except مدیریت نشود (و یا در بند except یا else رخ دهد) در واقع مجدداً پس از اجرای بند Finally رخ داده است (فراخوانی سوم به تابع divide را در بخش قبل ببینید). بند Finally در مسیر خروجی زمانی که از یک گزاره break ،continue یا return استفاده شده باشد همچنان اجرا خواهد شد.
کاربردهای عملی بند Finally برای آزادسازی منابع مانند فایلها یا اتصالهای پایگاه داده و مواردی از این دست است. فرق بین داشتن کدی داخل بند Finally و نوشتن آن خارج از این بند و پس از گزاره try چیست؟ به بیان ساده فرق بین دو حالت زیر چیست؟
1def divide(a, b):
2 try:
3 output = a / b
4 except ZeroDivisionError:
5 print('Cannot divide by zero')
6 else:
7 print('Output = {0}'.format(output))
8 finally:
9 print('Leaving the function')
10
11
12def divide(a, b):
13 try:
14 output = a / b
15 except ZeroDivisionError:
16 print('Cannot divide by zero')
17 else:
18 print('Output = {0}'.format(output))
19
20 print('Leaving the function')
تفاوت دو کد فوق تنها زمانی مشخص میشود که استثنایی (در هر یک از بندهای try ،except یا else) رخ دهد و مدیریت نشده باشد. در این حالت:
- کد اول عبارت 'Leaving the function' را نمایش داده و استثنا را مجدداً صادر میکند.
- کد دوم عبارت 'Leaving the function' را نمایش نمیدهد و استثنا به کد بیرونی ارسال میشود.
برای جمعبندی باید اشاره کنیم که فرق بین دو سناریوی فوق این است که کد موجود در بند Finally حتی در صورتی که استثنایی رخ داده باشد (و مدیریت نشده باشد) اجرا میشود؛ اما کدی که در ادامه گزاره try میآید چنین حالتی ندارد.
ایجاد استثناها
در این بخش با روش ایجاد دستی استثناها و همچنین ایجاد استثناهای سفارشی آشنا میشویم.
ایجاد استثناهای داخلی
پایتون روشی برای ایجاد دستی یک استثنا ارائه کرده است. این کار از طریق کلیدواژه raise صورت میپذیرد.
>>> raise AssertionError('Asserted statement is incorrect') Traceback (most recent call last): File "<stdin>"، line 1، in <module> raise AssertionError('Asserted statement is incorrect') AssertionError: Asserted statement is incorrect
کلیدواژه raise تنها یک آرگومان میگیرد که یا یک کلاس استثنا است (که از کلاس Exception مشتق میشود) و یا یک وهله از استثنا است. در کد مثال فوق، آرگومان یک وهله از استثنا با پیام رشتهای است. این پیام رشتهای (اختیاری) زمانی که ارسال شود، خطا را توصیف میکند.
اگر آرگومان یک کلاس استثنا باشد، در این صورت سازنده آن بدون هیچ آرگومانی به صورت زیر فراخوانی میشود:
>>> raise AssertionError Traceback (most recent call last) File "<stdin>"، line 1، in <module> raise AssertionError AssertionError
تعریف و ایجاد استثناهای سفارشی
پیش از این که به معرفی روش ایجاد استثناهای سفارشی بپردازیم ابتدا به این سؤال پاسخ میدهیم که اساساً این کار چه ضرورتی دارد. موارد خاصی وجود دارند که در آنها استثناهای داخلی نمیتوانند برای توصیف معنیدار خطای رخ داده، مورد استفاده قرار گیرند.
برای نمونه فرض کنید تابع سادهای داریم که تعداد واحدهای (برق) مصرفی بین دو خوانش مجزا را محاسبه میکند. اگر هر کدام از خوانش ها منفی باشند، تابع استثنای ValueError ایجاد میکند و فرض میکند که واحدهای مصرفی محاسبهشده منفی هستند. در این صورت میخواهیم یک استثنا ایجاد شود (برای مثال NegativeConsumptionError). اما این استثنا جزء استثناهای داخلی پایتون نیست. البته ما همچنان میتوانیم از استثنای داخلی ValueError برای مدیریت این مورد نیز استفاده کنیم. اگر بخواهیم از ValueError برای این خطا استفاده کنیم، در این صورت هیچ روشی برای کد فراخوانی کننده electricity_consumption جهت ایجاد تمایز بین 'Negative reading' and 'Negative consumption' وجود نخواهد داشت.
با استفاده از استثنای داخلی ValueError
1def electricity_consumption(last_reading, current_reading):
2 if last_reading < 0 or current_reading < 0:
3 raise ValueError('Negative readings')
4 units = current_reading - last_reading
5 if units < 0:
6 raise ValueError('Negative consumption')
7
8
9def biller():
10 try:
11 electricity_consumption(l_r, c_r):
12 except ValueError:
13 # no way to determine what error (negative reading or negative consumption) occurred (see [2])
با استفاده از استثنای سفارشی
1def electricity_consumption(last_reading, current_reading):
2 if last_reading < 0 or current_reading < 0:
3 raise ValueError('Negative readings')
4 units = current_reading - last_reading
5 if units < 0:
6 raise NegativeConsumptionError('Negative consumption; something fishy')
7
8
9def biller():
10 try:
11 electricity_consumption(l_r, c_r):
12 except ValueError:
13 # code to handle negative reading
14 except NegativeConsumptionError:
15 # code to handle negative consumption
روش تعریف استثنای سفارشی چگونه است؟
برای تعریف یک استثنای سفارشی باید یک کلاس تعریف کنید که از کلاس Exception مشتق میشود یا یک کلاس فرعی از آن بسازید. در ادامه یک مثال ساده را ملاحظه میکنید:
1class Error(Exception):
2 """Base class for exceptions in this module."""
3 pass
4
5
6class InputError(Error):
7 """Exception raised for errors in the input.
8 Attributes:
9 expression -- input expression in which the error occurred
10 message -- explanation of the error
11 """
12
13 def __init__(self, expression, message):
14 self.expression = expression
15 self.message = message
16
17
18class TransitionError(Error):
19 """Raised when an operation attempts a state transition that's not
20 allowed.
21 Attributes:
22 previous -- state at the beginning of transition
23 next -- attempted new state
24 message -- explanation of why the specific transition is not allowed
25 """
26
27 def __init__(self, previous, next, message):
28 self.previous = previous
29 self.next = next
30 self.message = message
یک رویه رایج، ایجاد کلاس مبنا برای استثناهای تعریف شده از سوی ماژول و ایجاد کلاس فرعی از آن برای ایجاد کلاسهای استثنای خاص برای شرایط خطای متفاوت است. زمانی که استثنای مبنا را برای یک ماژول تعریف کردیم و سپس از آن کلاس فرعی برای تعریف استثناهای خاص ساختیم، میتوانیم به سادگی همه استثناها را که از آن ناشی میشوند با استفاده از استثنای مبنا مدیریت کنیم.
همچنین میتوانید خصوصیتهایی روی استثناهای سفارشی تعیین کنید که با استفاده از دستگیرههایی قابل بازیابی باشند. به مثال زیر توجه کنید:
1try:
2 raise InputError('2 # 3', 'Unknown operator')
3except InputError as e:
4 print(e.expression)
5 print(e.message)
خروجی
2 # 3 Unknown operator
Logger.exception
ما در همه کدهای عملی خود از لاگرها استفاده میکنیم، چون لاگرها کار دیباگ گردن را تسهیل میکنند. لاگرها در پایتون، پشتیبانی خاصی برای استثناها دارند. از این رو در ادامه مقداری در مورد آنها توضیح میدهیم.
متد مربوطه ()logger.exception نام دارد. به کد زیر توجه کنید:
1import logging
2logger = logging.getLogger(__name__)
3
4try:
5 4 / 0
6except ZeroDivisionError:
7 logger.exception("Exception while performing division - handled")
خروجی کد فوق یک رد پشته کامل از استثنا است که مدیریت شده است و صرفاً یک توصیف متنی از استثنا محسوب نمیشود. مزیتهای آن به شرح زیر هستند:
دیگر نیاز نداریم از نامهای مستعار برای استثناها استفاده کنیم، مگر اینکه آن را درون بند except نیاز داشته باشیم، چون استثنای رخ داده به صورت ضمنی در ()logger.exception قرار دارد.
علاوه بر ردگیری پشته، ()logger.exception یک پیام روی رد پشته نیز نمایش میدهد. بدین ترتیب خروجی لاگ قطعه کد به صورت زیر خواهد بود:
ERROR: Exception while performing division — handled Traceback (most recent call last): File "<stdin>"، line 1، in <module> raise ZeroDivisionError ZeroDivisionError
نکته ۱: ()logger.exception پیامها را با سطح ERROR لاگ میکند.
نکته ۲: ()logger.exception باید تنها از یک دستگیره استثنا فراخوانی شود.
بدین ترتیب به پایان این مقاله میرسیم.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی پایتون Python
- مجموعه آموزشهای برنامهنویسی
- گنجینه آموزش های برنامه نویسی پایتون (Python)
- ترفندهای پایتون که باید آنها را بدانید — راهنمای کاربردی
- 1۰ گام تکمیلی ایجاد پروژه متن باز پایتون — راهنمای کاربردی
==
سلام و عرض احترام یک سوالی داشتم
وسطای اجرای کدم ممکنه ناگهان اتصال به یک url قطع بشه (قطعی سرور یا داون شدن url) و نهایتا این کار باعث میشه برنامه به طور کل از run خارج بشه، الان اگه من از این دستور استفاده کنم موقع قطع شدن دسترسی صرفا یک پیام میفرسته و دوباره try میکنه برای اتصال؟
سلام
وقت بخیر
سوالی داشتم
امکان داره از چندین try در داخل حلقه for استفاده کرد