8. ਗਲਤੀਆਂ ਅਤੇ ਅਪਵਾਦ

ਹੁਣ ਤੱਕ ਗਲਤੀ ਸੁਨੇਹਿਆਂ ਦਾ ਸਿਰਫ਼ ਜ਼ਿਕਰ ਹੀ ਕੀਤਾ ਗਿਆ ਹੈ, ਪਰ ਜੇ ਤੁਸੀਂ ਉਦਾਹਰਨਾਂ ਅਜ਼ਮਾਈਆਂ ਹਨ ਤਾਂ ਸ਼ਾਇਦ ਤੁਸੀਂ ਕੁਝ ਵੇਖੇ ਹੋਣਗੇ। (ਘੱਟੋ-ਘੱਟ) ਦੋ ਵੱਖ-ਵੱਖ ਕਿਸਮਾਂ ਦੀਆਂ ਗਲਤੀਆਂ ਹਨ: ਸਿੰਟੈਕਸ ਗਲਤੀਆਂ ਅਤੇ ਅਪਵਾਦ।

8.1. ਸਿੰਟੈਕਸ ਗਲਤੀਆਂ

ਸਿੰਟੈਕਸ ਗਲਤੀਆਂ, ਜਿਨ੍ਹਾਂ ਨੂੰ parsing errors ਵੀ ਕਿਹਾ ਜਾਂਦਾ ਹੈ, ਸ਼ਾਇਦ Python ਸਿੱਖਣ ਦੌਰਾਨ ਤੁਹਾਨੂੰ ਸਭ ਤੋਂ ਵੱਧ ਮਿਲਣ ਵਾਲੀ ਗਲਤੀ ਦੀ ਕਿਸਮ ਹੁੰਦੀਆਂ ਹਨ:

>>> while True print('Hello world')
  File "<stdin>", line 1
    while True print('Hello world')
               ^^^^^
SyntaxError: invalid syntax

ਪਾਰਸਰ ਗਲਤੀ ਵਾਲੀ ਲਾਈਨ ਨੂੰ ਦੁਬਾਰਾ ਦਿਖਾਉਂਦਾ ਹੈ ਅਤੇ ਛੋਟੇ ਤੀਰਾਂ ਰਾਹੀਂ ਉਸ ਥਾਂ ਵੱਲ ਇਸ਼ਾਰਾ ਕਰਦਾ ਹੈ ਜਿੱਥੇ ਗਲਤੀ ਦਾ ਪਤਾ ਲੱਗਿਆ ਸੀ। ਧਿਆਨ ਰੱਖੋ ਕਿ ਇਹ ਹਮੇਸ਼ਾਂ ਉਹੀ ਥਾਂ ਨਹੀਂ ਹੁੰਦੀ ਜਿਸਨੂੰ ਠੀਕ ਕਰਨ ਦੀ ਲੋੜ ਹੋਵੇ। ਇਸ ਉਦਾਹਰਨ ਵਿੱਚ ਗਲਤੀ print() ਫੰਕਸ਼ਨ ਕੋਲ ਪਾਈ ਗਈ ਹੈ ਕਿਉਂਕਿ ਇਸ ਤੋਂ ਪਹਿਲਾਂ ਇੱਕ ਕੋਲਨ (':') ਗਾਇਬ ਹੈ।

ਫਾਇਲ ਦਾ ਨਾਮ (ਸਾਡੀ ਉਦਾਹਰਨ ਵਿੱਚ <stdin>) ਅਤੇ ਲਾਈਨ ਨੰਬਰ ਵੀ ਦਿਖਾਏ ਜਾਂਦੇ ਹਨ ਤਾਂ ਜੋ ਜੇ ਇਨਪੁੱਟ ਕਿਸੇ ਫਾਇਲ ਤੋਂ ਆਈ ਹੋਵੇ ਤਾਂ ਤੁਹਾਨੂੰ ਪਤਾ ਹੋਵੇ ਕਿੱਥੇ ਵੇਖਣਾ ਹੈ।

8.2. ਐਕਸੈਪਸ਼ਨ

ਭਾਵੇਂ ਕੋਈ ਕਥਨ ਜਾਂ ਅਭਿਵ੍ਯੰਜਨਾ ਸਿੰਟੈਕਸ ਅਨੁਸਾਰ ਸਹੀ ਹੋਵੇ, ਫਿਰ ਵੀ ਉਸਨੂੰ ਚਲਾਉਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਦੌਰਾਨ ਗਲਤੀ ਆ ਸਕਦੀ ਹੈ। ਚਲਾਉਣ ਸਮੇਂ ਮਿਲਣ ਵਾਲੀਆਂ ਗਲਤੀਆਂ ਨੂੰ ਅਪਵਾਦ ਕਿਹਾ ਜਾਂਦਾ ਹੈ ਅਤੇ ਇਹ ਹਮੇਸ਼ਾਂ ਪ੍ਰੋਗਰਾਮ ਨੂੰ ਬੰਦ ਨਹੀਂ ਕਰਦੀਆਂ। ਤੁਸੀਂ ਜਲਦੀ ਹੀ ਸਿੱਖੋਗੇ ਕਿ Python ਪ੍ਰੋਗਰਾਮਾਂ ਵਿੱਚ ਇਹਨਾਂ ਨੂੰ ਕਿਵੇਂ ਸੰਭਾਲਿਆ ਜਾਂਦਾ ਹੈ। ਹਾਲਾਂਕਿ, ਜ਼ਿਆਦਾਤਰ ਅਪਵਾਦ ਪ੍ਰੋਗਰਾਮ ਦੁਆਰਾ ਸੰਭਾਲੇ ਨਹੀਂ ਜਾਂਦੇ ਅਤੇ ਹੇਠਾਂ ਦਿੱਤੇ ਤਰ੍ਹਾਂ ਗਲਤੀ ਸੁਨੇਹੇ ਪੈਦਾ ਕਰਦੇ ਹਨ:

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

ਗਲਤੀ ਸੁਨੇਹੇ ਦੀ ਆਖਰੀ ਲਾਈਨ ਦੱਸਦੀ ਹੈ ਕਿ ਕੀ ਹੋਇਆ। ਅਪਵਾਦ ਵੱਖ-ਵੱਖ ਕਿਸਮਾਂ ਦੇ ਹੁੰਦੇ ਹਨ ਅਤੇ ਉਹਨਾਂ ਦੀ ਕਿਸਮ ਸੁਨੇਹੇ ਦੇ ਹਿੱਸੇ ਵਜੋਂ ਦਿਖਾਈ ਜਾਂਦੀ ਹੈ। ਇਸ ਉਦਾਹਰਨ ਵਿੱਚ ZeroDivisionError, NameError ਅਤੇ TypeError ਹਨ। ਅਪਵਾਦ ਦੀ ਕਿਸਮ ਵਜੋਂ ਦਿਖਾਈ ਗਈ ਸਟਰਿੰਗ ਉਸ built-in ਅਪਵਾਦ ਦਾ ਨਾਮ ਹੁੰਦੀ ਹੈ ਜੋ ਵਾਪਰਿਆ ਸੀ। ਇਹ ਸਾਰੇ built-in ਅਪਵਾਦਾਂ ਲਈ ਸਹੀ ਹੈ, ਹਾਲਾਂਕਿ user-defined ਅਪਵਾਦਾਂ ਲਈ ਲਾਜ਼ਮੀ ਨਹੀਂ (ਪਰ ਇਹ ਇੱਕ ਲਾਭਦਾਇਕ ਰਿਵਾਜ ਹੈ)। ਮਿਆਰੀ ਅਪਵਾਦ ਨਾਮ built-in identifiers ਹੁੰਦੇ ਹਨ (reserved keywords ਨਹੀਂ)।

