# Python-এর exception: try, except, else, finally, raise ও নিজের exception

URL: https://skillaria.top/bn/guides/python/exceptions-python

Python-এর exception হলো একটি ত্রুটি-অবজেক্ট, যা স্বাভাবিক চলা থামিয়ে দেয়, যতক্ষণ না try/except ব্লক তা ধরে; else চলে যদি কোনো ত্রুটি না হয়, আর finally সব সময় চলে। নিজে exception ছোঁড়া যায় raise নির্দেশে, যেমন raise ValueError("যোগফল ঋণাত্মক"), আর নিজের exception বানানো হয় Exception-এর উত্তরাধিকারী ক্লাস দিয়ে।

Python-এর exception হলো এমন একটি অবজেক্ট, যা ত্রুটির সংকেত দেয় এবং প্রোগ্রামের স্বাভাবিক গতি থামিয়ে দেয়। কেউ ধরলে না থাকলে Python traceback ছাপিয়ে কাজ বন্ধ করে। ধরা হয় `try/except` দিয়ে, আর নিজে ছোঁড়া হয় `raise` নির্দেশ দিয়ে। এখানে দেখা যাক এটি কীভাবে কাজ করে, কোন ত্রুটিগুলো সবচেয়ে বেশি আসে এবং কীভাবে ত্রুটি হ্যান্ডলিংকে ত্রুটি লুকানোর কাজে পরিণত না করা যায়।

## Python-এর exception কী

যখন একটি অপারেশন চলতে পারে না — শূন্য দিয়ে ভাগ, অস্তিত্বহীন key-তে প্রবেশ, শব্দকে সংখ্যায় বদলানো — তখন Python একটি exception অবজেক্ট বানিয়ে সেটি «ছুড়ে» দেয়। বর্তমান ফাংশনের চলা থেমে যায়, exception ফাংশনের ডাক-শৃঙ্খল বেয়ে ওপরে উঠতে থাকে, যতক্ষণ না উপযুক্ত হ্যান্ডলার পায়। না পেলে প্রোগ্রাম বন্ধ হয়।

```
>>> 10 / 0
Traceback (most recent call last):
  File "", line 1, in
ZeroDivisionError: division by zero
```

প্রায়ই জিজ্ঞেস করা প্রশ্নের উত্তর: Python-এ শূন্য দিয়ে ভাগ করলে `ZeroDivisionError` ওঠে। এটি `/`, `//` ও `%` — তিনটিতেই প্রযোজ্য।

## Traceback কীভাবে পড়বেন

Traceback হলো রিপোর্ট, যে প্রোগ্রাম কীভাবে ত্রুটিতে পৌঁছাল। এটি নিচ থেকে উপরে পড়া সুবিধাজনক। সর্বশেষ লাইনটি সবচেয়ে গুরুত্বপূর্ণ: exception-এর ধরন ও বার্তা। ওপরে থাকে কোন লাইনে ঘটনাটি ঘটেছে, আরও ওপরে কে সেটি ডেকেছে, এবং এভাবে এন্ট্রি পয়েন্ট পর্যন্ত।

```
Traceback (most recent call last):
  File "tracker.py", line 9, in
    total = parse_amount(row)
  File "tracker.py", line 4, in parse_amount
    return float(raw.strip())
           ~~~~~^^^^^^^^^^^^^
ValueError: could not convert string to float: 'একশো'
```

Python 3.11 থেকে কোড লাইনের নিচে `~~~^^^` চিহ্ন আঁকা হয় (PEP 657): দেখায় লাইনের ঠিক কোন অংশটি ভেঙেছে। একই লাইনে পরপর কয়েকটি ডাক থাকলে এটি খুব কাজে আসে।

### «Did you mean» ইঙ্গিত

