# Mga exception sa Python: try, except, else, finally, raise, at sariling exception

URL: https://skillaria.top/tl/guides/python/exception-handling-python

Ang exception sa Python ay object ng error na humihinto sa takbo ng code hanggang sa hulihin ito ng try/except; tumatakbo ang else kapag walang error, at palaging tumatakbo ang finally. Maaaring magtaas ng exception sa sarili gamit ang raise, halimbawa raise ValueError("negatibo ang halaga"), at ang sariling exception ay ginagawa bilang klase na nagmamana ng Exception.

Ang exception sa Python ay object na nagsasabi na may error at humihinto sa normal na takbo ng programa. Kapag walang nakahuli rito, nagpi-print ang Python ng traceback at isinasara ang programa. Hinuhuli ang exception gamit ang `try/except`, at itinataas ito gamit ang `raise`. Tatalakayin natin kung paano ito gumagana, aling mga error ang pinakakaraniwan, at paano hindi gawing pagtatago ng error ang paghawak nito.

## Ano ang exception sa Python

Kapag hindi maisakatuparan ang isang operasyon — paghahati sa zero, pag-access sa key na wala sa dictionary, pag-convert ng salita sa numero — gagawa ang Python ng object ng exception at "itatapon" ito. Humihinto ang kasalukuyang function, at umaakyat ang exception sa kadena ng mga tawag hanggang sa may makitang angkop na handler. Kapag wala, bumabagsak ang programa.

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

Ang sagot sa madalas na tanong: kapag hinati sa zero sa Python, nagkakaroon ng exception na `ZeroDivisionError`. Pareho ito para sa `/`, `//`, at `%`.

## Paano basahin ang traceback

Ang traceback ay ulat kung paano nakarating ang programa sa error. Mas madaling basahin ito mula ibaba pataas. Ang huling linya ang pinakamahalaga: ang uri ng exception at ang mensahe. Sa itaas nito, ang linya ng code kung saan nangyari ang error, at mas taas pa, kung sino ang tumawag dito, hanggang sa punto ng pagpasok.

```
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: 'isang daan'
```

Mula Python 3.11, may mga marka sa ilalim ng linya ng code na `~~~^^^` (PEP 657): ipinapakita nila kung aling eksaktong ekspresyon sa linya ang bumagsak. Malaking tulong ito kapag maraming tawag ang magkakasunod sa iisang linya.

### Mga mungkahing "Did you mean"

Mula Python 3.10, kapag may typo sa pangalan, nagmumungkahi ang error ng malapit na pangalan. Sa Python 3.15, mas matalino na ang mga mungkahi: alam ng interpreter ang mga gawi mula sa ibang wika. Ang pagtawag sa `list.push` ay magmumungkahi ng `append`, at ang `"kape".toUpperCase()` ay ng method na `upper`. Tumitingin din ang mungkahi sa mga nested na field: kung ang dictionary ay nasa attribute na `totals` ng object, at tinawag mo ang `items()` sa mismong object, magmumungkahi ito ng `.totals.items`. Pati ang `delattr()` ay nagmumungkahi na ngayon ng malalapit na pangalan.

## Mga uri ng exception sa Python

Dose-dosenang built-in na exception ang mayroon, pero sa simula, ang mga ito ang pinakamadalas mong makita:

| Exception | Kailan lumilitaw | Halimbawa |
| --- | --- | --- |

| `NameError` | Hindi natukoy ang pangalan, kadalasan typo | `print(totl)` |

| `TypeError` | Hindi angkop ang operasyon sa uring ito | `"5" + 3` |

| `ValueError` | Tama ang uri, pero hindi tama ang halaga | `int("isang daan")` |

| `KeyError` | Walang ganitong key sa dictionary | `d["missing"]` |

| `IndexError` | Lampas sa saklaw ng listahan ang index | `[1, 2][5]` |

| `AttributeError` | Walang ganoong method o field ang object | `[].push(1)` |