ਲਾਈਨ ਦਾ ਬਾਕੀ ਹਿੱਸਾ ਅਪਵਾਦ ਦੀ ਕਿਸਮ ਅਤੇ ਉਸਦੇ ਕਾਰਨ ਬਾਰੇ ਹੋਰ ਵੇਰਵਾ ਦਿੰਦਾ ਹੈ।

ਗਲਤੀ ਸੁਨੇਹੇ ਦਾ ਪਹਿਲਾਂ ਵਾਲਾ ਹਿੱਸਾ ਉਸ ਸੰਦਰਭ ਨੂੰ ਦਰਸਾਉਂਦਾ ਹੈ ਜਿੱਥੇ ਅਪਵਾਦ ਵਾਪਰਿਆ ਸੀ, ਜਿਸਨੂੰ ਸਟੈਕ ਟ੍ਰੇਸਬੈਕ ਦੇ ਰੂਪ ਵਿੱਚ ਦਰਸਾਇਆ ਜਾਂਦਾ ਹੈ। ਆਮ ਤੌਰ 'ਤੇ ਇਸ ਵਿੱਚ ਸਰੋਤ ਕੋਡ ਦੀਆਂ ਲਾਈਨਾਂ ਦੀ ਸੂਚੀ ਹੁੰਦੀ ਹੈ, ਪਰ standard input ਤੋਂ ਪੜ੍ਹੀਆਂ ਗਈਆਂ ਲਾਈਨਾਂ ਨਹੀਂ ਦਿਖਾਈਆਂ ਜਾਂਦੀਆਂ।

ਬਿਲਟ-ਇਨ ਐਕਸੈਪਸ਼ਨ ਵਿੱਚ built-in exceptions ਅਤੇ ਉਹਨਾਂ ਦੇ ਅਰਥਾਂ ਦੀ ਸੂਚੀ ਦਿੱਤੀ ਗਈ ਹੈ।

8.3. ਅਪਵਾਦਾਂ ਨੂੰ ਸੰਭਾਲਣਾ