Python 3.10 থেকে নামে বানান ভুল হলে ত্রুটিবার্তা কাছাকাছি একটি বিকল্প প্রস্তাব করে। Python 3.15-এ ইঙ্গিত আরও বুদ্ধিমান: অন্য ভাষার অভ্যাসও চেনে। `list.push` লিখলে সাজেশন দেয় `append`, আর `"coffee".toUpperCase()` লিখলে `upper`। ইঙ্গিত ভেতরের ফিল্ডেও খোঁজে: dictionary যদি কোনো অবজেক্টের `totals` অ্যাট্রিবিউটে থাকে, আর আপনি অবজেক্টের উপরেই `items()` ডাকেন, ত্রুটি প্রস্তাব করবে `.totals.items`। কাছাকাছি নামের সাজেশন এখন `delattr()`-এও আসে।

## Python-এর exception-এর ধরন

অন্তর্নির্মিত exception অনেক, তবে শুরুতে সবচেয়ে বেশি দেখা যায় এগুলো:

| Exception | কখন ওঠে | উদাহরণ |
| --- | --- | --- |

| `NameError` | নাম সংজ্ঞায়িত নেই, বেশির ভাগ সময় বানান ভুল | `print(totl)` |

| `TypeError` | অপারেশন এই ধরনের জন্য উপযুক্ত নয় | `"5" + 3` |

| `ValueError` | ধরন ঠিক, মান ঠিক নয় | `int("একশো")` |

| `KeyError` | dictionary-তে এমন key নেই | `d["missing"]` |

| `IndexError` | সূচক তালিকার সীমার বাইরে | `[1, 2][5]` |

| `AttributeError` | অবজেক্টে এই মেথড বা ফিল্ড নেই | `[].push(1)` |

| `ZeroDivisionError` | শূন্য দিয়ে ভাগ | `1 / 0` |

| `FileNotFoundError` | ফাইল পাওয়া যায়নি | `open("nope.txt")` |

আলাদাভাবে উল্লেখযোগ্য `SyntaxError` (এবং এর বিশেষ রূপ `IndentationError`): এটি ফাইল পার্স করার সময়ই ওঠে, কোড চালানোর আগে, তাই একই ফাইলে `try` দিয়ে ধরা যায় না।

### Exception-এর শ্রেণিবিন্যাস

Exception হলো ক্লাস, আর সেগুলো একটি হায়ারার্কিতে সাজানো। শীর্ষে `BaseException`। তার থেকে আসে `Exception` (প্রায় সব «সাধারণ» ত্রুটি) এবং কয়েকটি সিস্টেম-স্তরের: `KeyboardInterrupt`, `SystemExit`, `GeneratorExit`।

```
BaseException
 ├── KeyboardInterrupt
 ├── SystemExit
 └── Exception
      ├── ArithmeticError
      │    └── ZeroDivisionError
      ├── LookupError
      │    ├── KeyError
      │    └── IndexError
      ├── OSError
      │    └── FileNotFoundError
      ├── TypeError
      └── ValueError
```

ধরার সময় হায়ারার্কি গুরুত্বপূর্ণ: `except LookupError` ধরবে `KeyError` ও `IndexError` দুটোই। আর `KeyboardInterrupt` কোডের ত্রুটি নয়: এটি জানায় ব্যবহারকারী Ctrl+C চেপেছেন। এটি ইচ্ছে করেই `Exception`-এর উত্তরাধিকারী নয়, যাতে `except Exception` প্রোগ্রাম বন্ধ করার পথে বাধা না হয়।

## Exception হ্যান্ডলিং: try/except/else/finally

যে কোড ভাঙতে পারে, তা রাখা হয় `try` ব্লকে। `except` ব্লকে উল্লেখ করা হয় ত্রুটির ধরন এবং তা নিয়ে কী করতে হবে। Python-এ exception হ্যান্ডলিংয়ের ব্লক এটিই — `except`।

```
def parse_amount(raw):
    try:
        value = float(raw)
    except ValueError as e:
        print(f"লাইন বাদ দিচ্ছি: {e}")
        return None
    else:
        return value          # চলে যদি কোনো ত্রুটি না হয়
    finally:
        print("পার্সিং শেষ")   # সব সময় চলে

print(parse_amount("250"))
```

