Error handling ও exception
এই পাঠে যা শিখবেন
- Exception কী — error vs syntax error
- try/except/else/finally — চারটি block
- Specific exception types — ValueError, KeyError, etc.
- Custom exception class
- raise — নিজের error
- EAFP বনাম LBYL — Python-এর philosophy
- AI pipeline-এ robust error handling
১ · Exception কী?
Code চলার সময় অস্বাভাবিক পরিস্থিতি — যেমন: ভাগ ০ দিয়ে, file নেই, network down। Python এই সংকেত দেয় exceptionExceptionপ্রোগ্রাম চলার সময় উদ্ভূত অপ্রত্যাশিত event। Python-এ একটি object — handle না করলে program terminate। try/except দিয়ে সামলানো। raise করে।
# Handle না করলে — program crash
result = 10 / 0 # ZeroDivisionError
print("কখনো এখানে আসবে না")
ZeroDivisionError: division by zero। Code-এর পরের লাইন আর চলে না।
২ · try/except — error ধরা
# Basic try/except
try:
x = 10 / 0
except ZeroDivisionError:
print("শূন্য দিয়ে ভাগ — undefined")
# Multiple exceptions
def parse_int(text):
try:
return int(text)
except ValueError:
return None
except TypeError:
return None
print(parse_int("42")) # 42
print(parse_int("abc")) # None
print(parse_int(None)) # None
# Exception object
try:
int("xyz")
except ValueError as e:
print(f"Error: {e}")
print(f"Type: {type(e).__name__}")
১) try: — risky কোড।
২) except SomeError: — match হলে এই block।
৩) else: — try সফল হলে (rare)।
৪) finally: — যাই হোক চলবে — cleanup-এ।
৩ · else ও finally
def safe_divide(a, b):
try:
result = a / b
except ZeroDivisionError:
print("ভাগ অসম্ভব")
return None
else:
print("সফলভাবে হিসাব")
return result
finally:
print("--- শেষ ---")
print(safe_divide(10, 2))
print()
print(safe_divide(10, 0))
finally — exception caught হোক বা না হোক, return-এর আগে — অবশ্যই চলবে। Cleanup (file close, lock release)-এর আদর্শ।
৪ · Common Python exceptions
- ValueError — সঠিক type, কিন্তু ভুল value:
int("abc")। - TypeError — ভুল type:
"5" + 5। - KeyError — dict-এ key নেই:
d["missing"]। - IndexError — list out of range:
lst[100]। - FileNotFoundError — file নেই:
open("missing.txt")। - AttributeError — object-এ attribute নেই।
- ZeroDivisionError — শূন্য দিয়ে ভাগ।
- ImportError — module install নেই।
- StopIteration — iterator শেষ।
৫ · raise — নিজের error
# Validation
def set_age(age):
if not isinstance(age, int):
raise TypeError("age must be int")
if age < 0:
raise ValueError(f"age cannot be negative, got {age}")
if age > 150:
raise ValueError(f"age too large: {age}")
return age
print(set_age(25))
try:
set_age(-5)
except ValueError as e:
print(f"Caught: {e}")
# Re-raise
def parse_data(s):
try:
return int(s)
except ValueError as e:
print(f"Logging: {e}")
raise # উপরে পাঠাও
except: বা except Exception: সব ধরে ফেলে — KeyboardInterrupt-ও। নির্দিষ্ট exception ধরুন।
৬ · Custom exception class
# Domain-specific exception
class ModelNotConvergedError(Exception):
"""Training-এ loss converge হয়নি।"""
def __init__(self, epoch, loss):
self.epoch = epoch
self.loss = loss
super().__init__(f"Epoch {epoch}: loss {loss:.4f} too high")
class InvalidHyperparameterError(ValueError):
"""Hyperparameter validation।"""
pass
# ব্যবহার
def train_demo(lr):
if lr <= 0 or lr > 1:
raise InvalidHyperparameterError(f"lr must be (0, 1], got {lr}")
final_loss = 0.5 # demo — converge হয়নি ধরা
if final_loss > 0.1:
raise ModelNotConvergedError(epoch=10, loss=final_loss)
try:
train_demo(0.001)
except InvalidHyperparameterError as e:
print(f"Hyperparameter ভুল: {e}")
except ModelNotConvergedError as e:
print(f"Training ব্যর্থ: epoch {e.epoch}, loss {e.loss}")
OutOfMemoryError, NaNError, SchemaMismatch ইত্যাদি common।
৭ · EAFP বনাম LBYL — Python philosophy
data = {"name": "ABCL", "score": 85}
# LBYL — Look Before You Leap (C-style)
if "score" in data and isinstance(data["score"], (int, float)):
val = data["score"] * 2
print(val)
# EAFP — Easier to Ask Forgiveness (Pythonic) ✓
try:
val = data["score"] * 2
print(val)
except (KeyError, TypeError):
print("score পাওয়া যায়নি বা type ভুল")
৮ · AI training-এ robust error handling
# Robust training pseudo-loop
def train_safely(model, data, epochs=10):
history = []
for epoch in range(epochs):
try:
loss = train_one_epoch(model, data)
history.append(loss)
if loss != loss: # NaN check
raise ValueError(f"NaN loss at epoch {epoch}")
except KeyboardInterrupt:
print(f"User stopped at epoch {epoch}")
break
except ValueError as e:
print(f"Epoch {epoch} ব্যর্থ: {e}")
print("Reducing learning rate ও আবার চেষ্টা")
# rollback, lr_reduce, etc.
continue
except Exception as e:
print(f"Unknown error: {type(e).__name__}: {e}")
raise # রহস্য — উপরে জানাও
return history
def train_one_epoch(model, data):
return 0.5 # demo
print(train_safely("BERT", "data"))
KeyboardInterrupt handle করুন (user stop)। NaN/Inf detect করুন। OOM-এ batch size halve। Production training-এ robust error handling = ১০-১০০× সময় বাঁচানো।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ "EAFP > LBYL" — Python-এর philosophy। কেন? অন্য ভাষায় (C/Java) কি বিপরীত? Performance, race condition, readability — তিন দৃষ্টিতে কোনটা ভাল?
এই philosophy Python-এ এত গভীর — যে standard library এই pattern-এ লেখা। বুঝলে — Pythonic code লেখা আসে।
Definitions:
- LBYL (Look Before You Leap): কাজ করার আগে condition check।
- EAFP (Easier to Ask Forgiveness): কাজ করো, ব্যর্থ হলে exception ধরো।
Python-এ EAFP কেন preferred?
- Cheap exceptions: exception raise/catch দ্রুত (সাধারণত)।
- Race-condition free: "check then act" — মাঝে state বদলাতে পারে; "act then handle" — atomic।
- Duck typing: "isinstance check" এড়িয়ে — method call করো, fail হলে handle।
- Readability: happy path-এ noise কম।
Race condition উদাহরণ:
# LBYL — race condition!
if os.path.exists("file.txt"):
# মাঝে অন্য process delete করে দিল
f = open("file.txt") # FileNotFoundError
# EAFP — atomic
try:
f = open("file.txt")
except FileNotFoundError:
handle_missing()
Performance comparison:
- Common case (no error): EAFP দ্রুত (no check overhead)।
- Common error: LBYL দ্রুত (no exception raise)।
- Python-এ exception cheap, কিন্তু hot loop-এ avoid।
C/Java-এ LBYL কেন preferred?
- Exception expensive (stack unwind, allocation)।
- Static type — compile-time check possible।
- Convention — checked exceptions force LBYL।
- "Exceptions are exceptional" philosophy।
Python idioms — EAFP-এর উদাহরণ:
# Dict lookup
# খারাপ — LBYL
if "key" in d:
val = d["key"]
else:
val = default
# ভাল — EAFP
try:
val = d["key"]
except KeyError:
val = default
# আরও ভাল — built-in
val = d.get("key", default)
# Attribute access
# ভাল
try:
name = obj.name
except AttributeError:
name = "unknown"
# আরও ভাল
name = getattr(obj, "name", "unknown")
কখন LBYL সঠিক?
- Hot loop — performance critical।
- Validation logic clear।
- Error message customization।
- Pre-condition complex।
AI/ML-এ এই philosophy:
- Tensor shape: EAFP — try operation, RuntimeError catch।
- Model loading: EAFP — version mismatch ধরা।
- Data parsing: EAFP — corrupt sample skip।
- GPU OOM: EAFP + retry with smaller batch।
Hybrid approach:
# Validate at boundary (LBYL)
def public_api(x):
if not isinstance(x, np.ndarray):
raise TypeError("expected ndarray")
# Internal logic — EAFP
try:
return process(x)
except (ValueError, RuntimeError) as e:
log_and_recover(e)
মূল উপলব্ধি: EAFP = "trust, then verify"। Python-এ — duck typing, dynamic, exception cheap → EAFP natural। AI কোডে hybrid: boundary-তে validate, internal-এ EAFP। Robustness comes from embracing failure, not avoiding।
প্র ০২
except Exception: বনাম specific exception — কোনটা best practice? "Bare except" কেন বিপজ্জনক? AI training-এ silent failures কী ক্ষতি করে?
Exception handling-এ specificity = correctness। Broad catch = silent bug = production disaster।
Bare except — প্রায় সবসময় ভুল:
# বিপজ্জনক
try:
risky_op()
except: # সব ধরে — KeyboardInterrupt, SystemExit-ও!
pass
# খুব খারাপ —
# - Ctrl+C কাজ করে না
# - sys.exit() block হয়
# - bug masked
# - debugging impossible
Hierarchy understanding:
BaseException
├── KeyboardInterrupt # user Ctrl+C
├── SystemExit # sys.exit()
├── GeneratorExit
└── Exception # 99% practical errors
├── ValueError
├── TypeError
├── KeyError
├── ...
Best practices ranked:
- Specific:
except FileNotFoundError:— perfect। - Group:
except (ValueError, TypeError):— multiple। - Domain:
except IOError:— class hierarchy। - Last resort:
except Exception as e:— log + re-raise। - Never:
except:বাexcept BaseException:
Pattern: log + re-raise:
import logging
try:
train_model()
except Exception as e:
logging.exception("Training failed") # full traceback
raise # উপরে চলে যাক
Silent failure-এর AI catastrophes:
- NaN gradient swallowed: training "চলছে" দেখায়, model garbage।
- Data validation skip: bad sample silently dropped → biased model।
- Checkpoint save failure: ১০ ঘণ্টা training, save fail, পরের run scratch।
- GPU memory leak hidden: performance degrade, root cause obscure।
- Tokenizer error masked: wrong tokens → wrong model।
"Don't catch, log" rule:
- Catch → handle meaningfully → re-raise বা return error।
- Catch → ignore → BUG MASKED।
- "Pass" inside except = code smell।
Modern Python improvements:
- Python 3.11 — Exception groups (
except*)। - Python 3.11 — fine-grained traceback।
- Notes on exceptions (
e.add_note)।
Production patterns:
# Retry with backoff
from time import sleep
def with_retry(func, retries=3):
for attempt in range(retries):
try:
return func()
except (ConnectionError, TimeoutError) as e:
if attempt == retries - 1:
raise
sleep(2 ** attempt) # exponential backoff
logging.warning(f"Retry {attempt + 1}: {e}")
Sentinel value vs exception:
- None/-1 return — caller must check।
- Exception — uncaught propagates。
- Result type (Rust style) — explicit success/error।
- AI-তে: data parsing-এ Result/Option pattern jaa৵o popular।
মূল উপলব্ধি: "Catch the exception you can handle, propagate the rest." Specific catch = clear intent। Bare/broad catch = bug factory। AI long-running training-এ exception handling discipline = সাফল্যের শর্ত।
প্র ০৩
Custom exception class বানানোর সুবিধা কী? AI library design-এ যেমন OutOfMemoryError, NaNError কেন define করে? Hierarchy কীভাবে design করবেন?
Custom exception = domain-specific contract। Library design-এ এটা mature engineering-এর hallmark।
Built-in vs custom:
- Built-in (ValueError, KeyError) — Python-এর core জিনিস।
- Custom — আপনার domain-এর জিনিস।
সুবিধা:
- Specific catch: caller সঠিক handler লিখতে পারে।
- Rich context: attributes (epoch, batch, layer)।
- Documentation: error type-ই docs।
- Testing: assertRaises-এ specific check।
- Hierarchy: general → specific catch।
Hierarchy design pattern:
class MLError(Exception):
"""Base — সব ML error-এর জন্য."""
class TrainingError(MLError):
"""Training stage."""
class NaNLossError(TrainingError):
def __init__(self, epoch, batch):
self.epoch = epoch
self.batch = batch
super().__init__(f"NaN at epoch {epoch}, batch {batch}")
class ConvergenceError(TrainingError):
def __init__(self, final_loss, threshold):
self.final_loss = final_loss
self.threshold = threshold
super().__init__(
f"Loss {final_loss:.4f} > threshold {threshold:.4f}"
)
class DataError(MLError):
"""Dataset-related."""
class SchemaMismatchError(DataError):
pass
ব্যবহার hierarchy-এর সুবিধা:
# Specific
try:
train()
except NaNLossError as e:
print(f"NaN at {e.epoch}/{e.batch} — reduce lr")
except ConvergenceError as e:
print(f"Did not converge: {e.final_loss}")
# General fallback
try:
pipeline()
except MLError as e:
notify_team(e)
except Exception:
raise # not ours, bubble up
AI library examples:
- PyTorch:
torch.cuda.OutOfMemoryError,RuntimeErrorwith shape info। - HuggingFace:
OSErrorfor model not found, customRepositoryNotFoundError। - Pydantic:
ValidationErrorwith field-level details। - scikit-learn:
NotFittedError,ConvergenceWarning। - JAX:
TracerArrayConversionError— JIT-specific।
Design principles:
- Inherit appropriately: file error → IOError; bad value → ValueError।
- Rich attributes: just message না, structured data।
- Helpful message: "RuntimeError" না, "expected shape (3,4), got (4,3)"।
- Avoid over-hierarchy: ৩-৪ level যথেষ্ট।
- Public exceptions documented।
Anti-pattern:
# খারাপ — generic
raise Exception("something failed")
# ভাল
raise NaNLossError(epoch=10, batch=100)
Exception chaining (Python 3+):
try:
parse_csv(path)
except CSVError as e:
raise DataError(f"Failed to load {path}") from e
# 'from e' preserves original cause
মূল উপলব্ধি: Custom exception = domain language। ভাল library mature হয় তার exception hierarchy দিয়ে। Beginner ব্যবহার করেন built-in; পেশাদার লেখেন custom। Caller-এর জন্য — exception type = এক ধরনের API।
প্র ০৪ Exception "expensive" নাকি "cheap"? Hot loop-এ কখন avoid? AI inference-এ try/except-এর performance impact কতটুকু?
এই প্রশ্ন performance ও correctness-এর সীমান্তে। ভুল সিদ্ধান্ত production AI inference-এ ১০× slowdown।
Exception cost — দু'টি অংশ:
- try block setup: Python 3.11+-এ প্রায় free (zero-cost exception)।
- Exception raise + handle: expensive — stack unwind, frame inspection, traceback।
Benchmark perspective:
import timeit
# try block alone (no exception)
def with_try():
try:
x = 1 + 1
except:
pass
# Without
def without_try():
x = 1 + 1
# Difference: ~5-10% (Python 3.10), nearly 0 (3.11+)
Exception raise:
# Raising → expensive
def raises():
try:
raise ValueError()
except:
pass
# ~১০-৫০× slower than no exception
Hot loop — কখন avoid:
- Inner loop, millions iterations।
- Exception যদি common (>1%) হয়।
- Inference per-request critical।
Hot loop-এ alternatives:
# খারাপ — exception in hot loop
for x in big_list:
try:
result = d[x]
except KeyError:
result = default
# ভাল — dict.get
for x in big_list:
result = d.get(x, default)
# আরও ভাল — pre-filter
valid = [x for x in big_list if x in d]
for x in valid:
result = d[x]
AI inference impact:
- Model serving — latency-sensitive (10-100 ms budget)।
- Per-request try/except — minimal impact (single try block)।
- Per-token try/except (LLM generation) — measurable।
- Validation pre-loop > exception in loop।
Profile-driven optimization:
import cProfile
cProfile.run("inference_loop()", sort="cumulative")
# Look for raise/except hotspots
Python 3.11 — zero-cost exceptions:
- PEP 657 — try block-এ no overhead যদি exception raise না হয়।
- Bytecode-level optimization।
- Common case (no error) এখন সত্যিই free।
Cython/C-extension consideration:
- NumPy/PyTorch — internal C, exception only at boundary।
- Vectorized ops — exception per element নয়, batch validate।
Modern best practice:
- Don't pre-optimize — exception cost rarely bottleneck।
- Profile first — actual hotspot find।
- Validate at boundary, not per-element।
- Hot inference — minimize per-iteration overhead overall।
Logging cost:
logging.exception()— full traceback expensive।- Hot path-এ — debug log gate করুন।
- Sampling logging in production।
Real-world examples:
- OpenAI API — request validation outer try।
- PyTorch DataLoader — sample-level exception catch (not per-element)।
- FastAPI — middleware-level exception handler।
মূল উপলব্ধি: Exception cost = "raising" + "handling"। Try block-এ থাকা cheap (especially 3.11+)। Common path-এ exception ঘটানো expensive। AI inference-এ — boundary validation + EAFP at boundary, vectorize within। Profile, don't guess।
অনুশীলন
-
Safe parser: একটি function লিখুন
safe_int(s)যা string-কে int-এ convert করে — fail হলে None ফেরায়।def safe_int(s): try: return int(s) except (ValueError, TypeError): return None print(safe_int("42")) # 42 print(safe_int("abc")) # None print(safe_int(None)) # None print(safe_int("3.14")) # None — float string -
File reader with fallback: একটি function — ফাইল পড়ে content দিন, না থাকলে empty string।
def read_or_empty(path): try: with open(path, encoding="utf-8") as f: return f.read() except FileNotFoundError: return "" except PermissionError: print(f"Permission denied: {path}") return "" print(repr(read_or_empty("missing.txt"))) # '' print(read_or_empty("hello.txt")[:20]) -
Custom exception:
InvalidScoreErrorclass বানান।validate_score(x)function — যদি 0-100-র বাইরে — raise।class InvalidScoreError(ValueError): pass def validate_score(x): if not isinstance(x, (int, float)): raise TypeError(f"score must be number, got {type(x).__name__}") if not (0 <= x <= 100): raise InvalidScoreError(f"score {x} not in [0, 100]") return x try: validate_score(150) except InvalidScoreError as e: print(f"ভুল স্কোর: {e}") try: validate_score("85") except TypeError as e: print(f"Type ভুল: {e}") print(validate_score(78)) # OK
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পাঠ ০৯ · NumPy পরিচিতি — ndarray পরবর্তী মডিউল M2 এখান থেকে AI-র জগৎ শুরু — array ও vectorization।
- পাঠ ০৭ · ফাইল পড়া ও লেখা আগের পাঠ
- মডিউল ১ পর্যালোচনা M1 শেষ Python ভিত্তি সম্পন্ন — অভিনন্দন!
- সব AI Courses দেখুন ABCL TECH
L01-L08 — Python-এর মৌলিক ভিত্তি। variable থেকে exception পর্যন্ত। M2-তে এখন AI-র মূল লাইব্রেরি — NumPy, Pandas, Matplotlib।