এক্সেপশন — ভাঙা অবস্থা সামলানো
try/except দিয়ে নির্দিষ্ট ত্রুটি ধরা, কেন except Exception: pass প্রায় সবসময়ই ভুল, else আর finally, নিজে raise করা, আর ত্রুটির পরিবার থেকে সঠিক মাপেরটা বেছে নেওয়া।
- 1সমস্যা
- 2বোঝা
- 3উদাহরণ
- 4অনুমান
- 5নিজে করা
- 6কঠিন করা
যে সমস্যাটা আমরা সমাধান করছি
গত অধ্যায়ের শেষ প্রশ্নটায় একটা প্রোগ্রাম তিনটে লাইন ঠিকঠাক প্রক্রিয়া করে চতুর্থটায় থেমে গিয়েছিল। ছোট করে দেখলে এরকম:
numbers = ["10", "twenty", "30"]
total = 0
for text in numbers:
total += int(text)
print(total)ValueError: invalid literal for int() with base 10: 'twenty'10 ঠিকঠাক যোগ হয়েছিল। 30 কখনো যোগ হলো না। আর print(total) কখনো চললই না।
একটা খারাপ লাইনের জন্য গোটা কাজটা বন্ধ। এখন পর্যন্ত আমাদের কাছে দুটোই বিকল্প ছিল — হয় সব তথ্য নিখুঁত হবে, নয়তো প্রোগ্রাম থেমে যাবে।
numbers = ["10", "twenty", "30"]
total = 0
for text in numbers:
try:
total += int(text)
except ValueError:
print("skipping", text)
print(total)skipping twenty
40তৃতীয় একটা বিকল্প আছে: সমস্যাটা ঘটতে দিন, তারপর তার উত্তর দিন।
এই অধ্যায় শেষে আপনি পারবেন
try/exceptদিয়ে একটা নির্দিষ্ট ত্রুটি ধরতে- বলতে কেন খালি
except:বাexcept Exception: passপ্রায় সবসময়ই ভুল as errদিয়ে ত্রুটিটা হাতে নিয়ে তার বার্তা পড়তেelseআরfinallyকী করে তা বলতে- নিজে
raiseকরতে, আর কখন করা উচিত তা বুঝতে - ত্রুটির পরিবার চিনতে, আর সঠিক মাপের ত্রুটিটা ধরতে
পূর্বশর্ত: ফাইল পড়া আর লেখা।
একটা ত্রুটিও একটা বস্তু
as লিখলে ত্রুটিটাকে হাতে পাওয়া যায়:
try:
int("twenty")
except ValueError as err:
print(type(err))
print(err)<class 'ValueError'>
invalid literal for int() with base 10: 'twenty'এতদিন যে লাল লেখাগুলো টার্মিনালে দেখে এসেছেন, সেগুলো আসলে এই বস্তুগুলোই — আর ছাপলে যা আসে সেটাই তার বার্তা।
এর একটা বাস্তব ফল আছে: ত্রুটিটা নিজেই সবচেয়ে ভালো ব্যাখ্যাটা বহন করে। নিজের ভাষায় «কিছু একটা ভুল হয়েছে» লেখার চেয়ে err-টা ছেপে দেওয়া প্রায় সবসময়ই বেশি কাজের।
সঠিক ত্রুটিটা ধরুন
try:
int("twenty")
except TypeError:
print("caught")ValueError: invalid literal for int() with base 10: 'twenty'except কেবল যে ধরনটা লেখা আছে সেটাই ধরে। এখানে TypeError লেখা, আর যা ঘটেছে সেটা ValueError — তাই except ব্লকটা যেন নেই-ই।
এটা বিরক্তিকর মনে হতে পারে, কিন্তু এটাই কাজের জিনিসটা। আপনি কোন ভুলটার জন্য প্রস্তুত, সেটা except লাইনটাই বলে দেয় — আর যেটার জন্য প্রস্তুত নন, সেটা আগের মতোই থামিয়ে দেয়, যা ঠিক কাজ।
সেজন্যই এটা লিখবেন না:
numbers = ["10", "twenty", "30"]
total = 0
for text in numbers:
try:
total += int(text)
except Exception:
pass
print(total)40উত্তরটা ঠিক, আর কোডটা তবুও খারাপ। except Exception: pass সব ত্রুটি গিলে ফেলে — যেগুলোর কথা আপনি ভেবেছেন আর যেগুলোর কথা ভাবেননি, দুটোই। কালকে numbers-এর জায়গায় ভুল করে একটা ডিকশনারি এলে, বা ভিতরের কোডে একটা বানান ভুল হলে, এই প্রোগ্রামটা নীরবে ভুল উত্তর দেবে।
নিয়ম: যে ত্রুটিটার জন্য আপনার পরিকল্পনা আছে, ঠিক সেটাই ধরুন। আর ধরলে কিছু একটা করুন — অন্তত বলুন কী হলো।
একাধিক ধরন
def get(data, key):
try:
return data[key]
except KeyError:
return "no such key"
except IndexError:
return "no such position"
print(get({"pen": 15}, "pen"))
print(get({"pen": 15}, "bag"))
print(get([1, 2, 3], 9))15
no such key
no such positionকয়েকটা except পরপর লেখা যায়, আর প্রথম যেটা মেলে সেটাই চলে — বাকিগুলো দেখাও হয় না।
একই উত্তর হলে একসাথে লেখা যায়:
for value in ["10", None, "x"]:
try:
print(int(value))
except (ValueError, TypeError) as err:
print(type(err).__name__, "-", err)10
TypeError - int() argument must be a string, a bytes-like object or a real number, not 'NoneType'
ValueError - invalid literal for int() with base 10: 'x'type(err).__name__ ত্রুটির ধরনটার নাম দেয় — লগ লেখার সময় কাজে আসে।
ত্রুটিদের একটা পরিবার আছে
print(issubclass(FileNotFoundError, OSError))
print(issubclass(ValueError, Exception))
print(issubclass(KeyError, LookupError))
print(issubclass(IndexError, LookupError))True
True
True
Trueত্রুটিগুলো একটা গাছের মতো সাজানো, আর পিতাকে ধরলে সন্তানও ধরা পড়ে:
try:
raise FileNotFoundError("gone")
except OSError as err:
print("caught as OSError:", err)caught as OSError: goneকাজে লাগার মতো কয়েকটা সম্পর্ক: FileNotFoundError আর PermissionError দুটোই OSError; KeyError আর IndexError দুটোই LookupError; আর প্রায় সবকিছুই Exception।
এখান থেকে নিয়মটা আসে: যত ছোট মাপের ত্রুটি ধরা যায়, ততই ভালো। except Exception লেখা সবচেয়ে সহজ আর সবচেয়ে কম কাজের।
else আর finally
def read(text):
try:
value = int(text)
except ValueError:
print("bad:", text)
return None
else:
print("good:", value)
return value
finally:
print("done with", text)
print(read("10"))
print(read("ten"))good: 10
done with 10
10
bad: ten
done with ten
Noneelse চলে যদি try ব্লকে কিছুই না ভাঙে। এতে try-এর ভিতরটা ছোট রাখা যায় — কেবল যে লাইনটা ভাঙতে পারে সেটুকু — আর সফল হলে যা করার তা else-এ যায়। try-এর ভিতরে বেশি কোড রাখলে এমন কিছুও ধরা পড়ে যাকে ধরার কথা ছিল না।
finally সবসময় চলে, যাই ঘটুক। এমনকি return-এর পরেও:
def risky():
try:
return "from try"
finally:
print("finally still runs")
print(risky())finally still runs
from tryreturn মানটা ঠিক করে ফেলেছে, কিন্তু ফাংশন থেকে বেরোনোর আগে finally চলে। পরিষ্কার করার কাজের জন্য এটাই জায়গা — যদিও ফাইলের ক্ষেত্রে with এই কাজটা আগেই করে দেয়।
নিজে raise করা
ধরা যতটা দরকার, ছোঁড়াও ততটাই।
def line_total(price, quantity):
if quantity < 0:
raise ValueError(f"quantity cannot be negative: {quantity}")
return price * quantity
print(line_total(15.0, 3))
print(line_total(15.0, -1))45.0
ValueError: quantity cannot be negative: -1raise ছাড়া বিকল্প কী ছিল? None ফেরত দেওয়া, বা 0 — আর তখন ভুলটা ভ্রমণ করত। কোনো একটা মোট হিসাবের ভিতরে ঢুকে যেত, আর অনেক পরে একটা অদ্ভুত সংখ্যা হয়ে দেখা দিত, যার উৎস খুঁজে পাওয়া যেত না।
একটা ত্রুটি ভুলটাকে তার জন্মের জায়গাতেই থামায়, আর বার্তায় কারণটাও বলে দেয়।
বার্তাটা লেখার সময় যে মানটা সমস্যা করেছে সেটা ভিতরে রাখুন। "invalid quantity" আর "quantity cannot be negative: -1" — দ্বিতীয়টা পড়ে কাজ শুরু করা যায়।
ধরে আবার ছোঁড়াও যায়, আরও ভালো বার্তা দিয়ে:
def parse(text):
try:
return int(text)
except ValueError:
raise ValueError(f"not a number: {text!r}")
print(parse("10"))
print(parse("ten"))10
ValueError: not a number: 'ten'{text!r} মানটাকে repr করে বসায়, তাই উদ্ধৃতি চিহ্নগুলো দেখা যায় — not a number: 'ten' আর not a number: ten-এর মধ্যে পার্থক্যটা বোঝা যায় যখন মানটা আসলে একটা খালি লেখা বা ফাঁকা স্পেস।
আগে জিজ্ঞেস করা, নাকি পরে ক্ষমা চাওয়া
prices = {"pen": 15}
if "bag" in prices:
print(prices["bag"])
else:
print("not stocked")
try:
print(prices["bag"])
except KeyError:
print("not stocked")not stocked
not stockedদুটোই ঠিক। পাইথনে দ্বিতীয়টাই বেশি প্রচলিত, আর তার একটা কারণ আছে: প্রথমটা দুবার খোঁজে — একবার in-এ, একবার [...]-এ — আর মাঝখানে কিছু বদলে গেলে তবু ভাঙতে পারে।
তবে যা নিয়মিত ঘটে তার জন্য if লেখাই পরিষ্কার। ব্যতিক্রমের জন্য ব্যতিক্রম — নামটা এমনিতেই মনে করিয়ে দেয়।
একটা সম্পূর্ণ উদাহরণ
orders.txt, যেখানে ইচ্ছে করে দুটো খারাপ লাইন আছে:
pen,15.0,3
bag,eight,1
ink,120.0,2
clip,5.0main.py:
"""Read an order file, and keep going when a line is wrong."""
from pathlib import Path
TAX_RATE = 0.15
def parse_line(raw, number):
"""Returns one order line, or raises ValueError saying which line was wrong."""
parts = raw.split(",")
if len(parts) != 3:
raise ValueError(f"line {number}: expected 3 fields, got {len(parts)}")
name, price, quantity = parts
try:
return name, float(price), int(quantity)
except ValueError as err:
raise ValueError(f"line {number}: {err}")
def read_order(path):
"""Returns the good lines and the complaints, never raising for bad data."""
try:
text = path.read_text(encoding="utf-8")
except FileNotFoundError:
return [], [f"no such file: {path}"]
lines = []
problems = []
for number, raw in enumerate(text.splitlines(), start=1):
if not raw.strip():
continue
try:
lines.append(parse_line(raw, number))
except ValueError as err:
problems.append(str(err))
return lines, problems
def main():
lines, problems = read_order(Path("orders.txt"))
total = 0.0
for name, price, quantity in lines:
amount = round(price * quantity * (1 + TAX_RATE), 2)
total += amount
print(f"{name:<6} {amount:>9.2f}")
print(f"{'total':<6} {total:>9.2f}")
print()
print(f"{len(lines)} lines used, {len(problems)} skipped")
for problem in problems:
print(" -", problem)
if __name__ == "__main__":
main()pen 51.75
ink 276.00
total 327.75
2 lines used, 2 skipped
- line 2: could not convert string to float: 'eight'
- line 5: expected 3 fields, got 2চারটে জিনিস দেখার মতো।
parse_line ছোঁড়ে, read_order ধরে। নিচের ফাংশনটা জানে না গোটা প্রোগ্রামের কী করা উচিত — সে কেবল জানে এই লাইনটা ব্যবহারযোগ্য নয়। কী করা হবে সেই সিদ্ধান্তটা উপরের ফাংশনের। ত্রুটি ছোঁড়া হয় নিচে, ধরা হয় উপরে — এই বিভাজনটাই ব্যতিক্রম ব্যবস্থাটার মূল কাজ।
লাইন নম্বরটা বার্তার ভিতরে আছে। could not convert string to float: 'eight' নিজে থেকে বলে না কোথায় দেখতে হবে; line 2: ... বলে। যে তথ্যটা ধরার সময় আপনার কাছে আছে আর পরে থাকবে না, সেটা বার্তায় ঢোকানোই নিয়ম।
read_order নিজে কখনো ভাঙে না। সে দুটো জিনিস ফেরত দেয় — যা কাজে লাগল আর যা লাগল না। ফাইলটা না থাকলেও একই আকারের উত্তর আসে, তাই main-কে দুরকম পথ লিখতে হয় না।
ভিতরের try ব্লকটা ঠিক এক লাইনের। float(price) আর int(quantity) — কেবল যে অংশটা ভাঙতে পারে সেটুকুই তার ভিতরে। parts = raw.split(",") বাইরে, কারণ সেটা ভাঙার কথা নয়, আর ভাঙলে আমি জানতে চাই।
কিছু ভাঙা অবস্থা ও তার সমাধান
ত্রুটিটা ধরা পড়ছে না, যদিও except লেখা আছে ধরনটা মেলেনি। বার্তার প্রথম শব্দটাই ত্রুটির নাম — সেটাই লিখুন, নয়তো তার পিতাকে ধরুন।
except আছে কিন্তু প্রোগ্রামটা তবু থেমে যাচ্ছে ভাঙা লাইনটা try ব্লকের ভিতরে নেই। ইনডেন্টেশন দেখুন।
SyntaxError: expected 'except' or 'finally' block একটা try লেখা হয়েছে যার কোনো except নেই। try একা দাঁড়াতে পারে না।
সবকিছু «কাজ করছে» কিন্তু উত্তরগুলো ভুল কোথাও except Exception: pass আছে। সেটা খুঁজুন — সমস্যা সেখানেই।
raise করলাম কিন্তু কোনো বার্তা দেখা যাচ্ছে না raise ValueError লেখা হয়েছে, raise ValueError("...") নয়। বন্ধনী আর বার্তাটা দিন।
finally-তে return লিখলে ত্রুটিটা হারিয়ে যায় সেটাই হয়, আর সেজন্যই finally-তে return লিখবেন না। সেখানে কেবল পরিষ্কার করার কাজ।
ধাপ ৪ / ৬ — অনুমান
যাচাই করুন
একটা except লেখা আছে। প্রোগ্রামটার কী হবে?
try:
int("twenty")
except TypeError:
print("caught")- Aপ্রোগ্রামটা `ValueError` নিয়ে থেমে যায় — `except`-টা যেন নেই-ই
- B`caught` ছাপা হয়
- Cকিছুই ছাপা হয় না, প্রোগ্রামটা নীরবে শেষ হয়
- D`TypeError` নিয়ে থেমে যায়
গোটা লুপটা try-এর ভিতরে। কী ছাপা হবে?
values = ["10", "x", "30"]
total = 0
try:
for text in values:
total += int(text)
except ValueError:
print("gave up")
print(total)- Agave up 10
- Bgave up 40
- Cgave up 0
- D40
try আর except — দুটোতেই return আছে। কী ছাপা হবে?
def read(text):
try:
return int(text)
except ValueError:
return 0
finally:
print("checked", text)
print(read("7"))
print(read("seven"))- Achecked 7 7 checked seven 0
- B7 checked 7 0 checked seven
- Cchecked 7 7 0
- D7 0
উত্তর দিতে অ্যাকাউন্ট লাগবে
উত্তর মিলিয়ে দেখতে সাইন ইন করুন
প্রশ্নগুলো উপরে আছে, আর মাথায় মাথায় উত্তর ভেবে নেওয়াই আসল কাজ। সঠিক উত্তর, ব্যাখ্যা আর তিন ধাপের ইঙ্গিত দেখতে সাইন ইন করুন।
নিজে করুন
গত অধ্যায়ের summary.py-তে ফিরে যান, যেটা people.txt পড়ত।
এখন সেটাকে এমন করুন যাতে সে কখনো ক্র্যাশ না করে, আর শেষে বলে দেয় কোন লাইনগুলো বাদ গেল ও কেন। একটা parse_line(raw, number) ফাংশন বানান যে খারাপ লাইনের জন্য লাইন নম্বরসহ ValueError ছোঁড়ে।
তারপর ফাইলটাকে ইচ্ছে করে নষ্ট করুন — একটা লাইন থেকে কমা সরিয়ে দিন, একটাতে সংখ্যার জায়গায় অক্ষর বসান, একটা লাইন একেবারে খালি রাখুন — আর দেখুন প্রোগ্রামটা চারটে অভিযোগসহ শেষ পর্যন্ত চলে কিনা।
তারপর চারটে পরীক্ষা:
except ValueError-এর জায়গায়except Exception: passলিখুন। আউটপুট কী হয়, আর সেটা কেন বেশি বিপজ্জনক?parse_line-এর বার্তা থেকে লাইন নম্বরটা সরান। এবার একটা খারাপ লাইন খুঁজে বের করতে কত সময় লাগে?tryব্লকটা বড় করুন — গোটা লুপের শরীরটা তার ভিতরে ঢোকান। একটা লাইন খারাপ হলে এখন কী হয়?finallyযোগ করুন যে প্রতিটা লাইনের পর কিছু ছাপে, আর ভাল-খারাপ দুই ক্ষেত্রেই সে চলে কিনা দেখুন।
তৃতীয় পরীক্ষাটা এই অধ্যায়ের সবচেয়ে কাজের অভ্যাসটা শেখায়: try ব্লক যত ছোট, ধরাটা তত সৎ।
ধাপ ৬ / ৬
কঠিন করা — অধ্যায়ের কুইজ
সহজ থেকে কঠিন — দশটি প্রশ্ন, শেষেরগুলো ইচ্ছে করেই কঠিন।
সাইন ইন করে কুইজ দিন