এখানে আউটপুটের ক্রম: প্রথমে «পার্সিং শেষ», তারপর `250.0`। `else` ব্লকের `return` মানটি মনে রাখে, তারপর Python অবশ্যই `finally` চালায় এবং শুধু পরে ফল ফেরত দেয়। তাই পরিষ্কার-পরিচ্ছন্নতার জন্য `finally` নির্ভরযোগ্য: ফাইল বন্ধ করা, সংযোগ ছাড়া।

`finally`-তে নিজের `return` লিখলে সেটি আগেরটিকে ঢেকে দেয় এবং exception-ও গিলে ফেলতে পারে। Python 3.14 থেকে এমন কোডে `SyntaxWarning` (PEP 765) দেখানো হয় — `finally`-তে `return` লিখবেন না।

### কয়েকটি exception একসঙ্গে

বিভিন্ন ধরনের জন্য আলাদা `except` লেখা যায়, অথবা একটি tuple-এ ধরনগুলো দেওয়া যায়:

```
try:
    amount = int(data["amount"])
except KeyError:
    amount = 0
except (ValueError, TypeError) as e:
    print("ভুল পরিমাণ:", e)
    amount = 0
```

ব্লকগুলো উপর থেকে নিচে যাচাই হয়, প্রথম মিলটি চলে — তাই নির্দিষ্ট ধরনগুলো সাধারণের ওপরে রাখুন। Python 3.14 থেকে (PEP 758) একাধিক ধরন কোনো বন্ধনী ছাড়াই লেখা যায় — `except ValueError, TypeError:` — তবে `as` থাকলে বন্ধনী এখনো বাধ্যতামূলক।

### ত্রুটির বার্তা ও ধরন কীভাবে বের করবেন

Exception অবজেক্ট পাওয়া যায় `as e` দিয়ে। `str(e)` বার্তা দেয়, `type(e).__name__` ধরনের নাম:

```
try:
    int("একশো")
except ValueError as e:
    print(type(e).__name__)   # ValueError
    print(e)                  # invalid literal for int() with base 10: 'একশো'
```

## traceback মডিউল: পুরো ত্রুটির লেখা

কখনো ত্রুটি ধরতে হয়, কিন্তু পুরো traceback রাখতে হয় — লগ বা রিপোর্টের জন্য। এ জন্য আছে `traceback` মডিউল:

```
import traceback

try:
    1 / 0
except ZeroDivisionError:
    traceback.print_exc()             # traceback stderr-এ ছাপে
    text = traceback.format_exc()     # একই লেখা স্ট্রিং হিসেবে
```

Log লিখতে `logging` মডিউল ব্যবহার করলে `except`-এর ভেতরে `logging.exception("হিসাব করা যায়নি")` লেখা সহজ — traceback বার্তার সঙ্গে নিজেই যুক্ত হয়ে যায়।

## Python raise: exception নিজে ছোঁড়া

`raise` নির্দেশ exception হাতে করে ছোঁড়ে। এভাবে একটি ফাংশন জানায়, এই ডেটা নিয়ে সে কাজ করতে পারছে না:

```
def add_expense(amount):
    if amount < 0:
        raise ValueError(f"পরিমাণ ঋণাত্মক হতে পারে না: {amount}")
    ...

add_expense(-5)
# ValueError: পরিমাণ ঋণাত্মক হতে পারে না: -5
```

Python raise একটি ফাংশন নয়, নির্দেশ — তাই `raise`-এর পরে বন্ধনী লাগে না: বন্ধনীর ভেতরে যায় exception-এর কনস্ট্রাক্টরের আর্গুমেন্ট। ধরন বেছে নিন অর্থ অনুযায়ী: ভুল মান হলে `ValueError`, ভুল ধরন হলে `TypeError`।

### আর্গুমেন্ট ছাড়া raise ও raise from

