|
45 | 45 | **«خطاهای منطقی» (Logical errors)** برخلاف دیگر خطاها باعث توقف اجرای برنامه نشده بلکه باعث تولید نتایج نادرستی میشوند که از دیدگاه برنامهنویسی درست بوده ولی از دیدگاه منطقی کاملا اشتباه هستند. به عنوان یک مثال ساده فرض کنید فرمول محاسبه معدل اشتباه پیادهسازی شده باشد! این نوع خطا مصداق بارز «باگ» (Bug) در برنامه است که همیشه پیشگیری، از کشف و اصلاح آنها به مراتب سادهتر خواهد بود. |
46 | 46 |
|
47 | 47 |
|
48 | | -با وجود این توضیحات و همانطور که مشاهده خواهید کرد، بروز خطا همواره یک امر زشت و ناخواسته نبوده بلکه گاهی نیز یک استراتژی از سوی برنامهنویس خواهد بود تا یک وضعیت را به سطوح دیگر از برنامه اعلام یا اینکه مستقیما تغییری در روند اجرای برنامه ایجاد کند. در این صورت خطاها دیگر با نام زشت خطا خوانده نمیشوند بلکه به آنها **استثنا یا Exception** میگویند. |
| 48 | +با وجود این توضیحات و همانطور که مشاهده خواهید کرد، بروز خطا همواره یک امر زشت و ناخواسته نبوده بلکه گاهی نیز یک استراتژی از سوی برنامهنویس خواهد بود تا یک وضعیت را به سطوح دیگر از برنامه اعلام یا اینکه مستقیما تغییری در روند اجرای برنامه ایجاد کند. در این صورت خطاها دیگر با نام زشت خطا خوانده نمیشوند بلکه به آنها **استثنا یا اعتراض یا Exception** میگویند. |
49 | 49 |
|
50 | 50 |
|
51 | | -به صورت کلی، **استثنا یا Exception** امکانی برای خروج برنامه از یک وضعیت مشخص است و بروز آن، همانند اعلام عمومی یک خبر مهم در برنامه میباشد. میتوان با پیشبینی بروز Exceptionها در برنامه، به اصطلاح آنها را **catch** نمود و فرآیندی را برای مدیریت آنها پیادهسازی کرد. |
| 51 | +به صورت کلی، **Exception** امکانی برای خروج برنامه از یک وضعیت مشخص است و بروز آن، همانند اعلام عمومی یک خبر مهم در برنامه میباشد. میتوان با پیشبینی بروز Exceptionها در برنامه، به اصطلاح آنها را **catch** نمود و فرآیندی را برای مدیریت آنها پیادهسازی کرد. |
52 | 52 |
|
53 | 53 | درک وقوع یک Exception و امکان ایجاد یک فرآیند برای مدیریت آن، قابلیت مهمی در یک زبانبرنامهنویسی محسوب میشود. چرا که میتوان از آن در دو نقش زیر بهره گرفت: |
54 | 54 |
|
|
171 | 171 | این نکته را نیز در نظر بگیرید - همانطور که اگر به خروجیهای دقت کرده باشید حتما متوجه شدهاید در دو حالت مربوط به گزارش خطای مربوط به ``SyntaxError`` خبری از سطر ``:Traceback (most recent call last)`` که در حالت خطای زمان اجرای ``TypeError`` مشاهده کردیم، نمیباشد. در واقع این سطر تنها در گزارش خطاهایی که پس از اجرای برنامه رخ دهند (Runtime errors)، نمایش داده خواهد شد. در زمان بررسی و ترجمه کد پایتون به بایتکد هرجا مشکلی باشد عملیات در همان نقطه متوقف میشود و صرفا گزارشی مبنی بر ابراز آن نقطه به برنامهنویس ارايه میگردد و نه چیزی که بتوان آن را یک گزارش ردیابی با Traceback نامید چرا که هنوز برنامه به اجرا درنیامده و اصلا نیازی به این کار نیست! |
172 | 172 |
|
173 | 173 |
|
| 174 | +Exception handling |
| 175 | +~~~~~~~~~~~~~~~~~~~~~~~~ |
| 176 | +در زبانهای برنامهنویسی صدای اعتراض یک Exception قابل درک و تشخیص است و میتوان برای آنها فرآیندی را پیشبینی کرد که بروز آنها نه تنها باعث اتمام برنامه نشود بلکه برنامه بتواند در مسیر درست به اجرای خود ادامه دهد. |
| 177 | + |
| 178 | +در زبان برنامهنویسی پایتون دستور ``try/except`` برای همین منظور فراهم دیده شده است [`اسناد پایتون <https://docs.python.org/3/reference/compound_stmts.html#the-try-statement>`__]. |
| 179 | + |
| 180 | +``try/except`` |
| 181 | +------------------------ |
| 182 | + |
| 183 | + |
| 184 | +ساختار این دستور به شکل زیر است:: |
| 185 | + |
| 186 | + try: |
| 187 | + pass |
| 188 | + |
| 189 | + except: |
| 190 | + pass |
| 191 | + |
| 192 | +در این ساختار آن قطعه کدی که محتمل بروز Exception میباشد، داخل بدنه ``try`` و قطعه کدی که میبایست پس از وقوع Exception به اجرا درآید، داخل بدنه ``except`` قرار میگیرند:: |
| 193 | + |
| 194 | + >>> def print_int_sum(a, b): |
| 195 | + ... try: |
| 196 | + ... print(a + b) |
| 197 | + ... except: |
| 198 | + ... print(f'ERROR: {a}+{b}') |
| 199 | + ... |
| 200 | + >>> print_int_sum(2, 3) |
| 201 | + 5 |
| 202 | + >>> print_int_sum(9, 3) |
| 203 | + 12 |
| 204 | + >>> print_int_sum(5, 'D') |
| 205 | + ERROR: 5+D |
| 206 | + |
| 207 | +حالت فعلی از دستور ``except`` هر نوع Exceptionای که در داخل بدنه ``try`` رخ دهد را تشخیص و ادامه اجرای برنامه را به دست میگیرد، به اصطلاح یک expression-less except است. ولی میتوان دستور ``except`` را محدود به تشخیص نوع خاصی از Exception کرد. در این صورت میبایست نوع Exception مورد نظر خود را در کنار دستور ``except`` درج نماییم: |
| 208 | + |
| 209 | + |
| 210 | +.. code-block:: python |
| 211 | + :linenos: |
| 212 | +
|
| 213 | + def print_int_sum(a, b): |
| 214 | +
|
| 215 | + try: |
| 216 | + print(a + b) |
| 217 | +
|
| 218 | + except TypeError: |
| 219 | + print(f'ERROR: {a}+{b}') |
| 220 | +
|
| 221 | +
|
| 222 | +میتوان با استفاده از یک دستور ``try`` چندین Exception را تشخیص دهیم. برای این منظور کافی است از یک دستور ``try`` به همراه چندین دستور ``except`` استفاده کنیم: |
| 223 | + |
| 224 | +.. code-block:: python |
| 225 | + :linenos: |
| 226 | +
|
| 227 | + def print_sum_div_first(a, b): |
| 228 | +
|
| 229 | + try: |
| 230 | + sum = a + b |
| 231 | + div = sum / a |
| 232 | + print(div) |
| 233 | +
|
| 234 | + except TypeError: |
| 235 | + print(f'TypeError: ({a}+{b!r})/{a}') |
| 236 | +
|
| 237 | + except: |
| 238 | + print(f'OTHER ERROR: ({a}+{b!r})/{a}') |
| 239 | +
|
| 240 | +
|
| 241 | + print_sum_div_first(5, 6) |
| 242 | + print_sum_div_first(3, 'G') |
| 243 | + print_sum_div_first(0, 8) |
| 244 | +
|
| 245 | +:: |
| 246 | + |
| 247 | + 2.2 |
| 248 | + TypeError: (3+'G')/3 |
| 249 | + OTHER ERROR: (0+8)/0 |
| 250 | + |
| 251 | + |
| 252 | +ساختار ``try/except`` این مثال شامل دو دستور ``except`` میباشد، دستور نخست تنها ``TypeError`` و دستور دوم هر Exception دیگری به جز موارد بالای خود (در اینجا: ``TypeError``) را تشخیص میدهند. |
| 253 | + |
| 254 | +در مثال قبل، دستور موجود در سطر ۱۷ باعث بروز خطای «تقسیم بر صفر» [`ویکیپدیا <https://en.wikipedia.org/wiki/Division_by_zero>`__] یا Exceptionای با نام ``ZeroDivisionError`` در پایتون شده است - که میتوان به صورت زیر آن را بازنویسی نمود: |
| 255 | + |
| 256 | +.. code-block:: python |
| 257 | + :linenos: |
| 258 | +
|
| 259 | + def print_sum_div_first(a, b): |
| 260 | +
|
| 261 | + try: |
| 262 | + sum = a + b |
| 263 | + div = sum / a |
| 264 | + print(div) |
| 265 | +
|
| 266 | + except TypeError: |
| 267 | + print(f'TypeError: ({a}+{b!r})/{a}') |
| 268 | +
|
| 269 | + except ZeroDivisionError: |
| 270 | + print(f'ZeroDivisionError: ({a}+{b!r})/{a}') |
| 271 | +
|
| 272 | +
|
| 273 | + print_sum_div_first(5, 6) |
| 274 | + print_sum_div_first(3, 'G') |
| 275 | + print_sum_div_first(0, 8) |
| 276 | +
|
| 277 | +:: |
| 278 | + |
| 279 | + 2.2 |
| 280 | + TypeError: (3+'G')/3 |
| 281 | + ZeroDivisionError: (0+8)/0 |
| 282 | + |
| 283 | + |
| 284 | +چنانچه مکانیزم مدیریت خطای شما برای چندین نوع Exception مشخص یکسان است میتوانید آن دستورهای ``except`` را با یکدیگر ترکیب کرد و تنها از یک دستور ``except`` استفاده نمایید. برای این منظور تنها کافی است نام تمام Exceptionهای مورد نظر خود را در قالب یک شی تاپل به دستور ``except`` بسپرید: |
| 285 | + |
| 286 | +.. code-block:: python |
| 287 | + :linenos: |
| 288 | +
|
| 289 | + def print_sum_div_first(a, b): |
| 290 | +
|
| 291 | + try: |
| 292 | + sum = a + b |
| 293 | + div = sum / a |
| 294 | + print(div) |
| 295 | +
|
| 296 | + except (TypeError, ZeroDivisionError): |
| 297 | + print(f'Error: ({a}+{b!r})/{a}') |
| 298 | +
|
| 299 | +
|
| 300 | + print_sum_div_first(5, 6) |
| 301 | + print_sum_div_first(3, 'G') |
| 302 | + print_sum_div_first(0, 8) |
| 303 | +
|
| 304 | +:: |
| 305 | + |
| 306 | + 2.2 |
| 307 | + Error: (3+'G')/3 |
| 308 | + Error: (0+8)/0 |
| 309 | + |
| 310 | + |
| 311 | +هر چیزی در پایتون یک شی است، حتی Exceptionها! مفسر پایتون در ازای هر Exceptionای که رخ میدهد یک شی نیز در اختیار برنامهنویس قرار میدهد و این شی در صورت تمایل از طریق دستور ``except`` قابل دسترس میباشد. برای این منظور تنها کافی است از دستور ``as`` برای انتساب آن Exception به یک متغییر دلخواه استفاده نماییم: |
| 312 | + |
| 313 | +.. code-block:: python |
| 314 | + :linenos: |
| 315 | +
|
| 316 | + def print_sum_div_first(a, b): |
| 317 | +
|
| 318 | + try: |
| 319 | + sum = a + b |
| 320 | + div = sum / a |
| 321 | + print(div) |
| 322 | +
|
| 323 | + except TypeError as err: |
| 324 | + print(f'{err.__class__.__name__}: ({a}+{b!r})/{a}') |
| 325 | +
|
| 326 | + except ZeroDivisionError as err: |
| 327 | + print(f'{err.__class__.__name__}: ({a}+{b!r})/{a}') |
| 328 | +
|
| 329 | +
|
| 330 | + print_sum_div_first(5, 6) |
| 331 | + print_sum_div_first(3, 'G') |
| 332 | + print_sum_div_first(0, 8) |
| 333 | +
|
| 334 | +
|
| 335 | +.. code-block:: python |
| 336 | + :linenos: |
| 337 | +
|
| 338 | + def print_sum_div_first(a, b): |
| 339 | +
|
| 340 | + try: |
| 341 | + sum = a + b |
| 342 | + div = sum / a |
| 343 | + print(div) |
| 344 | +
|
| 345 | + except (TypeError, ZeroDivisionError) as err: |
| 346 | + print(f'{err.__class__.__name__}: ({a}+{b!r})/{a}') |
| 347 | +
|
| 348 | + print_sum_div_first(5, 6) |
| 349 | + print_sum_div_first(3, 'G') |
| 350 | + print_sum_div_first(0, 8) |
| 351 | +
|
| 352 | +:: |
| 353 | + |
| 354 | + 2.2 |
| 355 | + TypeError: (3+'G')/3 |
| 356 | + ZeroDivisionError: (0+8)/0 |
| 357 | + |
| 358 | +البته چنانچه مایل هستید شی Exception را از طریق یک دستور ``except`` کلی (یعنی بدون ذکر نام Exception خاصی) دریافت کنید، میتوانید از نوع یا کلاس ``Exception`` که در واقع supperclass اکثر Exceptionهای پایتون میباشد، استفاده نمایید: |
| 359 | + |
| 360 | +.. code-block:: python |
| 361 | + :linenos: |
| 362 | +
|
| 363 | + def print_sum_div_first(a, b): |
| 364 | +
|
| 365 | + try: |
| 366 | + sum = a + b |
| 367 | + div = sum / a |
| 368 | + print(div) |
| 369 | +
|
| 370 | + except Exception as err: |
| 371 | + print(f'{err.__class__.__name__}: ({a}+{b!r})/{a}') |
| 372 | +
|
| 373 | + print_sum_div_first(5, 6) |
| 374 | + print_sum_div_first(3, 'G') |
| 375 | + print_sum_div_first(0, 8) |
| 376 | +
|
| 377 | +:: |
| 378 | + |
| 379 | + 2.2 |
| 380 | + TypeError: (3+'G')/3 |
| 381 | + ZeroDivisionError: (0+8)/0 |
| 382 | + |
| 383 | + |
| 384 | +.. tip:: |
| 385 | + |
| 386 | + به صورت کلی وقتی در زمان اجرای دستورات داخل بدنه ``try`` یک Exception رخ میدهد، مفسر پایتون اجرای برنامه را در آن نقطه متوقف و شروع به جستجو برای یافتن یک دستور ``except`` متناسب با آن Exception یا به اصطلاح یک handler برای آن میکند. در صورت پیدا کردن ``except`` مناسب، ادامه روند اجرای برنامه را از آن سر میگیرد و در غیر این صورت Exception بدون handler باعث توقف اجرای کل برنامه میگردد. |
| 387 | + |
| 388 | +.. tip:: |
| 389 | + |
| 390 | + چنانچه از چندین دستور ``except`` بهره میگیرید باید توجه داشته باشید که دستور ``except`` کلی یا همان expression-less except - در صورت وجود - میبایست به عنوان آخرین دستور ``except`` قرار بگیرد، در غیر این صورت دیگر دستورهای ``except`` که نوع Exception در آنها مشخص شده است، فرصت اجرا پیدا نخواهند کرد. |
| 391 | + |
| 392 | +.. tip:: |
| 393 | + |
| 394 | + دو دستور ``except`` زیر از نظر مفسر پایتون به عنوان یک handler برای تمام انواع Exceptionها میباشند و تنها تفاوت آنها در امکان دریافت شی Exception میباشد: |
| 395 | + |
| 396 | + :: |
| 397 | + |
| 398 | + except: |
| 399 | + |
| 400 | + :: |
| 401 | + |
| 402 | + except Exception as error: |
| 403 | + |
| 404 | +.. tip:: |
| 405 | + |
| 406 | + به صورت کلی دستور ``try`` پایتون فاقد یک حوزه یا Scope مجزا میباشد، بنابراین تمامی متغیرهایی که در بدنه دستور ``try`` تعریف میگردند جزیی از حوزه بیرونی خود هستند و در تمام بخشهای داخل آن حوزه در دسترس خواهند بود. البته نباید فراموش کرد که اگر در هنگام انتساب به نام یک متغیر خطایی رخ داده باشد، بدیهی است که آن متغیر ایجاد نشده و اساسا در دسترس نیز نخواهد بود. |
| 407 | + |
| 408 | +.. tip:: |
| 409 | + |
| 410 | + شی Exception که توسط دستور ``except`` دریافت میگردد تنها در داخل بدنه همان دستور ``except`` در دسترس خواهد بود، چرا که بلافاصله پس از اتمام دستورات داخل بدنه آن ``except``، شی مذکور نیز حذف میگردد. |
| 411 | + |
| 412 | + |
| 413 | + |
| 414 | + |
| 415 | + |
| 416 | + |
| 417 | +``try/except/else`` |
| 418 | +------------------------ |
| 419 | + |
| 420 | +در کنار دستور ``try/except`` میتوان دستور ``else`` را نیز استفاده کرد. کاربرد این دستور این است که میتوان قطعه کدی را برای مواقعی که اجرای بخش ``try`` به پایان رسیده و هیچ Exception رخ نداده باشد، به اجرا درآوریم: |
| 421 | + |
| 422 | + |
| 423 | +.. code-block:: python |
| 424 | + :linenos: |
| 425 | +
|
| 426 | + def print_sum_div_first(a, b): |
| 427 | +
|
| 428 | + try: |
| 429 | + sum = a + b |
| 430 | + div = sum / a |
| 431 | +
|
| 432 | + except Exception as err: |
| 433 | + print(f'{err.__class__.__name__}: ({a}+{b!r})/{a}') |
| 434 | +
|
| 435 | + else: |
| 436 | + print(f'result: ({a}+{b!r})/{a} = {div}') |
| 437 | +
|
| 438 | + print_sum_div_first(5, 6) |
| 439 | + print_sum_div_first(3, 'G') |
| 440 | + print_sum_div_first(0, 8) |
| 441 | +
|
| 442 | +:: |
| 443 | + |
| 444 | + result: (5+6)/5 = 2.2 |
| 445 | + TypeError: (3+'G')/3 |
| 446 | + ZeroDivisionError: (0+8)/0 |
| 447 | + |
| 448 | +به یک مثال دیگر نیز توجه نماید (مرتبط با مبحث فایلها - درس دهم): |
| 449 | + |
| 450 | +.. code-block:: python |
| 451 | + :linenos: |
| 452 | +
|
| 453 | + def write_to_log(text, write_mode): |
| 454 | + try: |
| 455 | + output = open('log_file.txt', write_mode) |
| 456 | + output.write(text) |
| 457 | +
|
| 458 | + except FileNotFoundError as fnfe: |
| 459 | + print('File Not Found!!!') |
| 460 | +
|
| 461 | + else: |
| 462 | + output.close() |
| 463 | + print('Successful, closed!') |
| 464 | +
|
| 465 | +
|
| 466 | + write_to_log('A text to insert in the log file', 'r') # WRONG mode! |
| 467 | + print('*' * 30) |
| 468 | + write_to_log('A text to insert in the log file', 'a') |
| 469 | +
|
| 470 | +:: |
| 471 | + |
| 472 | + File Not Found!!! |
| 473 | + ****************************** |
| 474 | + Successful, closed! |
| 475 | + |
| 476 | + |
| 477 | + |
| 478 | +``try/finally`` ``try/except/finally`` ``try/except/else/finally`` |
| 479 | +--------------------------------------------------------------------------------------------- |
| 480 | + |
| 481 | +دستور ``finally`` نیز یک دستور اختیاری مشابه با ``else`` میباشد که میتوان از آن در کنار دستور ``try`` بهره گرفت. با استفاده از این دستور میتوان یک قطعه کد را مهیا کرد که چه در حالتی که Exceptionای داخل ``try`` رخ دهد و چه ندهد اجرا شود. در واقع دستورات موجود در بدنه دستور ``finally`` تحت هر شرایطی اجرا میشوند. |
| 482 | + |
| 483 | +اکنون میتوان روند کلی فرآیند اجرای دستورات پایتون در یک بلاک ``try`` را به این صورت شرح داد: |
| 484 | + |
| 485 | +**۱) در صورت عدم بروز Exception** داخل بدنه دستور ``try``: پس از پایان اجرای دستورات داخل بدنه دستور ``try``، نقطه اجرای برنامه به دستور ``else`` - در صورت وجود - سپرده میشود، پس از پایان اجرای دستورات داخل بدنه ``else``، نقطه اجرای برنامه به دستور ``finally`` - در صورت وجود - سپرده میشود. |
| 486 | + |
| 487 | +**۲) در صورت بروز Exception** داخل بدنه دستور ``try``: نقطه اجرای برنامه بلافاصله به دستور ``except`` مناسب سپرده میشود، پس از پایان اجرای دستورات داخل بدنه ``except``، نقطه اجرای برنامه به دستور ``finally`` - در صورت وجود - سپرده میشود. |
| 488 | + |
| 489 | + |
| 490 | + |
| 491 | + |
| 492 | +.. code-block:: python |
| 493 | + :linenos: |
| 494 | +
|
| 495 | + def print_sum_div_first(a, b): |
| 496 | + try: |
| 497 | + print('----> try') |
| 498 | + sum = a + b |
| 499 | + div = sum / a |
| 500 | +
|
| 501 | + except Exception as err: |
| 502 | + print('----> except') |
| 503 | +
|
| 504 | + else: |
| 505 | + print('----> else') |
| 506 | +
|
| 507 | + finally: |
| 508 | + print('----> finally') |
| 509 | +
|
| 510 | +
|
| 511 | + print_sum_div_first(5, 6) |
| 512 | + print('*' * 20) |
| 513 | + print_sum_div_first(3, 'G') |
| 514 | +
|
| 515 | +:: |
| 516 | + |
| 517 | + ----> try |
| 518 | + ----> else |
| 519 | + ----> finally |
| 520 | + ******************** |
| 521 | + ----> try |
| 522 | + ----> except |
| 523 | + ----> finally |
| 524 | + |
| 525 | +کاربرد اصلی دستور ``finally`` تمیزکاری یا Cleaning Up کردن کد پس از انجام کاری مشخص است. |
| 526 | + |
| 527 | + |
| 528 | + |
174 | 529 |
|
175 | 530 |
|
176 | 531 | | |
|
0 commit comments