Rilasciato · in miglioramento
Guida a Python · 5/6
Per ora questo capitolo è disponibile solo in inglese.
Python reports problems by raising exceptions rather than returning error codes. This chapter covers the four parts of a try statement, raising your own exceptions and designing exception hierarchies, context managers for reliable cleanup, and testing your code with pytest and the standard unittest module.
When an exception is raised, normal execution stops and Python unwinds the call stack looking for a matching handler. If none is found, the program prints a traceback and exits. A try statement has up to four clauses:
| Clause | Runs when |
|---|---|
try | Always; this is the code that might fail |
except | An exception of the listed type was raised |
else | The try block finished without raising |
finally | Always, last, whether or not something failed |
def read_number(path: str) -> int:
try:
f = open(path, encoding="utf-8")
except FileNotFoundError:
print(f"{path} does not exist")
return 0
else:
try:
return int(f.read().strip())
except ValueError as e:
print(f"not a number: {e}")
return 0
finally:
f.close() # runs on success and on failureCatch only the exceptions you can actually handle. A bare except: or a blanket except Exception: swallows genuine bugs along with the failure you expected. To handle several types the same way, list them in a tuple: except (KeyError, IndexError):.
Use raise when input or state is invalid. For domain-specific failures, subclass Exception. A common pattern is one base class per library with more specific subclasses underneath, so callers can catch broadly or narrowly as they prefer.
class PaymentError(Exception):
"""Base class for payment failures."""
class InsufficientFunds(PaymentError):
def __init__(self, needed: int, available: int) -> None:
super().__init__(f"needed {needed}, only {available} available")
self.needed = needed
self.available = available
def pay(balance: int, amount: int) -> int:
if amount <= 0:
raise ValueError("amount must be positive")
if amount > balance:
raise InsufficientFunds(amount, balance)
return balance - amount
try:
pay(1000, 5000)
except PaymentError as e:
print("payment failed:", e)When you catch a low-level error and re-raise something more meaningful, write . The original cause stays attached to the traceback, which makes debugging much easier.
raise NewError(...) from eFiles, network connections and locks must be released after use. The with statement guarantees that cleanup happens when the block exits, even if an exception escapes it, and it reads far better than a hand-written try/finally. To write your own, the contextlib.contextmanager decorator turns a generator into a context manager.
import time
from contextlib import contextmanager
with open("notes.txt", "w", encoding="utf-8") as f:
f.write("hello\n")
# the file is already closed here
@contextmanager
def timer(label: str):
start = time.perf_counter()
try:
yield
finally:
elapsed = time.perf_counter() - start
print(f"{label}: {elapsed:.3f}s")
with timer("summing"):
total = sum(range(1_000_000))pytest is the most popular testing tool in the Python world. It discovers files and functions whose names start with test_, and it lets you use the plain assert statement; when an assertion fails, it shows you exactly which values differed.
# tests/test_pay.py
import pytest
from payments import InsufficientFunds, pay
def test_pay_reduces_balance():
assert pay(1000, 300) == 700
def test_pay_rejects_overdraft():
with pytest.raises(InsufficientFunds):
pay(100, 500)
@pytest.mark.parametrize("amount", [0, -10])
def test_pay_rejects_non_positive(amount):
with pytest.raises(ValueError, match="positive"):
pay(1000, amount)
def test_writes_file(tmp_path):
target = tmp_path / "out.txt" # a fresh temporary directory per test
target.write_text("ok", encoding="utf-8")
assert target.read_text(encoding="utf-8") == "ok"python -m pip install pytest
pytest # run the whole suite
pytest -k overdraft -v # only tests matching "overdraft", verboselyRequesting tmp_path simply by naming it as a parameter is an example of a fixture: reusable setup that pytest injects for you. You can define your own with @pytest.fixture.
The standard library also ships unittest, so it works without installing anything. Tests are methods starting with test_ on a subclass of TestCase, and checks use methods such as assertEqual. pytest runs unittest-style tests too, so the two can coexist in one project.
import unittest
from payments import pay
class PayTest(unittest.TestCase):
def test_pay(self):
self.assertEqual(pay(1000, 300), 700)
def test_negative(self):
with self.assertRaises(ValueError):
pay(1000, -1)
if __name__ == "__main__":
unittest.main()try/except/else/finally, catching only exceptions you can deal with.Exception, and chain causes with from.with and context managers.assert, pytest.raises and fixtures; unittest remains available in the standard library.
0 commenti
Accedi · Accedi per lasciare un commento.
Scrivi tu il primo commento.