`except`-এর ভেতরে শুধু `raise` লিখলে বর্তমান exception অপরিবর্তিতভাবে আরও ওপরে চলে যায়। লগে কিছু লিখে কিন্তু হ্যান্ডলিং ডাককারীর হাতে রাখতে চাইলে এটি লাগে।

একটি exception-কে অন্যটি দিয়ে বদলালে `raise ... from e` ব্যবহার করুন — মূল ত্রুটি শৃঙ্খলে থেকে যায় এবং traceback-এ দুটোই দেখা যায়:

```
def load_config(path):
    try:
        with open(path) as f:
            return f.read()
    except FileNotFoundError as e:
        raise RuntimeError(f"কনফিগ নেই: {path}") from e
```

### add_note: ত্রুটিতে প্রসঙ্গ যোগ

Python 3.11 থেকে (PEP 678) exception-এ `add_note()` মেথড দিয়ে নোট যোগ করে আবার ছোঁড়া যায়। ত্রুটির ধরন বদলায় না, নোট বার্তার নিচে ছাপা হয়:

```
def parse(raw, line_no):
    try:
        return float(raw)
    except ValueError as e:
        e.add_note(f"ফাইল expenses.csv, লাইন {line_no}")
        raise

parse("একশো", 17)
# ValueError: could not convert string to float: 'একশো'
# ফাইল expenses.csv, লাইন 17
```

## নিজের exception বানানো

নিজের exception দরকার হয় বিষয়-ক্ষেত্রের ত্রুটির জন্য: বাজেট ছাড়িয়েছে, অর্ডার আগেই পরিশোধিত, ব্যবহারকারী ব্লক করা। শুধু `Exception`-এর উত্তরাধিকারী একটি ক্লাস লাগে:

```
class BudgetExceeded(Exception):
    def __init__(self, category, limit, spent):
        super().__init__(f"{category}: সীমা {limit}, খরচ {spent}")
        self.category = category
        self.limit = limit
        self.spent = spent

def check_budget(category, limit, spent):
    if spent > limit:
        raise BudgetExceeded(category, limit, spent)

try:
    check_budget("Food", 10000, 12500)
except BudgetExceeded as e:
    print("সতর্কতা:", e)   # সতর্কতা: Food: সীমা 10000, খরচ 12500
```

এখন কল-করা কোড ঠিক বাজেট ছাড়ানোটি ধরে এবং বানানের ভুলের সঙ্গে গুলিয়ে ফেলে না। বড় প্রজেক্টে একটি সাধারণ base ক্লাস রাখা ভালো (`class TrackerError(Exception)`) এবং সব নিজস্ব exception তা থেকে উত্তরাধিকার নেবে — তখন একটি `except TrackerError` দিয়েই সবগুলো ধরা যায়। উত্তরাধিকার নিন `Exception` থেকে, `BaseException` থেকে নয়।

## Exception group ও except*

কখনো একসঙ্গে কয়েকটি ত্রুটি হয়, যেমন পুরো ফাইল যাচাইয়ে বা `asyncio.TaskGroup`-এর সমান্তরাল কাজে। Python 3.11 থেকে এগুলো জমানো যায় `ExceptionGroup`-এ, আর ধরন অনুযায়ী ধরা যায় `except*` দিয়ে:

```
def check_all(rows):
    errors = []
    for r in rows:
        try:
            float(r)
        except ValueError as e:
            errors.append(e)
    if errors:
        raise ExceptionGroup("ফাইলের ত্রুটি", errors)

try:
    check_all(["1", "a", "b"])
except* ValueError as eg:
    print(f"ভুল লাইন: {len(eg.exceptions)}")   # ভুল লাইন: 2
```

## Python try except সব ত্রুটি ধরলে কেন ক্ষতি

সবচেয়ে ক্ষতিকর অভ্যাস — সবকিছু ধরা: `except: pass`। প্রোগ্রাম «আর পড়ে যায় না», কিন্তু আসলে সমস্যা জানানোও বন্ধ করে। বেয়ার `except` `KeyboardInterrupt` ও `SystemExit`-ও ধরে, তাই Ctrl+C দিয়ে প্রোগ্রাম থামানো যায় না। `except Exception: pass` একটু ভালো, তবু নীরবে গিলে ফেলে নামের বানান ভুল ও যুক্তির ভুল।