| `ZeroDivisionError` | Paghahati sa zero | `1 / 0` |

| `FileNotFoundError` | Hindi nakita ang file | `open("nope.txt")` |

Espesyal ang `SyntaxError` (at ang partikular na kaso nitong `IndentationError`): lumilitaw ito habang binabasa ang file, bago pa patakbuhin ang code, kaya hindi ito mahuhuli ng `try` sa parehong file.

### Hierarchy ng mga exception

Mga klase ang mga exception at nakaayos ang mga ito sa hierarchy. Nasa tuktok ang `BaseException`. Dito nagmamana ang `Exception` (halos lahat ng "karaniwang" error) at ilang sistemang exception: `KeyboardInterrupt`, `SystemExit`, `GeneratorExit`.

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

Mahalaga ang hierarchy sa paghuli: ang `except LookupError` ay huhuli ng `KeyError` at `IndexError`. Ang `KeyboardInterrupt` naman ay hindi error sa code: ipinapahiwatig nito na pinindot ng user ang Ctrl+C. Sinadyang hindi ito nagmamana mula sa `Exception`, para hindi mapigilan ng `except Exception` ang pagpapahinto sa programa.

## Paghawak ng exception: try/except/else/finally

Inilalagay sa block na `try` ang code na maaaring bumagsak. Sa block na `except`, tinutukoy ang uri ng error at kung ano ang gagawin dito. Ang `except` ang block na ginagamit ng Python para sa paghawak ng exception.

```
def parse_amount(raw):
    try:
        value = float(raw)
    except ValueError as e:
        print(f"Nilaktawan ang linya: {e}")
        return None
    else:
        return value          # tumatakbo kapag walang error
    finally:
        print("tapos na ang pag-parse")   # palaging tumatakbo

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

Sa halimbawang ito, unang lalabas ang "tapos na ang pag-parse", saka ang `250.0`. Inaalala ng `return` sa block na `else` ang halaga; pagkatapos, tiyak na tatakbo ang `finally` bago ibalik ang resulta. Kaya maaasahan ang `finally` para sa paglilinis: pagsara ng file, pagpapalaya ng koneksiyon.

Kapag may sariling `return` ang `finally`, papalitan nito ang naunang return at maaari pang "lamunin" ang exception. Mula Python 3.14, nagbibigay ng `SyntaxWarning` (PEP 765) ang ganitong code — huwag maglagay ng `return` sa `finally`.

### Ilang exception nang sabay

Puwedeng magsulat ng ilang block na `except` para sa iba't ibang uri, o ilista ang mga uri sa tuple:

```
try:
    amount = int(data["amount"])
except KeyError:
    amount = 0
except (ValueError, TypeError) as e:
    print("Maling halaga:", e)
    amount = 0
```

Sinusuri ang mga block mula itaas pababa, at ang unang tumutugma ang tatakbo, kaya dapat nauuna ang mas tiyak na uri kaysa sa pangkalahatan. Mula Python 3.14 (PEP 758), puwedeng ilista ang maraming uri nang walang panaklong — `except ValueError, TypeError:` — pero kapag may `as`, kailangan pa rin ang panaklong.

### Paano ipakita ang mensahe ng error at alamin ang uri nito

Makukuha ang object ng exception sa pamamagitan ng `as e`. Ang `str(e)` ang nagbibigay ng mensahe, at ang `type(e).__name__` ng pangalan ng uri:

```
try:
    int("isang daan")
except ValueError as e:
    print(type(e).__name__)   # ValueError
    print(e)                  # invalid literal for int() with base 10: 'isang daan'
```

## Module na traceback: buong teksto ng error

Minsan kailangang hulihin ang error, pero itago ang buong traceback — para sa log o ulat. Para dito, may module na `traceback`:

```
import traceback

try:
    1 / 0
except ZeroDivisionError:
    traceback.print_exc()             # ini-print ang traceback sa stderr
    text = traceback.format_exc()     # parehong teksto bilang string
