Python try/except 示例

只捕获你真的能处理的那个错误,并且让 try 块窄到你一眼就知道是哪一步失败了。下面这些写法覆盖精确捕获、清理、抛出、重试,以及那种让程序悄悄失败的宽泛捕获。

🎙️ 发布并录制于: ·

具体异常和多个处理分支

try 里只放你预期会失败的那一步。把用户输入的文本转成数字,就捕 ValueError。异常的细节对用户有用时,用 as e 把它绑定下来。超时该重试、连接断了该报警,这两种就分开写处理分支。

try:
    age = int(user_input)
except ValueError:
    print("that wasn't a number")

try:
    data = json.loads(text)
except json.JSONDecodeError as e:
    print(f"bad JSON at line {e.lineno}: {e.msg}")

try:
    resp = requests.get(url, timeout=5)
except requests.Timeout:
    retry()
except requests.ConnectionError:
    alert_ops()

except (ValueError, KeyError) as e:
    log(e)

把几个异常类写成一个元组,只有在它们的处理方式完全一样时才合适。恢复方式不同,就分开写 except,让策略留在明面上。

else 和 finally

else 只在 try 成功时执行。它把后续处理挪到受保护的块外面,这样 process 里的 bug 就不会被误认成打开文件失败。finally 不管成功、失败还是提前返回都会执行,所以适合放释放锁这类无条件的清理。

try:
    f = open(path, encoding="utf-8")
except FileNotFoundError:
    print("no such file")
else:
    process(f.read())
    f.close()
finally:
    release_lock()

对文件来说,with 语句通常比手动 close 更稳。finally 最有价值的场景是资源没有上下文管理器,或者清理动作跨了好几步操作。

抛出、重新抛出,和重试

调用方给了一个你的函数没法接受的值,就抛 ValueError。在 except 里写一个裸的 raise,会把同一个异常连着原始 traceback 一起往上送。做重试时只捕获临时性的失败,每失败一次就多等一会儿,最后一次尝试之后重新抛出。

def set_age(age):
    if age < 0:
        raise ValueError(f"age can't be negative, got {age}")

try:
    charge_card(order)
except PaymentError:
    log_incident(order)
    raise                          # same error and traceback

class QuotaExceeded(Exception):
    pass
for attempt in range(3):
    try:
        result = flaky_api_call()
        break
    except requests.Timeout:
        if attempt == 2:
            raise
        time.sleep(2 ** attempt)   # 1 second, then 2

不要重试校验错误、权限失败,也不要重试刷卡被拒。反复重试一个永久性的失败只会拖时间,还可能把副作用做重一遍。重试策略应该属于那个知道再试一次安不安全的操作。

修掉静默捕获和骗人的 traceback

典型症状是,代码跑完了,但什么都没发生。去搜 except 冒号 pass,或者 except Exception 冒号 pass。这些行会把真正的 traceback 丢掉,包括 NameError: name 'user_id' is not defined 这类消息。调试期间先把这个捕获删掉,或者用 logger.exception 把 traceback 记下来。

NameError: name 'user_id' is not defined

try:
    save_user(user_id)
except Exception:
    pass                           # hides the NameError

# If a top-level worker truly must stay alive:
try:
    run_one_job()
except Exception:
    logger.exception("job failed")  # records full traceback
具体怎么修
先临时删掉这个宽泛的处理分支,重跑一遍,把原始 traceback 露出来。修掉它点名的那一行。然后只捕获程序真的能恢复的那个具体异常。如果是在进程边界上,宽泛捕获确实是有意为之,那就用 logger.exception(...);它和 logger.error(e) 不一样,会带上 traceback。

裸 except 的范围还要更大:连 KeyboardInterrupt 这类信号都会被它吃掉。不要为了保险把整个函数包起来。让错误往上冒,直到某一层能做出真正的决定:重试、报给用户,还是停下来。

📚 本页是 Python 系列中的一篇。通勤路上也能听的完整速成课:10 分钟学会 Python →

Tell me what missed

A correction is more useful than a compliment. This goes straight to the person who writes SwiftGrasp.

Was this page useful?
0/1000

Please do not include passwords, private keys, or personal information.