Mga exception sa Python: try, except, else, finally, raise, at sariling exception
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 "<stdin>", line 1, in <module>
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 <module>
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 natry/except KeyErrorsa 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.
Mga madalas itanong
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.
Detalyadong tinatalakay ang paksang ito, may praktis, sa kursong “Python 3.15: от новичка до профи”.
Pumunta sa kurso