```

Kung gumagamit ka ng module na `logging` para sa mga log, mas madaling tawagin ang `logging.exception("hindi ma-kalkula ang gastos")` sa loob ng `except` — awtomatikong idadagdag ang traceback sa mensahe.

## Python raise: paano magtaas ng exception

Itinatapon ng statement na `raise` ang exception nang manu-mano. Sa ganitong paraan, sinasabi ng function na hindi nito kayang gawin ang trabaho sa ganoong datos:

```
def add_expense(amount):
    if amount < 0:
        raise ValueError(f"Hindi maaaring negatibo ang halaga: {amount}")
    ...

add_expense(-5)
# ValueError: Hindi maaaring negatibo ang halaga: -5
```

Ang Python raise ay statement, hindi function, kaya hindi kailangan ng panaklong pagkatapos ng salitang `raise`: ang nasa loob ng panaklong ay mga argumento para sa constructor ng exception. Pumili ng uri ayon sa kahulugan: maling halaga — `ValueError`, maling uri — `TypeError`.

### raise na walang argumento at raise from

Sa loob ng `except`, ang simpleng `raise` ay ipinapasa ang kasalukuyang exception nang walang pagbabago. Kapaki-pakinabang ito kapag gusto mong magtala sa log pero iwan ang pangangasiwa sa tumatawag.

Kapag pinapalitan ang isang exception ng iba, gamitin ang `raise ... from e` — mananatili sa kadena ang orihinal na error, at makikita sa traceback ang pareho:

```
def load_config(path):
    try:
        with open(path) as f:
            return f.read()
    except FileNotFoundError as e:
        raise RuntimeError(f"Walang config na {path}") from e
```

### add_note: idagdag ang konteksto sa error

Mula Python 3.11 (PEP 678), maaaring magdagdag ng tala sa exception gamit ang method na `add_note()` at saka ito ipasa pa. Hindi nagbabago ang uri ng error, at lilitaw ang tala sa ilalim ng mensahe:

```
def parse(raw, line_no):
    try:
        return float(raw)
    except ValueError as e:
        e.add_note(f"file expenses.csv, linya {line_no}")
        raise

