到目前为止,我们的程序都假设一切顺利。可现实不是:文件可能不存在、用户可能输错、网络可能断、除法的分母可能是 0。程序一遇到这些就崩溃,体验极差。

这一课,我们学会两件事:让程序优雅地处理错误(不崩),以及出错后快速定位问题(调试)


一、先认识几种常见报错

报错不可怕,看懂报错信息是关键。几种最常见的:

# 1. 名字打错了
print(x)                # NameError: name 'x' is not defined

# 2. 类型不对
"5" + 5                 # TypeError: can only concatenate str

# 3. 下标越界
[1, 2, 3][5]            # IndexError: list index out of range

# 4. 键不存在
{}["a"]                 # KeyError: 'a',键不存在会直接报错
{}.get("a")             # None,用 get 安全查,不报错

# 5. 分母为 0
1 / 0                   # ZeroDivisionError

# 6. 文件不存在
open("不存在.txt")       # FileNotFoundError

# 7. 类型转换失败
int("abc")              # ValueError

看报错时,重点看最后一行:错误类型 + 原因,以及上面几行:哪一行代码出的错。


二、try / except:把错误"接住"

try 包住可能出错的代码,用 except 接住错误,程序就不会崩:

try:
    num = int(input("输入一个数字:"))
    print("你输入的是", num)
except ValueError:
    print("输入的不是数字!")

用户输入了"abc"也不会崩,而是打印提示。

可以接住多种错误,分别处理:

try:
    x = int(input("数字:"))
    result = 100 / x
    print(result)
except ValueError:
    print("请输入数字")
except ZeroDivisionError:
    print("不能除以 0")

也可以不指定类型,兜底接住所有错误(但不推荐滥用,会掩盖真 bug):

except Exception as e:
    print("出错了:", e)
🧊 冷笑话:"程序员为什么总那么乐观?""因为他只写 try,从来不写 except。"

三、else / finally:成功时、无论如何时

try:
    f = open("data.txt", "r", encoding="utf-8")
except FileNotFoundError:
    print("文件不存在")
else:
    print("文件打开成功")      # try 没出错才执行
finally:
    print("无论成功失败都执行")  # 总是执行(常用于清理资源)

不过读文件我们已经用 with 了,它内部就自带清理逻辑,更推荐。


四、主动抛错:raise

有时候我们要主动"喊停",用 raise

def divide(a, b):
    if b == 0:
        raise ValueError("分母不能为 0")
    return a / b

divide(10, 0)    # 主动抛出 ValueError,并给出明确原因

五、调试:定位 bug 的三板斧

板斧一:print 大法

最简单也最常用——在关键处打印变量,看值对不对:

def 总分(scores):
    print("进入总分函数,收到:", scores)   # 调试打印
    return sum(scores)

板斧二:Python 自带的 pdb

在代码里插一行 breakpoint()(Python 3.7+),运行到这会暂停,进入交互调试:

def buggy(nums):
    total = 0
    for n in nums:
        total += n
        breakpoint()     # 停在这,可以敲 n 看值、c 继续
    return total

板斧三:读报错 + 二分定位

程序很长不知道哪错?从中间开始注释掉一半,看还错不错,反复二分,很快定位到出错的那一行。


六、logging:print 的正式升级版

print 调试法好上手,但真写项目时它有两个硬伤:

  1. 没级别:调试信息、普通信息、警告、错误全混在一起,上线后还得手动一个个删。
  2. 没法关:想"平时别打印,出问题再打印",print 做不到。

logging 就是为这个设计的——Python 自带的标准库,按级别记录信息,想开就开、想关就关。

基本用法

import logging

logging.basicConfig(level=logging.INFO)   # 设置最低显示级别

logging.debug("调试信息:x=5")     # 默认不显示
logging.info("用户登录成功")        # 显示
logging.warning("磁盘空间不足")     # 显示
logging.error("数据库连接失败")     # 显示
logging.critical("系统即将崩溃")    # 显示

级别从低到高

级别用途什么时候用
DEBUG调试细节定位问题时临时开
INFO正常流程记录"程序做了啥"
WARNING潜在问题不影响运行,但值得注意
ERROR出错某一步失败了
CRITICAL致命整个程序要挂了

basicConfig(level=...) 设的是"门槛"——低于这个级别的日志不会打印。比如设成 INFO,那 debug 就自动闭嘴,不用你手动删。

写到文件 + 带时间戳

默认日志打到屏幕,想落地到文件(这样程序跑在服务器上也能事后查):

import logging

logging.basicConfig(
    filename="app.log",          # 写到文件
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",   # 带时间和级别
    datefmt="%Y-%m-%d %H:%M:%S",
)

logging.info("启动程序")
logging.error("读取配置失败")

跑完打开 app.log,每行都有时间、级别、消息:

2026-09-08 04:40:39 [INFO] 启动程序
2026-09-08 04:40:39 [ERROR] 读取配置失败
经验谈:开发时用 DEBUG,上线时切成 INFOWARNING。这样调试信息平时自动隐藏,出问题再临时调低级别查,不用改代码。从此告别"上线前满屏找 print 挨个删"。

七、一个真实场景:健壮地读文件

把前面学的组合起来,写一个"怎么折腾都不崩"的读文件函数:

import logging

logging.basicConfig(level=logging.INFO)

def read_file_safely(path):
    try:
        with open(path, "r", encoding="utf-8") as f:
            return f.read()
    except FileNotFoundError:
        logging.warning(f"文件 {path} 不存在,返回空字符串")
        return ""
    except PermissionError:
        logging.error("没有权限读这个文件")
        return ""
    except Exception as e:
        logging.error(f"未知错误: {e}")
        return ""

content = read_file_safely("不存在的文件.txt")
print(content)

八、动手时间 🎯

实验 1:安全计算器

while True:
    expr = input("输入算式(如 10/2,q 退出):")
    if expr == "q":
        break
    try:
        a, op, b = expr.split()
        a, b = float(a), float(b)
        if op == "+":
            print(a + b)
        elif op == "/":
            print(a / b)
        else:
            print("只支持 + 和 /")
    except ZeroDivisionError:
        print("不能除以 0")
    except ValueError:
        print("格式不对,请输入如:10 / 2")

实验 2:读取配置文件,缺失则用默认值

import json

def load_config(path):
    try:
        with open(path, "r", encoding="utf-8") as f:
            return json.load(f)
    except FileNotFoundError:
        print("配置文件不存在,用默认值")
        return {"host": "localhost", "port": 80}

cfg = load_config("config.json")
print(cfg)

实验 3:用 breakpoint 调试一段代码

def process(nums):
    result = []
    for n in nums:
        if n > 0:
            result.append(n * 2)
    return result

breakpoint()          # 运行到这里暂停,可敲 process([-1,2,3]) 试
print(process([-1, 2, 3, -5]))

九、小结

  1. try/except 接住错误,程序不崩else 成功时跑、finally 总跑、raise 主动抛。
  2. 看报错看最后一行(类型+原因)和上面的行号,别被一堆红字吓住。
  3. 调试三板斧:print 大法 → breakpoint → 二分注释定位;正式项目用 logging 分级记录,DEBUG 开发、INFO/WARNING 上线,能写文件、带时间戳。

下一课,我们学 面向对象——用 class 把数据和操作打包成"对象",让代码更像真实世界,这是进阶编程的必经之路。

先把「安全计算器」和「健壮读文件」敲熟,异常处理的手感就长在你脑子里了。

标签: none

添加新评论