পাঠ ০৮ · ২৫-এর মধ্যে · মডিউল ১

Error handling ও exception

Errors & exceptions — robust code
৬ মিনিট পড়া শুরু · Beginner ব্রাউজারে কোড চালান

এই পাঠে যা শিখবেন

  • 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 করে।

Python
# Handle না করলে — program crash
result = 10 / 0          # ZeroDivisionError
print("কখনো এখানে আসবে না")

    
Run করলে দেখবেন ZeroDivisionError: division by zero। Code-এর পরের লাইন আর চলে না।

২ · try/except — error ধরা

Python
# 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-block-এর ৪ অংশ

১) try: — risky কোড।
২) except SomeError: — match হলে এই block।
৩) else: — try সফল হলে (rare)।
৪) finally: — যাই হোক চলবে — cleanup-এ।

৩ · else ও finally

Python
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

Python
# 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          # উপরে পাঠাও

    
"Bare except" এড়িয়ে চলুন। except: বা except Exception: সব ধরে ফেলে — KeyboardInterrupt-ও। নির্দিষ্ট exception ধরুন।

৬ · Custom exception class

Python
# 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}")

    
Custom exception — domain-specific error। Caller দেখলেই বুঝবে কী ভুল। AI library-তে — OutOfMemoryError, NaNError, SchemaMismatch ইত্যাদি common।
try/except/else/finally — flow execution path try block risky code Exception? না else block হ্যাঁ except block finally block always runs পরের কোড
try → exception আছে কি? — হ্যাঁ → except, না → else। দু'পথেই finally। চারটি block-এর সমন্বয়।

৭ · EAFP বনাম LBYL — Python philosophy

Python
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 ভুল")

    
LBYL = "ঘরে যাবার আগে দরজা চেক"। EAFP = "ঢুকে যাও, বন্ধ থাকলে আবার চেষ্টা"। Python-এ EAFP — exception cheap, race condition-free।

৮ · AI training-এ robust error handling

Python
# 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"))

    
AI training-এ — 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:

  1. Specific: except FileNotFoundError: — perfect।
  2. Group: except (ValueError, TypeError): — multiple।
  3. Domain: except IOError: — class hierarchy।
  4. Last resort: except Exception as e: — log + re-raise।
  5. 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, RuntimeError with shape info।
  • HuggingFace: OSError for model not found, custom RepositoryNotFoundError।
  • Pydantic: ValidationError with 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।

অনুশীলন

  1. 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
  2. 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])
  3. Custom exception: InvalidScoreError class বানান। 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-এ আপনার পরবর্তী পদক্ষেপ

কোড রানার কাজ না করলে? ব্রাউজারে কাজ না করলে Google Colab ব্যবহার করুন।
🎉 মডিউল ১ শেষ!

L01-L08 — Python-এর মৌলিক ভিত্তি। variable থেকে exception পর্যন্ত। M2-তে এখন AI-র মূল লাইব্রেরি — NumPy, Pandas, Matplotlib।

পূর্ববর্তী পাঠ
পাঠ ০৭ · ফাইল পড়া ও লেখা