ਅਜਿਹੇ ਪ੍ਰੋਗਰਾਮ ਲਿਖੇ ਜਾ ਸਕਦੇ ਹਨ ਜੋ ਚੁਣੇ ਹੋਏ ਅਪਵਾਦਾਂ ਨੂੰ ਸੰਭਾਲ ਸਕਣ। ਹੇਠਾਂ ਦਿੱਤੀ ਉਦਾਹਰਨ ਵੇਖੋ, ਜੋ ਵਰਤੋਂਕਾਰ ਤੋਂ ਇਨਪੁੱਟ ਮੰਗਦੀ ਰਹਿੰਦੀ ਹੈ ਜਦ ਤੱਕ ਇੱਕ ਵੈਧ ਪੂਰਨ ਅੰਕ ਦਰਜ ਨਾ ਕੀਤਾ ਜਾਵੇ, ਪਰ ਨਾਲ ਹੀ ਵਰਤੋਂਕਾਰ ਨੂੰ ਪ੍ਰੋਗਰਾਮ ਵਿੱਚ ਰੁਕਾਵਟ ਪੈਦਾ ਕਰਨ ਦੀ ਆਗਿਆ ਵੀ ਦਿੰਦੀ ਹੈ (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 clause (ਅਰਥਾਤ try ਅਤੇ except ਕੀਵਰਡਾਂ ਦੇ ਵਿਚਕਾਰ ਮੌਜੂਦ ਕਥਨਾਂ) ਨੂੰ ਚਲਾਇਆ ਜਾਂਦਾ ਹੈ।

  • ਜੇ ਕੋਈ exception ਨਹੀਂ ਵਾਪਰਦੀ ਤਾਂ except clause ਨੂੰ ਛੱਡ ਦਿੱਤਾ ਜਾਂਦਾ ਹੈ ਅਤੇ try ਸਟੇਟਮੈਂਟ ਦੀ ਕਾਰਵਾਈ ਪੂਰੀ ਹੋ ਜਾਂਦੀ ਹੈ।

  • ਜੇ try clause ਦੀ ਕਾਰਵਾਈ ਦੌਰਾਨ ਕੋਈ exception ਵਾਪਰਦੀ ਹੈ ਤਾਂ ਬਾਕੀ clause ਨੂੰ ਛੱਡ ਦਿੱਤਾ ਜਾਂਦਾ ਹੈ। ਫਿਰ ਜੇ exception ਦੀ ਕਿਸਮ except ਤੋਂ ਬਾਅਦ ਦਿੱਤੀ exception ਨਾਲ ਮਿਲਦੀ ਹੈ ਤਾਂ except clause ਚਲਾਇਆ ਜਾਂਦਾ ਹੈ ਅਤੇ ਇਸ ਤੋਂ ਬਾਅਦ ਕਾਰਵਾਈ try/except ਬਲਾਕ ਤੋਂ ਅੱਗੇ ਜਾਰੀ ਰਹਿੰਦੀ ਹੈ।

  • ਜੇ ਕੋਈ ਅਜਿਹਾ ਅਪਵਾਦ ਵਾਪਰਦਾ ਹੈ ਜੋ except clause ਵਿੱਚ ਦਿੱਤੇ ਅਪਵਾਦ ਨਾਲ ਨਹੀਂ ਮਿਲਦਾ, ਤਾਂ ਇਸਨੂੰ ਬਾਹਰੀ try ਕਥਨਾਂ ਵੱਲ ਭੇਜਿਆ ਜਾਂਦਾ ਹੈ। ਜੇ ਕੋਈ handler ਨਹੀਂ ਮਿਲਦਾ ਤਾਂ ਇਹ ਇੱਕ unhandled exception ਮੰਨਿਆ ਜਾਂਦਾ ਹੈ ਅਤੇ ਪ੍ਰੋਗਰਾਮ ਗਲਤੀ ਸੁਨੇਹੇ ਨਾਲ ਰੁਕ ਜਾਂਦਾ ਹੈ।

ਇੱਕ try ਸਟੇਟਮੈਂਟ ਵਿੱਚ ਇੱਕ ਤੋਂ ਵੱਧ except clause ਹੋ ਸਕਦੇ ਹਨ ਤਾਂ ਜੋ ਵੱਖ-ਵੱਖ ਅਪਵਾਦਾਂ ਲਈ ਹੈਂਡਲਰ ਨਿਰਧਾਰਤ ਕੀਤੇ ਜਾ ਸਕਣ। ਵੱਧ ਤੋਂ ਵੱਧ ਇੱਕ ਹੀ ਹੈਂਡਲਰ ਚਲਾਇਆ ਜਾਂਦਾ ਹੈ। ਹੈਂਡਲਰ ਸਿਰਫ਼ ਉਸੇ try clause ਵਿੱਚ ਵਾਪਰਨ ਵਾਲੇ ਅਪਵਾਦਾਂ ਨੂੰ ਸੰਭਾਲਦੇ ਹਨ, ਉਸੇ try ਸਟੇਟਮੈਂਟ ਦੇ ਹੋਰ ਹੈਂਡਲਰਾਂ ਵਿੱਚ ਵਾਪਰਨ ਵਾਲੇ ਅਪਵਾਦਾਂ ਨੂੰ ਨਹੀਂ। ਇੱਕ except clause ਕਈ ਅਪਵਾਦਾਂ ਦੇ ਨਾਮ ਵੀ ਦੇ ਸਕਦਾ ਹੈ, ਉਦਾਹਰਨ ਵਜੋਂ:

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

except clause ਵਿੱਚ ਦਿੱਤੀ ਕਲਾਸ ਉਹਨਾਂ ਅਪਵਾਦਾਂ ਨਾਲ ਮੇਲ ਖਾਂਦੀ ਹੈ ਜੋ ਉਸ ਕਲਾਸ ਜਾਂ ਉਸ ਦੀਆਂ ਵਿਉਤਪੰਨ ਕਲਾਸਾਂ ਦੇ ਇੰਸਟੈਂਸ ਹੁੰਦੇ ਹਨ (ਪਰ ਇਸਦਾ ਉਲਟ ਸਹੀ ਨਹੀਂ ਹੈ — ਵਿਉਤਪੰਨ ਕਲਾਸ ਵਾਲਾ except clause ਬੇਸ ਕਲਾਸ ਦੇ ਇੰਸਟੈਂਸ ਨਾਲ ਮੇਲ ਨਹੀਂ ਖਾਂਦਾ)। ਉਦਾਹਰਨ ਵਜੋਂ, ਹੇਠਾਂ ਦਿੱਤਾ ਕੋਡ 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 clauses ਦਾ ਕ੍ਰਮ ਉਲਟ ਦਿੱਤਾ ਜਾਵੇ (ਅਰਥਾਤ except B ਸਭ ਤੋਂ ਪਹਿਲਾਂ ਹੋਵੇ), ਤਾਂ ਨਤੀਜਾ B, B, B ਆਉਂਦਾ ਕਿਉਂਕਿ ਸਭ ਤੋਂ ਪਹਿਲਾਂ ਮੇਲ ਖਾਣ ਵਾਲਾ except clause ਹੀ ਚਲਾਇਆ ਜਾਂਦਾ ਹੈ।

ਜਦੋਂ ਕੋਈ ਅਪਵਾਦ ਵਾਪਰਦਾ ਹੈ, ਤਾਂ ਉਸ ਨਾਲ ਕੁਝ ਮੁੱਲ ਜੁੜੇ ਹੋ ਸਕਦੇ ਹਨ ਜਿਨ੍ਹਾਂ ਨੂੰ ਅਪਵਾਦ ਦੇ ਆਰਗੂਮੈਂਟ ਵੀ ਕਿਹਾ ਜਾਂਦਾ ਹੈ। ਆਰਗੂਮੈਂਟਾਂ ਦੀ ਮੌਜੂਦਗੀ ਅਤੇ ਕਿਸਮ ਅਪਵਾਦ ਦੀ ਕਿਸਮ ਉੱਤੇ ਨਿਰਭਰ ਕਰਦੀ ਹੈ।

except clause ਅਪਵਾਦ ਦੇ ਨਾਮ ਤੋਂ ਬਾਅਦ ਇੱਕ ਵੇਰੀਏਬਲ ਵੀ ਨਿਰਧਾਰਤ ਕਰ ਸਕਦਾ ਹੈ। ਇਹ ਵੇਰੀਏਬਲ ਅਪਵਾਦ ਇੰਸਟੈਂਸ ਨਾਲ ਜੁੜ ਜਾਂਦਾ ਹੈ, ਜਿਸ ਵਿੱਚ ਆਮ ਤੌਰ 'ਤੇ args ਐਟ੍ਰਿਬਿਊਟ ਹੁੰਦਾ ਹੈ ਜੋ ਆਰਗੂਮੈਂਟਾਂ ਨੂੰ ਸੰਭਾਲਦਾ ਹੈ। ਸੁਵਿਧਾ ਲਈ built-in ਅਪਵਾਦ ਕਿਸਮਾਂ __str__() ਨੂੰ ਪਰਿਭਾਸ਼ਿਤ ਕਰਦੀਆਂ ਹਨ ਤਾਂ ਜੋ .args ਨੂੰ ਸਿੱਧਾ ਐਕਸੈਸ ਕੀਤੇ ਬਿਨਾਂ ਸਾਰੇ ਆਰਗੂਮੈਂਟ ਪ੍ਰਿੰਟ ਕੀਤੇ ਜਾ ਸਕਣ।

>>> try:
...     raise Exception('spam', 'eggs')
... except Exception as inst:
...     print(type(inst))    # exception ਦੀ ਕਿਸਮ
...     print(inst.args)     # .args ਵਿੱਚ ਸੰਭਾਲੇ ਆਰਗੂਮੈਂਟ
...     print(inst)          # __str__ ਨਾਲ args ਸਿੱਧੇ ਪ੍ਰਿੰਟ ਕੀਤੇ ਜਾ ਸਕਦੇ ਹਨ,
...                          # ਪਰ exception subclasses ਵਿੱਚ ਇਸਨੂੰ override ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ
...     x, y = inst.args     # args ਨੂੰ unpack ਕਰੋ
...     print('x =', x)
...     print('y =', y)
...
<class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs

ਬਿਨਾਂ ਸੰਭਾਲੀਆਂ exceptions ਲਈ ਸੁਨੇਹੇ ਦੇ ਆਖਰੀ ਹਿੱਸੇ ('detail') ਵਜੋਂ exception ਦੀ __str__() ਆਉਟਪੁੱਟ ਪ੍ਰਿੰਟ ਕੀਤੀ ਜਾਂਦੀ ਹੈ।

BaseException ਸਾਰੀਆਂ exceptions ਦੀ ਸਾਂਝੀ base class ਹੈ। ਇਸ ਦੀ ਇੱਕ subclass Exception ਹੈ, ਜੋ ਸਾਰੀਆਂ ਗੈਰ-ਘਾਤਕ exceptions ਦੀ base class ਹੈ। ਜਿਹੜੀਆਂ exceptions Exception ਦੀਆਂ subclasses ਨਹੀਂ ਹੁੰਦੀਆਂ, ਉਹ ਆਮ ਤੌਰ 'ਤੇ handle ਨਹੀਂ ਕੀਤੀਆਂ ਜਾਂਦੀਆਂ ਕਿਉਂਕਿ ਉਹ ਇਸ ਗੱਲ ਦਾ ਸੰਕੇਤ ਦਿੰਦੀਆਂ ਹਨ ਕਿ ਪ੍ਰੋਗਰਾਮ ਨੂੰ ਸਮਾਪਤ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ। ਇਨ੍ਹਾਂ ਵਿੱਚ SystemExit ਸ਼ਾਮਲ ਹੈ ਜੋ sys.exit() ਦੁਆਰਾ raise ਕੀਤੀ ਜਾਂਦੀ ਹੈ ਅਤੇ KeyboardInterrupt ਜੋ ਵਰਤੋਂਕਾਰ ਵੱਲੋਂ ਪ੍ਰੋਗਰਾਮ ਵਿੱਚ ਰੁਕਾਵਟ ਪੈਦਾ ਕਰਨ ਸਮੇਂ raise ਕੀਤੀ ਜਾਂਦੀ ਹੈ।

Exception ਨੂੰ ਇੱਕ wildcard ਵਜੋਂ ਵਰਤਿਆ ਜਾ ਸਕਦਾ ਹੈ ਜੋ (ਲਗਭਗ) ਹਰ exception ਨੂੰ ਪਕੜ ਲੈਂਦਾ ਹੈ। ਹਾਲਾਂਕਿ, ਇਹ ਚੰਗੀ ਪ੍ਰਥਾ ਮੰਨੀ ਜਾਂਦੀ ਹੈ ਕਿ ਅਸੀਂ ਉਹਨਾਂ exceptions ਦੀਆਂ ਕਿਸਮਾਂ ਬਾਰੇ ਜਿੰਨਾ ਸੰਭਵ ਹੋ ਸਕੇ ਉਤਨੇ ਖਾਸ ਹੋਈਏ ਜਿਨ੍ਹਾਂ ਨੂੰ ਅਸੀਂ ਸੰਭਾਲਣਾ ਚਾਹੁੰਦੇ ਹਾਂ, ਅਤੇ ਅਣਉਮੀਦ exceptions ਨੂੰ ਅੱਗੇ ਪ੍ਰਸਾਰਿਤ ਹੋਣ ਦੇਈਏ।

Exception ਨੂੰ ਸੰਭਾਲਣ ਦਾ ਸਭ ਤੋਂ ਆਮ ਤਰੀਕਾ ਇਹ ਹੈ ਕਿ exception ਨੂੰ ਪ੍ਰਿੰਟ ਜਾਂ log ਕੀਤਾ ਜਾਵੇ ਅਤੇ ਫਿਰ ਇਸਨੂੰ ਦੁਬਾਰਾ raise ਕੀਤਾ ਜਾਵੇ (ਤਾਂ ਜੋ ਕਾਲ ਕਰਨ ਵਾਲਾ ਕੋਡ ਵੀ ਇਸਨੂੰ ਸੰਭਾਲ ਸਕੇ):

import sys

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

try ... except ਸਟੇਟਮੈਂਟ ਵਿੱਚ ਇੱਕ ਵਿਕਲਪਿਕ else clause ਵੀ ਹੋ ਸਕਦਾ ਹੈ, ਜੋ ਮੌਜੂਦ ਹੋਣ ਦੀ ਸਥਿਤੀ ਵਿੱਚ ਸਾਰੇ except clauses ਤੋਂ ਬਾਅਦ ਆਉਣਾ ਲਾਜ਼ਮੀ ਹੈ। ਇਹ ਉਸ ਕੋਡ ਲਈ ਲਾਭਦਾਇਕ ਹੈ ਜੋ ਤਦ ਹੀ ਚਲਾਇਆ ਜਾਣਾ ਚਾਹੀਦਾ ਹੈ ਜਦੋਂ try clause ਕੋਈ exception raise ਨਾ ਕਰੇ। ਉਦਾਹਰਨ ਵਜੋਂ:

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 clause ਦੀ ਵਰਤੋਂ try clause ਵਿੱਚ ਵਾਧੂ ਕੋਡ ਸ਼ਾਮਲ ਕਰਨ ਨਾਲੋਂ ਬਿਹਤਰ ਹੈ, ਕਿਉਂਕਿ ਇਹ ਗਲਤੀ ਨਾਲ ਅਜਿਹੀ exception ਨੂੰ ਪਕੜਨ ਤੋਂ ਬਚਾਉਂਦੀ ਹੈ ਜੋ try ... except ਸਟੇਟਮੈਂਟ ਦੁਆਰਾ ਸੁਰੱਖਿਅਤ ਕੀਤੇ ਕੋਡ ਤੋਂ ਨਹੀਂ ਆਈ ਸੀ।

Exception handlers ਸਿਰਫ਼ ਉਹ exceptions ਨਹੀਂ ਸੰਭਾਲਦੇ ਜੋ ਸਿੱਧੇ try clause ਵਿੱਚ ਵਾਪਰਦੀਆਂ ਹਨ, ਸਗੋਂ ਉਹਨਾਂ functions ਦੇ ਅੰਦਰ ਵਾਪਰਨ ਵਾਲੀਆਂ exceptions ਨੂੰ ਵੀ ਸੰਭਾਲਦੇ ਹਨ ਜਿਨ੍ਹਾਂ ਨੂੰ try clause ਦੇ ਅੰਦਰ (ਸਿੱਧੇ ਜਾਂ ਅਪਰੋਕਸ਼ ਤੌਰ 'ਤੇ) ਕਾਲ ਕੀਤਾ ਗਿਆ ਹੋਵੇ। ਉਦਾਹਰਨ ਵਜੋਂ:

>>> 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

8.4. Exceptions ਰੇਜ਼ ਕਰਨਾ

raise ਸਟੇਟਮੈਂਟ ਪ੍ਰੋਗਰਾਮਰ ਨੂੰ ਕਿਸੇ ਨਿਰਧਾਰਤ exception ਨੂੰ ਜਾਣ-ਬੁੱਝ ਕੇ ਵਾਪਰਣ ਲਈ ਮਜਬੂਰ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ। ਉਦਾਹਰਨ ਵਜੋਂ:

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

raise ਦਾ ਇਕੱਲਾ argument ਉਸ exception ਨੂੰ ਦਰਸਾਉਂਦਾ ਹੈ ਜਿਸਨੂੰ raise ਕਰਨਾ ਹੈ। ਇਹ ਜਾਂ ਤਾਂ ਇੱਕ exception instance ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ ਜਾਂ ਇੱਕ exception class (ਅਜਿਹੀ class ਜੋ BaseException ਤੋਂ derived ਹੋਵੇ, ਜਿਵੇਂ Exception ਜਾਂ ਇਸ ਦੀਆਂ subclasses)। ਜੇ ਇੱਕ exception class ਦਿੱਤੀ ਜਾਂਦੀ ਹੈ, ਤਾਂ ਇਸਦਾ constructor ਬਿਨਾਂ arguments ਦੇ ਆਪੇ ਕਾਲ ਕਰਕੇ instance ਬਣਾਇਆ ਜਾਂਦਾ ਹੈ:

raise ValueError  # 'raise ValueError()' ਦਾ ਸੰਖੇਪ ਰੂਪ

ਜੇ ਤੁਹਾਨੂੰ ਇਹ ਜਾਣਨਾ ਹੋਵੇ ਕਿ ਕੋਈ exception raise ਹੋਈ ਸੀ ਜਾਂ ਨਹੀਂ, ਪਰ ਤੁਸੀਂ ਉਸਨੂੰ ਸੰਭਾਲਣਾ ਨਹੀਂ ਚਾਹੁੰਦੇ, ਤਾਂ raise ਸਟੇਟਮੈਂਟ ਦਾ ਇੱਕ ਸਧਾਰਣ ਰੂਪ exception ਨੂੰ ਦੁਬਾਰਾ 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>
    raise NameError('HiThere')
NameError: HiThere

8.5. ਅਪਵਾਦ ਚੇਨਿੰਗ

ਜੇ except ਭਾਗ ਦੇ ਅੰਦਰ ਕੋਈ unhandled exception ਵਾਪਰਦਾ ਹੈ, ਤਾਂ ਉਸਦੇ ਨਾਲ ਪਹਿਲਾਂ ਸੰਭਾਲਿਆ ਜਾ ਰਿਹਾ ਅਪਵਾਦ ਵੀ ਜੁੜਿਆ ਹੋਇਆ ਹੋਵੇਗਾ ਅਤੇ ਗਲਤੀ ਸੁਨੇਹੇ ਵਿੱਚ ਸ਼ਾਮਲ ਕੀਤਾ ਜਾਵੇਗਾ:

>>> try:
...     open("database.sqlite")
... except OSError:
...     raise RuntimeError("unable to handle error")
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
    open("database.sqlite")
    ~~~~^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'database.sqlite'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
    raise RuntimeError("unable to handle error")
RuntimeError: unable to handle error

ਇਹ ਦਰਸਾਉਣ ਲਈ ਕਿ ਇੱਕ exception ਕਿਸੇ ਹੋਰ exception ਦਾ ਸਿੱਧਾ ਨਤੀਜਾ ਹੈ, raise ਸਟੇਟਮੈਂਟ ਇੱਕ ਵਿਕਲਪਿਕ from clause ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ:

# exc ਇੱਕ exception instance ਜਾਂ None ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ।
raise RuntimeError from exc

ਇਹ ਉਸ ਵੇਲੇ ਲਾਭਦਾਇਕ ਹੋ ਸਕਦਾ ਹੈ ਜਦੋਂ ਤੁਸੀਂ exceptions ਨੂੰ ਰੂਪਾਂਤਰਿਤ ਕਰ ਰਹੇ ਹੋਵੋ। ਉਦਾਹਰਨ ਵਜੋਂ:

>>> def func():
...     raise ConnectionError
...
>>> try:
...     func()
... except ConnectionError as exc:
...     raise RuntimeError('Failed to open database') from exc
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
    func()
    ~~~~^^
  File "<stdin>", line 2, in func
ConnectionError

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
    raise RuntimeError('Failed to open database') from exc
RuntimeError: Failed to open database

ਇਹ from None ਤਰੀਕੇ ਦੀ ਵਰਤੋਂ ਕਰਕੇ automatic exception chaining ਨੂੰ ਅਯੋਗ ਕਰਨ ਦੀ ਵੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ:

>>> try:
...     open('database.sqlite')
... except OSError:
...     raise RuntimeError from None
...
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
    raise RuntimeError from None
RuntimeError

Chaining ਦੇ ਮਕੈਨਿਜ਼ਮ ਬਾਰੇ ਹੋਰ ਜਾਣਕਾਰੀ ਲਈ ਬਿਲਟ-ਇਨ ਐਕਸੈਪਸ਼ਨ ਵੇਖੋ।

8.6. ਵਰਤੋਂਕਾਰ-ਪਰਿਭਾਸ਼ਿਤ Exceptions

ਪ੍ਰੋਗਰਾਮ ਇੱਕ ਨਵੀਂ ਅਪਵਾਦ ਕਲਾਸ ਬਣਾਕੇ ਆਪਣੇ ਅਪਵਾਦ ਪਰਿਭਾਸ਼ਿਤ ਕਰ ਸਕਦੇ ਹਨ (ਕਲਾਸਾਂ ਵਿੱਚ Python ਕਲਾਸਾਂ ਬਾਰੇ ਹੋਰ ਜਾਣਕਾਰੀ ਵੇਖੋ)। ਅਪਵਾਦ ਆਮ ਤੌਰ 'ਤੇ Exception ਕਲਾਸ ਤੋਂ ਸਿੱਧੇ ਜਾਂ ਅਪਰੋਕਸ਼ ਤੌਰ 'ਤੇ derived ਹੋਣੇ ਚਾਹੀਦੇ ਹਨ।

ਅਪਵਾਦ ਕਲਾਸਾਂ ਨੂੰ ਇਸ ਤਰ੍ਹਾਂ ਪਰਿਭਾਸ਼ਿਤ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ ਕਿ ਉਹ ਉਹ ਸਭ ਕੁਝ ਕਰ ਸਕਣ ਜੋ ਹੋਰ ਕਲਾਸਾਂ ਕਰ ਸਕਦੀਆਂ ਹਨ, ਪਰ ਆਮ ਤੌਰ 'ਤੇ ਉਹਨਾਂ ਨੂੰ ਸਧਾਰਣ ਰੱਖਿਆ ਜਾਂਦਾ ਹੈ ਅਤੇ ਉਹ ਸਿਰਫ਼ ਕੁਝ ਐਟ੍ਰਿਬਿਊਟ ਪ੍ਰਦਾਨ ਕਰਦੀਆਂ ਹਨ ਜਿਨ੍ਹਾਂ ਰਾਹੀਂ handlers ਗਲਤੀ ਬਾਰੇ ਜਾਣਕਾਰੀ ਪ੍ਰਾਪਤ ਕਰ ਸਕਦੇ ਹਨ।

ਜ਼ਿਆਦਾਤਰ exceptions ਦੇ ਨਾਮ "Error" ਨਾਲ ਖਤਮ ਹੁੰਦੇ ਹਨ, ਬਿਲਕੁਲ ਮਿਆਰੀ exceptions ਦੀ naming ਵਾਂਗ।

ਕਈ ਮਿਆਰੀ ਮੌਡਿਊਲ ਆਪਣੇ ਅਪਵਾਦ ਪਰਿਭਾਸ਼ਿਤ ਕਰਦੇ ਹਨ ਤਾਂ ਜੋ ਉਹਨਾਂ ਦੁਆਰਾ ਪਰਿਭਾਸ਼ਿਤ ਫੰਕਸ਼ਨਾਂ ਵਿੱਚ ਆਉਣ ਵਾਲੀਆਂ ਗਲਤੀਆਂ ਦੀ ਰਿਪੋਰਟ ਕੀਤੀ ਜਾ ਸਕੇ।

8.7. ਸਫਾਈ ਕਾਰਵਾਈਆਂ ਪਰਿਭਾਸ਼ਿਤ ਕਰਨਾ

try ਸਟੇਟਮੈਂਟ ਵਿੱਚ ਇੱਕ ਹੋਰ ਵਿਕਲਪਿਕ clause ਹੁੰਦਾ ਹੈ ਜਿਸਦਾ ਉਦੇਸ਼ ਅਜਿਹੀਆਂ clean-up actions ਨੂੰ ਪਰਿਭਾਸ਼ਿਤ ਕਰਨਾ ਹੈ ਜੋ ਹਰ ਸਥਿਤੀ ਵਿੱਚ ਚਲਾਈਆਂ ਜਾਣੀਆਂ ਚਾਹੀਦੀਆਂ ਹਨ। ਉਦਾਹਰਨ ਵਜੋਂ:

>>> try:
...     raise KeyboardInterrupt
... finally:
...     print('Goodbye, world!')
...
Goodbye, world!
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
    raise KeyboardInterrupt
KeyboardInterrupt

ਜੇ finally clause ਮੌਜੂਦ ਹੋਵੇ, ਤਾਂ finally clause try ਸਟੇਟਮੈਂਟ ਦੇ ਪੂਰਾ ਹੋਣ ਤੋਂ ਪਹਿਲਾਂ ਆਖਰੀ ਕੰਮ ਵਜੋਂ ਚਲਾਇਆ ਜਾਂਦਾ ਹੈ। finally clause ਉਸ ਵੇਲੇ ਵੀ ਚਲਦਾ ਹੈ ਜਦੋਂ try ਸਟੇਟਮੈਂਟ exception ਪੈਦਾ ਕਰੇ ਜਾਂ ਨਾ ਕਰੇ। ਹੇਠਲੇ ਬਿੰਦੂ ਉਹਨਾਂ ਜਟਿਲ ਸਥਿਤੀਆਂ ਬਾਰੇ ਦੱਸਦੇ ਹਨ ਜੋ exception ਵਾਪਰਨ ਵੇਲੇ ਹੋ ਸਕਦੀਆਂ ਹਨ:

  • ਜੇ try clause ਦੀ ਕਾਰਵਾਈ ਦੌਰਾਨ ਕੋਈ exception ਵਾਪਰਦੀ ਹੈ, ਤਾਂ ਉਸਨੂੰ except clause ਦੁਆਰਾ ਸੰਭਾਲਿਆ ਜਾ ਸਕਦਾ ਹੈ। ਜੇ exception ਨੂੰ except clause ਦੁਆਰਾ ਸੰਭਾਲਿਆ ਨਾ ਜਾਵੇ, ਤਾਂ finally clause ਦੇ ਚੱਲਣ ਤੋਂ ਬਾਅਦ exception ਨੂੰ ਦੁਬਾਰਾ raise ਕੀਤਾ ਜਾਂਦਾ ਹੈ।

  • ਕੋਈ exception except ਜਾਂ else clause ਦੀ ਕਾਰਵਾਈ ਦੌਰਾਨ ਵੀ ਵਾਪਰ ਸਕਦੀ ਹੈ। ਇਸ ਸਥਿਤੀ ਵਿੱਚ ਵੀ finally clause ਦੇ ਚੱਲਣ ਤੋਂ ਬਾਅਦ exception ਨੂੰ ਦੁਬਾਰਾ raise ਕੀਤਾ ਜਾਂਦਾ ਹੈ।

  • ਜੇ finally clause ਵਿੱਚ break, continue ਜਾਂ return ਸਟੇਟਮੈਂਟ ਚਲਦੀ ਹੈ, ਤਾਂ exceptions ਨੂੰ ਦੁਬਾਰਾ raise ਨਹੀਂ ਕੀਤਾ ਜਾਂਦਾ। ਇਹ ਗੁੰਝਲਦਾਰ ਹੋ ਸਕਦਾ ਹੈ, ਇਸ ਲਈ ਇਸਦੀ ਸਿਫਾਰਸ਼ ਨਹੀਂ ਕੀਤੀ ਜਾਂਦੀ। ਵਰਜਨ 3.14 ਤੋਂ compiler ਇਸ ਲਈ SyntaxWarning ਜਾਰੀ ਕਰਦਾ ਹੈ (ਵੇਖੋ PEP 765)।

  • ਜੇਕਰ try ਬਿਆਨ break, continue ਜਾਂ return ਬਿਆਨ ਤੱਕ ਪਹੁੰਚਦਾ ਹੈ, ਤਾਂ finally ਕਲੌਜ਼ break, continue ਜਾਂ return ਬਿਆਨ ਦੇ ਚਲਾਉਣ ਤੋਂ ਠੀਕ ਪਹਿਲਾਂ ਚਲਾਇਆ ਜਾਵੇਗਾ।

  • ਜੇ finally clause ਵਿੱਚ return ਸਟੇਟਮੈਂਟ ਹੋਵੇ, ਤਾਂ ਵਾਪਸ ਕੀਤਾ ਗਿਆ ਮੁੱਲ finally clause ਦੇ return ਤੋਂ ਆਵੇਗਾ, ਨਾ ਕਿ try clause ਦੇ return ਤੋਂ। ਇਹ ਗੁੰਝਲਦਾਰ ਹੋ ਸਕਦਾ ਹੈ, ਇਸ ਲਈ ਇਸਦੀ ਸਿਫਾਰਸ਼ ਨਹੀਂ ਕੀਤੀ ਜਾਂਦੀ। ਵਰਜਨ 3.14 ਤੋਂ compiler ਇਸ ਲਈ SyntaxWarning ਜਾਰੀ ਕਰਦਾ ਹੈ (ਵੇਖੋ PEP 765)।

ਉਦਾਹਰਨ ਵਜੋਂ:

>>> def bool_return():
...     try:
...         return True
...     finally:
...         return False
...
>>> bool_return()
False

ਇੱਕ ਹੋਰ ਜਟਿਲ ਉਦਾਹਰਨ:

>>> def divide(x, y):
...     try:
...         result = x / y
...     except ZeroDivisionError:
...         print("division by zero!")
...     else:
...         print("result is", result)
...     finally:
...         print("executing finally clause")
...
>>> divide(2, 1)
result is 2.0
executing finally clause
>>> divide(2, 0)
division by zero!
executing finally clause
>>> divide("2", "1")
executing finally clause
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    divide("2", "1")
    ~~~~~~^^^^^^^^^^
  File "<stdin>", line 3, in divide
    result = x / y
             ~~^~~
TypeError: unsupported operand type(s) for /: 'str' and 'str'

ਜਿਵੇਂ ਤੁਸੀਂ ਵੇਖ ਸਕਦੇ ਹੋ, finally clause ਹਰ ਸਥਿਤੀ ਵਿੱਚ ਚਲਾਈ ਜਾਂਦੀ ਹੈ। ਦੋ strings ਨੂੰ ਭਾਗ ਦੇਣ ਨਾਲ ਉਤਪੰਨ ਹੋਈ TypeError ਨੂੰ except clause ਦੁਆਰਾ ਸੰਭਾਲਿਆ ਨਹੀਂ ਜਾਂਦਾ ਅਤੇ ਇਸ ਲਈ finally clause ਦੇ ਚੱਲਣ ਤੋਂ ਬਾਅਦ ਇਸਨੂੰ ਦੁਬਾਰਾ raise ਕੀਤਾ ਜਾਂਦਾ ਹੈ।

ਅਸਲ ਐਪਲੀਕੇਸ਼ਨਾਂ ਵਿੱਚ finally clause ਬਾਹਰੀ ਸਰੋਤਾਂ (ਜਿਵੇਂ ਫਾਈਲਾਂ ਜਾਂ ਨੈੱਟਵਰਕ ਕਨੈਕਸ਼ਨ) ਨੂੰ ਰਿਲੀਜ਼ ਕਰਨ ਲਈ ਲਾਭਦਾਇਕ ਹੁੰਦੀ ਹੈ, ਭਾਵੇਂ ਸਰੋਤ ਦੀ ਵਰਤੋਂ ਸਫਲ ਰਹੀ ਹੋਵੇ ਜਾਂ ਨਹੀਂ।

8.8. ਪਹਿਲਾਂ ਤੋਂ ਪਰਿਭਾਸ਼ਿਤ Clean-up Actions

ਕੁਝ ਵਸਤੂਆਂ ਮਿਆਰੀ clean-up actions ਪਰਿਭਾਸ਼ਿਤ ਕਰਦੀਆਂ ਹਨ ਜੋ ਵਸਤੂ ਦੀ ਲੋੜ ਖਤਮ ਹੋਣ 'ਤੇ ਚਲਾਈਆਂ ਜਾਂਦੀਆਂ ਹਨ, ਭਾਵੇਂ ਉਸ ਵਸਤੂ ਨਾਲ ਕੀਤੀ ਕਾਰਵਾਈ ਸਫਲ ਰਹੀ ਹੋਵੇ ਜਾਂ ਅਸਫਲ। ਹੇਠਾਂ ਦਿੱਤੀ ਉਦਾਹਰਨ ਵੇਖੋ ਜੋ ਇੱਕ ਫਾਈਲ ਖੋਲ੍ਹ ਕੇ ਉਸਦੀ ਸਮੱਗਰੀ ਸਕ੍ਰੀਨ ਉੱਤੇ ਪ੍ਰਿੰਟ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰਦੀ ਹੈ।

for line in open("myfile.txt"):
    print(line, end="")

ਇਸ ਕੋਡ ਦੀ ਸਮੱਸਿਆ ਇਹ ਹੈ ਕਿ ਕੋਡ ਦੇ ਇਸ ਹਿੱਸੇ ਦੇ ਚੱਲਣ ਤੋਂ ਬਾਅਦ ਵੀ ਫਾਈਲ ਅਣਨਿਰਧਾਰਤ ਸਮੇਂ ਲਈ ਖੁੱਲ੍ਹੀ ਰਹਿੰਦੀ ਹੈ। ਇਹ ਸਧਾਰਣ ਸਕ੍ਰਿਪਟਾਂ ਵਿੱਚ ਸਮੱਸਿਆ ਨਹੀਂ ਹੁੰਦੀ, ਪਰ ਵੱਡੀਆਂ ਐਪਲੀਕੇਸ਼ਨਾਂ ਵਿੱਚ ਸਮੱਸਿਆ ਬਣ ਸਕਦੀ ਹੈ। with ਸਟੇਟਮੈਂਟ ਫਾਈਲਾਂ ਵਰਗੇ objects ਨੂੰ ਇਸ ਤਰੀਕੇ ਨਾਲ ਵਰਤਣ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ ਕਿ ਉਹ ਹਮੇਸ਼ਾਂ ਸਮੇਂ ਸਿਰ ਅਤੇ ਠੀਕ ਤਰੀਕੇ ਨਾਲ ਸਾਫ਼ ਕੀਤੇ ਜਾਣ।

with open("myfile.txt") as f:
    for line in f:
        print(line, end="")

ਸਟੇਟਮੈਂਟ ਦੇ ਚੱਲਣ ਤੋਂ ਬਾਅਦ ਫਾਈਲ f ਹਮੇਸ਼ਾਂ ਬੰਦ ਕਰ ਦਿੱਤੀ ਜਾਂਦੀ ਹੈ, ਭਾਵੇਂ ਲਾਈਨਾਂ ਨੂੰ ਪ੍ਰੋਸੈਸ ਕਰਦੇ ਸਮੇਂ ਕੋਈ ਸਮੱਸਿਆ ਆਈ ਹੋਵੇ। ਜਿਹੜੇ objects, ਫਾਈਲਾਂ ਵਾਂਗ, ਪਹਿਲਾਂ ਤੋਂ ਪਰਿਭਾਸ਼ਿਤ ਸਫਾਈ ਕਾਰਵਾਈਆਂ ਪ੍ਰਦਾਨ ਕਰਦੇ ਹਨ, ਉਹ ਆਪਣੀ documentation ਵਿੱਚ ਇਸ ਬਾਰੇ ਦਰਸਾਉਂਦੇ ਹਨ।

8.9. ਕਈ ਅਸੰਬੰਧਿਤ Exceptions ਨੂੰ Raise ਕਰਨਾ ਅਤੇ ਸੰਭਾਲਣਾ

ਕਈ ਵਾਰ ਅਜਿਹੀਆਂ ਸਥਿਤੀਆਂ ਹੁੰਦੀਆਂ ਹਨ ਜਿੱਥੇ ਵਾਪਰੀਆਂ ਕਈ exceptions ਦੀ ਰਿਪੋਰਟ ਕਰਨੀ ਲਾਜ਼ਮੀ ਹੁੰਦੀ ਹੈ। concurrency frameworks ਵਿੱਚ ਇਹ ਆਮ ਗੱਲ ਹੈ, ਜਿੱਥੇ ਕਈ ਕੰਮ ਇੱਕੋ ਸਮੇਂ ਅਸਫਲ ਹੋ ਸਕਦੇ ਹਨ। ਹੋਰ ਵੀ ਅਜਿਹੇ ਮਾਮਲੇ ਹਨ ਜਿੱਥੇ ਪਹਿਲੀ exception ਨੂੰ raise ਕਰਨ ਦੀ ਬਜਾਏ ਕਾਰਵਾਈ ਜਾਰੀ ਰੱਖਣ ਅਤੇ ਕਈ ਗਲਤੀਆਂ ਇਕੱਠੀਆਂ ਕਰਨ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ।

ਬਿਲਟ-ਇਨ ExceptionGroup ਅਪਵਾਦ ਇੰਸਟੈਂਸਾਂ ਦੀ ਇੱਕ ਸੂਚੀ ਨੂੰ ਇਕੱਠਾ wrap ਕਰਦਾ ਹੈ ਤਾਂ ਜੋ ਉਹਨਾਂ ਨੂੰ ਇਕੱਠੇ raise ਕੀਤਾ ਜਾ ਸਕੇ। ਇਹ ਆਪਣੇ ਆਪ ਵਿੱਚ ਇੱਕ ਅਪਵਾਦ ਹੈ, ਇਸ ਲਈ ਇਸਨੂੰ ਹੋਰ ਅਪਵਾਦਾਂ ਵਾਂਗ catch ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ।

>>> def f():
...     excs = [OSError('error 1'), SystemError('error 2')]
...     raise ExceptionGroup('there were problems', excs)
...
>>> f()
  + Exception Group Traceback (most recent call last):
  |   File "<stdin>", line 1, in <module>
  |     f()
  |     ~^^
  |   File "<stdin>", line 3, in f
  |     raise ExceptionGroup('there were problems', excs)
  | ExceptionGroup: there were problems (2 sub-exceptions)
  +-+---------------- 1 ----------------
    | OSError: error 1
    +---------------- 2 ----------------
    | SystemError: error 2
    +------------------------------------
>>> try:
...     f()
... except Exception as e:
...     print(f'caught {type(e)}: {e}')
...
caught <class 'ExceptionGroup'>: there were problems (2 sub-exceptions)
>>>

except ਦੀ ਥਾਂ except* ਵਰਤ ਕੇ ਅਸੀਂ ਸਿਰਫ਼ ਉਹਨਾਂ exceptions ਨੂੰ ਚੁਣੀਂਦੇ ਤੌਰ 'ਤੇ ਸੰਭਾਲ ਸਕਦੇ ਹਾਂ ਜੋ ਸਮੂਹ ਵਿੱਚ ਕਿਸੇ ਨਿਰਧਾਰਤ ਕਿਸਮ ਨਾਲ ਮੇਲ ਖਾਂਦੇ ਹਨ। ਹੇਠਾਂ ਦਿੱਤੀ ਉਦਾਹਰਨ ਵਿੱਚ nested exception group ਦਿਖਾਇਆ ਗਿਆ ਹੈ। ਹਰ except* clause ਸਮੂਹ ਵਿੱਚੋਂ ਇੱਕ ਨਿਰਧਾਰਤ ਕਿਸਮ ਦੀਆਂ exceptions ਕੱਢਦਾ ਹੈ ਅਤੇ ਬਾਕੀ ਸਾਰੀਆਂ exceptions ਨੂੰ ਅਗਲੇ clauses ਵੱਲ ਅਤੇ ਅੰਤ ਵਿੱਚ ਦੁਬਾਰਾ raise ਕਰਨ ਲਈ ਜਾਣ ਦਿੰਦਾ ਹੈ।

>>> def f():
...     raise ExceptionGroup(
...         "group1",
...         [
...             OSError(1),
...             SystemError(2),
...             ExceptionGroup(
...                 "group2",
...                 [
...                     OSError(3),
...                     RecursionError(4)
...                 ]
...             )
...         ]
...     )
...
>>> try:
...     f()
... except* OSError as e:
...     print("There were OSErrors")
... except* SystemError as e:
...     print("There were SystemErrors")
...
There were OSErrors
There were SystemErrors
  + Exception Group Traceback (most recent call last):
  |   File "<stdin>", line 2, in <module>
  |     f()
  |     ~^^
  |   File "<stdin>", line 2, in f
  |     raise ExceptionGroup(
  |     ...<12 lines>...
  |     )
  | ExceptionGroup: group1 (1 sub-exception)
  +-+---------------- 1 ----------------
    | ExceptionGroup: group2 (1 sub-exception)
    +-+---------------- 1 ----------------
      | RecursionError: 4
      +------------------------------------
>>>

ਧਿਆਨ ਦਿਓ ਕਿ ਅਪਵਾਦ ਸਮੂਹ ਦੇ ਅੰਦਰ ਮੌਜੂਦ ਅਪਵਾਦ ਇੰਸਟੈਂਸ ਹੋਣੇ ਚਾਹੀਦੇ ਹਨ, ਕਿਸਮਾਂ ਨਹੀਂ। ਇਸ ਦਾ ਕਾਰਨ ਇਹ ਹੈ ਕਿ ਅਮਲ ਵਿੱਚ ਇਹ ਅਪਵਾਦ ਆਮ ਤੌਰ 'ਤੇ ਉਹ ਹੁੰਦੇ ਹਨ ਜੋ ਪਹਿਲਾਂ ਹੀ ਪ੍ਰੋਗਰਾਮ ਦੁਆਰਾ ਰੇਜ਼ ਅਤੇ ਕੈਚ ਕੀਤੇ ਜਾ ਚੁੱਕੇ ਹੁੰਦੇ ਹਨ, ਜਿਵੇਂ ਹੇਠਾਂ ਦਿੱਤੇ ਪੈਟਰਨ ਵਿੱਚ ਦਿਖਾਇਆ ਗਿਆ ਹੈ:

>>> excs = []
... for test in tests:
...     try:
...         test.run()
...     except Exception as e:
...         excs.append(e)
...
>>> if excs:
...    raise ExceptionGroup("Test Failures", excs)
...

8.10. ਨੋਟਾਂ ਨਾਲ Exceptions ਨੂੰ ਹੋਰ ਜਾਣਕਾਰੀ ਦੇਣਾ

ਜਦੋਂ ਕਿਸੇ ਅਪਵਾਦ ਨੂੰ ਰੇਜ਼ ਕਰਨ ਲਈ ਬਣਾਇਆ ਜਾਂਦਾ ਹੈ, ਤਾਂ ਆਮ ਤੌਰ 'ਤੇ ਉਸਨੂੰ ਗਲਤੀ ਦਾ ਵੇਰਵਾ ਦੇਣ ਵਾਲੀ ਜਾਣਕਾਰੀ ਨਾਲ ਸ਼ੁਰੂਆਤੀ ਮੁੱਲ ਦਿੱਤਾ ਜਾਂਦਾ ਹੈ। ਕੁਝ ਮੌਕਿਆਂ 'ਤੇ ਅਪਵਾਦ ਨੂੰ ਕੈਚ ਕਰਨ ਤੋਂ ਬਾਅਦ ਹੋਰ ਜਾਣਕਾਰੀ ਜੋੜਨਾ ਲਾਭਦਾਇਕ ਹੁੰਦਾ ਹੈ। ਇਸ ਲਈ ਅਪਵਾਦਾਂ ਵਿੱਚ add_note(note) ਵਿਧੀ ਹੁੰਦੀ ਹੈ ਜੋ ਇੱਕ ਸਤਰ ਸਵੀਕਾਰ ਕਰਦੀ ਹੈ ਅਤੇ ਉਸਨੂੰ ਅਪਵਾਦ ਦੀ ਨੋਟਾਂ ਦੀ ਸੂਚੀ ਵਿੱਚ ਜੋੜ ਦਿੰਦੀ ਹੈ। ਮਿਆਰੀ ਟ੍ਰੇਸਬੈਕ ਰੈਂਡਰਿੰਗ ਅਪਵਾਦ ਤੋਂ ਬਾਅਦ ਜੋੜੀਆਂ ਗਈਆਂ ਸਾਰੀਆਂ ਨੋਟਾਂ ਨੂੰ ਉਸੇ ਕ੍ਰਮ ਵਿੱਚ ਦਿਖਾਉਂਦੀ ਹੈ।

>>> try:
...     raise TypeError('bad type')
... except Exception as e:
...     e.add_note('Add some information')
...     e.add_note('Add some more information')
...     raise
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
    raise TypeError('bad type')
TypeError: bad type
Add some information
Add some more information
>>>

ਉਦਾਹਰਨ ਵਜੋਂ, ਜਦੋਂ exceptions ਨੂੰ ਇੱਕ exception group ਵਿੱਚ ਇਕੱਠਾ ਕੀਤਾ ਜਾਂਦਾ ਹੈ, ਤਾਂ ਅਸੀਂ ਹਰ ਗਲਤੀ ਲਈ ਵਾਧੂ ਸੰਦਰਭ ਜਾਣਕਾਰੀ ਜੋੜਨਾ ਚਾਹ ਸਕਦੇ ਹਾਂ। ਹੇਠਾਂ ਦਿੱਤੀ ਉਦਾਹਰਨ ਵਿੱਚ ਸਮੂਹ ਦੀ ਹਰ exception ਨਾਲ ਇੱਕ note ਜੁੜੀ ਹੈ ਜੋ ਦੱਸਦੀ ਹੈ ਕਿ ਇਹ ਗਲਤੀ ਕਦੋਂ ਵਾਪਰੀ ਸੀ।

>>> def f():
...     raise OSError('operation failed')
...
>>> excs = []
>>> for i in range(3):
...     try:
...         f()
...     except Exception as e:
...         e.add_note(f'Happened in Iteration {i+1}')
...         excs.append(e)
...
>>> raise ExceptionGroup('We have some problems', excs)
  + Exception Group Traceback (most recent call last):
  |   File "<stdin>", line 1, in <module>
  |     raise ExceptionGroup('We have some problems', excs)
  | ExceptionGroup: We have some problems (3 sub-exceptions)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   File "<stdin>", line 3, in <module>
    |     f()
    |     ~^^
    |   File "<stdin>", line 2, in f
    |     raise OSError('operation failed')
    | OSError: operation failed
    | Happened in Iteration 1
    +---------------- 2 ----------------
    | Traceback (most recent call last):
    |   File "<stdin>", line 3, in <module>
    |     f()
    |     ~^^
    |   File "<stdin>", line 2, in f
    |     raise OSError('operation failed')
    | OSError: operation failed
    | Happened in Iteration 2
    +---------------- 3 ----------------
    | Traceback (most recent call last):
    |   File "<stdin>", line 3, in <module>
    |     f()
    |     ~^^
    |   File "<stdin>", line 2, in f
    |     raise OSError('operation failed')
    | OSError: operation failed
    | Happened in Iteration 3
    +------------------------------------
>>>