parse("isang daan", 17)
# ValueError: could not convert string to float: 'isang daan'
# file expenses.csv, linya 17
```

## Paano gumawa ng sariling exception sa Python

Kailangan ang sariling exception para sa mga error ng domain: nalampasan ang budget, bayad na ang order, naka-block ang user. Sapat na ang klaseng nagmamana ng `Exception`:

```
class BudgetExceeded(Exception):
    def __init__(self, category, limit, spent):
        super().__init__(f"{category}: limitasyon {limit}, nagastos {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("Pagkain", 10000, 12500)
except BudgetExceeded as e:
    print("Babala:", e)   # Babala: Pagkain: limitasyon 10000, nagastos 12500
```

Ngayon, eksaktong nahuhuli ng tumatawag na code ang paglampas sa budget at hindi ito nalilito sa mga typo. Sa malaking proyekto, makabubuting gumawa ng pangkalahatang base class (`class TrackerError(Exception)`) at magmana rito ang lahat ng sariling exception — kaya maaari silang hulihin sa iisang `except TrackerError`. Magmana mula sa `Exception`, hindi mula sa `BaseException`.

## Mga grupo ng exception at except*

Minsan, ilang error ang sabay-sabay — halimbawa, sa pagsusuri ng buong file, o sa mga parallel na gawain ng `asyncio.TaskGroup`. Mula Python 3.11, maaaring pagsama-samahin ang mga ito sa `ExceptionGroup`, at hawakan ayon sa uri gamit ang `except*`:

```
def check_all(rows):
    errors = []
    for r in rows:
        try:
            float(r)
        except ValueError as e:
            errors.append(e)
    if errors:
        raise ExceptionGroup("mga error sa file", errors)

try:
    check_all(["1", "a", "b"])
except* ValueError as eg:
    print(f"Mga sirang linya: {len(eg.exceptions)}")   # Mga sirang linya: 2
```

## Python try except sa lahat ng error: bakit hindi ito dapat gawin

Ang pinakamasamang ugali ay ang paghuli sa lahat ng bagay: `except: pass`. Hindi na "bumabagsak" ang programa, pero sa katotohanan, hindi na rin nito iniuulat ang mga problema. Ang bare na `except` ay humuhuli pati sa `KeyboardInterrupt` at `SystemExit`, kaya hindi na mapipigilan ang programa gamit ang Ctrl+C. Mas mabuti nang kaunti ang `except Exception: pass`, pero tahimik pa rin nitong nilalamon ang mga typo sa pangalan at mga lohikal na error.

Paano wastong humawak ng exception sa Python:

- hulihin lamang ang uring kaya mong hawakan: `except ValueError`, hindi lahat;
- panatilihing maikli ang block na `try` — tanging mga linyang maaaring bumagsak nang ganoon ang nandoon;
- kung malawak ang paghuli (`except Exception`) sa pinakataas na antas ng programa, siguraduhing mag-log ng traceback;
- huwag gamitin ang exception para sa karaniwang sangay kapag sapat na ang pagsusuri: `dict.get()` sa halip na `try/except KeyError` sa mga simpleng kaso;
- para sa mga resource, piliin ang `with` — awtomatiko nitong isinasara ang file kahit may error.

## Buod

Ang mga exception sa Python ay normal na mekanismo para mag-ulat ng error, hindi dahilan para itago ang mga ito. Basahin ang traceback mula ibaba pataas, hulihin ang tiyak na uri sa `try/except`, gamitin ang `else` para sa code na "kung naging maayos ang lahat", at ang `finally` para sa paglilinis. Pinapayagan ng Python raise na ikaw mismo ang mag-ulat ng error, pinananatili ng `raise from` at `add_note` ang konteksto, at nagmamana ang sariling exception mula sa `Exception`. Tinalakay ang paksang ito nang praktikal sa kurso ng Python: hindi na bumabagsak ang tagasubaybay ng gastos dahil sa isang sirang linya ng CSV, at ipinapahayag nito ang paglampas sa budget gamit ang sariling exception.

## FAQ

**Ano ang pagkakaiba ng error at exception sa Python?**

Sa karaniwang usapan, magkasingkahulugan ang mga ito. Sa teknikal na aspeto, nahuhuli na ng SyntaxError habang binabasa ang code, samantalang ang ibang error ay lumilitaw habang tumatakbo bilang mga object ng exception na maaaring hulihin.

**Maaari bang ipagpatuloy ang takbo mula sa mismong lugar kung saan nangyari ang exception?**

Hindi. Pagkatapos mahuli, nagpapatuloy ang takbo pagkatapos ng block na try/except, hindi mula sa linyang pinagmulan ng error. Kung kailangang iproseso ang bawat linya ng file, ilagay ang try sa loob ng loop.

**Ano ang mangyayari kung may exception sa loob ng except?**

Aakyat ito pataas, at mananatili ang orihinal na exception sa attribute nitong __context__. Lilitaw sa traceback ang pariralang During handling of the above exception, another exception occurred.

**Paano malalaman sa mga test na nagtataas ng exception ang function?**

Sa pytest, ginagamit ang with pytest.raises(ValueError):; sa unittest, ang self.assertRaises(ValueError). Papasa ang test lamang kung may lumitaw na exception ng tamang uri sa loob ng block.

**Ano ang assert at ano ang pagkakaiba nito sa raise?**

Ang assert kondisyon, "mensahe" ay nagtataas ng AssertionError kapag mali ang kondisyon. Ito ay kasangkapan para sa pagsusuri habang nagde-develop; kapag pinatakbo nang may flag na -O, dine-disable ang assert, kaya dapat gamitin ang if at raise sa pagsusuri ng input ng user.


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