Python-এর exception: try, except, else, finally, raise ও নিজের exception
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 "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
প্রায়ই জিজ্ঞেস করা প্রশ্নের উত্তর: Python-এ শূন্য দিয়ে ভাগ করলে ZeroDivisionError ওঠে। এটি /, // ও % — তিনটিতেই প্রযোজ্য।
Traceback কীভাবে পড়বেন
Traceback হলো রিপোর্ট, যে প্রোগ্রাম কীভাবে ত্রুটিতে পৌঁছাল। এটি নিচ থেকে উপরে পড়া সুবিধাজনক। সর্বশেষ লাইনটি সবচেয়ে গুরুত্বপূর্ণ: exception-এর ধরন ও বার্তা। ওপরে থাকে কোন লাইনে ঘটনাটি ঘটেছে, আরও ওপরে কে সেটি ডেকেছে, এবং এভাবে এন্ট্রি পয়েন্ট পর্যন্ত।
Traceback (most recent call last):
File "tracker.py", line 9, in <module>
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 দিয়ে।
সাধারণ প্রশ্ন
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 ব্যবহার করুন।
“Python 3.15: от новичка до профи” কোর্সে বিষয়টি অনুশীলনসহ বিস্তারিত শেখানো হয়েছে।
কোর্সে যান