Python-এ exception কীভাবে সঠিকভাবে হ্যান্ডল করবেন:

- শুধু সেই ধরনটি ধরুন যেটি সামলাতে পারেন: `except ValueError`, সব নয়;
- `try` ব্লক ছোট রাখুন — শুধু সেই লাইনগুলো যেগুলো ঠিক ওইভাবে ভাঙতে পারে;
- প্রোগ্রামের উপর স্তরে বিস্তৃতভাবে ধরলে (`except Exception`) অবশ্যই traceback লগ করুন;
- সহজ যাচাই থাকলে exception দিয়ে শাখা বানাবেন না: সাধারণ ক্ষেত্রে `try/except KeyError`-এর বদলে `dict.get()`;
- রিসোর্সের জন্য `with` ব্যবহার করুন — ত্রুটি হলেও ফাইল নিজে বন্ধ হবে।

## সারসংক্ষেপ

Python-এর exception ত্রুটি জানানোর স্বাভাবিক ব্যবস্থা, ত্রুটি লুকানোর উপায় নয়। Traceback নিচ থেকে উপরে পড়ুন, `try/except`-এ নির্দিষ্ট ধরন ধরুন, স্বাভাবিক পথের জন্য `else` আর পরিষ্কারের জন্য `finally` ব্যবহার করুন। Python raise নিজে ত্রুটি জানাতে দেয়, `raise from` ও `add_note` প্রসঙ্গ ধরে রাখে, আর নিজের exception-এর উত্তরাধিকার নেয় `Exception` থেকে। কোর্সে এই বিষয়টি বাস্তবে শেখানো হয়েছে: খরচের ট্র্যাকার CSV-র একটি ভুল লাইনে আর ভেঙে পড়ে না, এবং বাজেটের সীমা ছাড়ানো ধরা হয় নিজের exception দিয়ে।

## FAQ

**Python-এ error আর exception-এর পার্থক্য কী?**

কথ্য ভাষায় এ দুটি সমার্থক। প্রযুক্তিগতভাবে SyntaxError কোড পার্স করার সময়ই ধরা পড়ে, আর বাকি ত্রুটি চালানোর সময় exception অবজেক্ট হিসেবে ওঠে এবং ধরা যায়।

**exception হওয়ার পর কি সেখান থেকেই চালিয়ে যাওয়া যায়?**

না। ধরার পরে চলা শুরু হয় try/except ব্লকের পরে, যেখানে ত্রুটি হয়েছিল সেই লাইন থেকে নয়। ফাইলের প্রতিটি লাইন আলাদাভাবে সামলাতে চাইলে try লুপের ভেতরে রাখুন।

**except-এর ভেতরে exception উঠলে কী হয়?**

নতুনটি ওপরে উঠে যায়, আর মূলটি তার __context__ অ্যাট্রিবিউটে থেকে যায়। traceback-এ আসে বার্তা: During handling of the above exception, another exception occurred।

**টেস্টে কীভাবে যাচাই করব যে ফাংশন exception ছোঁড়ে?**

pytest-এ লেখা হয় with pytest.raises(ValueError):, unittest-এ self.assertRaises(ValueError)। ব্লকের ভেতরে সঠিক ধরনের exception উঠলেই টেস্ট পাস করে।

**assert কী এবং raise থেকে কীভাবে আলাদা?**

assert শর্ত, "বার্তা" শর্ত মিথ্যা হলে AssertionError ছোঁড়ে। এটি উন্নয়নের সময় যাচাইয়ের সরঞ্জাম: -O পতাকাসহ চালালে assert বন্ধ হয়ে যায়, তাই ব্যবহারকারীর ইনপুট যাচাই করতে if ও raise ব্যবহার করুন।


---
Source: Skillaria.Top (https://skillaria.top)
