Python3 错误和异常
语法错误与异常、try/except/else/finally、抛出异常、自定义异常与清理行为。
语法错误
Syntax errors are the most common kind: the source does not conform to Python grammar, so the interpreter refuses to compile it and reports the line plus a ^ pointing near the problem.
try:
compile('while True print("hi")', '<demo>', 'exec')
except SyntaxError as e:
print("SyntaxError:", e.msg)
print("line:", e.lineno)
print("offset:", e.offset)
try:
compile('def f(:\n pass', '<demo>', 'exec')
except SyntaxError as e:
print("SyntaxError:", e.msg)SyntaxError: invalid syntax
line: 1
offset: 12
SyntaxError: invalid syntax异常
Even valid syntax can fail at runtime: division by zero, index out of range, missing key, type mismatch. These are exceptions — they abort the current flow and print a traceback; read it bottom-up to find the failing line.
cases = [
lambda: 1 / 0,
lambda: [1, 2][5],
lambda: {'a': 1}['b'],
lambda: 'a' + 1,
]
for fn in cases:
try:
fn()
except Exception as e:
print(type(e).__name__, '->', e)ZeroDivisionError -> division by zero
IndexError -> list index out of range
KeyError -> 'b'
TypeError -> can only concatenate str (not "int") to str异常处理
Put risky code in try, catch by type in except, run else when nothing raised, and finally always. Handlers match top-down, so list subclasses before their base classes.
def safe_div(a, b):
try:
result = a / b
except ZeroDivisionError:
print("cannot divide by zero")
return None
except TypeError as e:
print("bad operand:", e)
return None
else:
print("ok")
return result
finally:
print("-- finally --")
print(safe_div(6, 3))
print(safe_div(1, 0))
print(safe_div('a', 2))ok
-- finally --
2.0
cannot divide by zero
-- finally --
None
bad operand: unsupported operand type(s) for /: 'str' and 'int'
-- finally --
Noneexcept: or except Exception: pass — it swallows KeyboardInterrupt and real bugs alike. Catch only the specific types you can actually handle.抛出异常
raise throws deliberately — a class or an instance. raise ... from ... keeps the original cause visible, and a bare raise inside except re-raises unchanged.
def set_age(age):
if not isinstance(age, int):
raise TypeError("age must be int, got %s" % type(age).__name__)
if age < 0:
raise ValueError("age must be >= 0, got %d" % age)
return age
for v in (18, -1, 'x'):
try:
print("age:", set_age(v))
except (TypeError, ValueError) as e:
print(type(e).__name__, '->', e)
try:
try:
1 / 0
except ZeroDivisionError as e:
raise RuntimeError("compute failed") from e
except RuntimeError as e:
print("cause:", type(e.__cause__).__name__)age: 18
ValueError -> age must be >= 0, got -1
TypeError -> age must be int, got str
cause: ZeroDivisionError用户自定义异常
Custom exceptions subclass Exception (or a descendant) and are named ...Error. Adding attributes lets callers read extra context inside except.
class ValidationError(Exception):
def __init__(self, field, message):
super().__init__("%s: %s" % (field, message))
self.field = field
self.message = message
def validate(user):
if not user.get('email'):
raise ValidationError('email', 'is required')
return True
try:
validate({'name': 'Tom'})
except ValidationError as e:
print("field:", e.field)
print("message:", e.message)
print("str:", e)
print("is Exception:", isinstance(e, Exception))field: email
message: is required
str: email: is required
is Exception: True定义清理行为
The finally clause runs whether or not an exception occurred — even across return — so it is the place to release resources. Beware: a return inside finally overrides the value from try.
def f():
try:
print("try")
return "from try"
finally:
print("finally")
print(f())
def g():
try:
raise ValueError("boom")
finally:
print("cleanup runs even on error")
try:
g()
except ValueError as e:
print("caught:", e)try
finally
from try
cleanup runs even on error
caught: boom预定义的清理行为
Some objects clean up after themselves and with triggers it: closing files, releasing locks, returning connections. This is the context-manager protocol __enter__ / __exit__, where __exit__ plays the role of finally.
class Resource:
def __enter__(self):
print("acquire")
return self
def __exit__(self, exc_type, exc, tb):
print("release")
return False
with Resource() as r:
print("working")
try:
with Resource() as r:
raise ValueError("boom")
except ValueError as e:
print("caught:", e)
with open('out.txt', 'w', encoding='utf-8') as f:
f.write("hi")
print("file closed:", f.closed)acquire
working
release
acquire
release
caught: boom
file closed: True语法错误
语法错误(解析错误)是最常见的一类错误:代码不符合 Python 语法,解释器在编译阶段就拒绝执行,并指出出错行号和一个 ^ 指向大致位置。
try:
compile('while True print("hi")', '<demo>', 'exec')
except SyntaxError as e:
print("SyntaxError:", e.msg)
print("line:", e.lineno)
print("offset:", e.offset)
try:
compile('def f(:\n pass', '<demo>', 'exec')
except SyntaxError as e:
print("SyntaxError:", e.msg)SyntaxError: invalid syntax
line: 1
offset: 12
SyntaxError: invalid syntax异常
即使语法正确,运行时仍可能出错:除以零、下标越界、键不存在、类型不匹配。这类错误叫异常,会中断当前流程并打印「调用栈」,从下往上读就能定位到真正的出错行。
cases = [
lambda: 1 / 0,
lambda: [1, 2][5],
lambda: {'a': 1}['b'],
lambda: 'a' + 1,
]
for fn in cases:
try:
fn()
except Exception as e:
print(type(e).__name__, '->', e)ZeroDivisionError -> division by zero
IndexError -> list index out of range
KeyError -> 'b'
TypeError -> can only concatenate str (not "int") to str异常处理
try 里放可能出错的代码,except 按异常类型捕获,else 在无异常时执行,finally 无论如何都执行。捕获顺序从上到下匹配,所以子类要写在父类前面。
def safe_div(a, b):
try:
result = a / b
except ZeroDivisionError:
print("cannot divide by zero")
return None
except TypeError as e:
print("bad operand:", e)
return None
else:
print("ok")
return result
finally:
print("-- finally --")
print(safe_div(6, 3))
print(safe_div(1, 0))
print(safe_div('a', 2))ok
-- finally --
2.0
cannot divide by zero
-- finally --
None
bad operand: unsupported operand type(s) for /: 'str' and 'int'
-- finally --
Noneexcept: 或 except Exception: pass —— 它会吞掉 KeyboardInterrupt 和真正的 bug。只捕获你确实能处理的具体异常类型。抛出异常
用 raise 主动抛出异常,可以抛内置类型也可以抛实例;raise ... from ... 保留原始异常链,便于排查根因。在 except 里裸 raise 会原样重新抛出。
def set_age(age):
if not isinstance(age, int):
raise TypeError("age must be int, got %s" % type(age).__name__)
if age < 0:
raise ValueError("age must be >= 0, got %d" % age)
return age
for v in (18, -1, 'x'):
try:
print("age:", set_age(v))
except (TypeError, ValueError) as e:
print(type(e).__name__, '->', e)
try:
try:
1 / 0
except ZeroDivisionError as e:
raise RuntimeError("compute failed") from e
except RuntimeError as e:
print("cause:", type(e.__cause__).__name__)age: 18
ValueError -> age must be >= 0, got -1
TypeError -> age must be int, got str
cause: ZeroDivisionError用户自定义异常
自定义异常继承 Exception(或其子类),命名以 Error 结尾。给异常加上自定义属性,可以让调用方在 except 里拿到更多上下文。
class ValidationError(Exception):
def __init__(self, field, message):
super().__init__("%s: %s" % (field, message))
self.field = field
self.message = message
def validate(user):
if not user.get('email'):
raise ValidationError('email', 'is required')
return True
try:
validate({'name': 'Tom'})
except ValidationError as e:
print("field:", e.field)
print("message:", e.message)
print("str:", e)
print("is Exception:", isinstance(e, Exception))field: email
message: is required
str: email: is required
is Exception: True定义清理行为
finally 子句无论是否发生异常、是否 return 都会执行,用来释放资源。注意:finally 里的 return 会覆盖 try 里的返回值,是个隐蔽的坑。
def f():
try:
print("try")
return "from try"
finally:
print("finally")
print(f())
def g():
try:
raise ValueError("boom")
finally:
print("cleanup runs even on error")
try:
g()
except ValueError as e:
print("caught:", e)try
finally
from try
cleanup runs even on error
caught: boom预定义的清理行为
一些对象自带清理逻辑,用 with 语句即可自动触发:文件关闭、锁释放、数据库连接归还。这背后是上下文管理器协议 __enter__ / __exit__,__exit__ 等价于 finally。
class Resource:
def __enter__(self):
print("acquire")
return self
def __exit__(self, exc_type, exc, tb):
print("release")
return False
with Resource() as r:
print("working")
try:
with Resource() as r:
raise ValueError("boom")
except ValueError as e:
print("caught:", e)
with open('out.txt', 'w', encoding='utf-8') as f:
f.write("hi")
print("file closed:", f.closed)acquire
working
release
acquire
release
caught: boom
file closed: True