Skip to content

异常与错误处理

本章讲解Cython的异常处理机制。Cython支持Python风格异常和C级错误码,两者的混合使用需要小心处理。

学习路径:异常机制 → C级错误处理 → 自定义异常 → 调试技巧

核心概念:

  • except *:未知错误处理
  • except -1:返回值指示错误
  • with gil:GIL区域内抛出Python异常

功能说明:使用raise抛出Python异常。

# Python风格异常
def raise_error():
raise ValueError("Invalid value")
# Cython中也可以
cpdef void check_positive(int x) except *:
if x < 0:
raise ValueError("x must be positive")

输出示例:

>>> check_positive(-1)
ValueError: x must be positive

功能说明:声明函数如何处理异常。

# except * - 未知错误时设置Python异常
cdef int risky() except *:
# 如果函数返回时存在待处理的异常,设置Python异常
return might_fail()
# except -1 - 返回-1时设置异常(仅对有符号整数)
cdef int risky2() except -1:
return might_fail()
# except? - 返回值可能无效(条件异常)
cdef int conditional() except? -1:
return might_return_neg()

说明:

声明行为
except *函数返回后检查异常
except -1返回-1时设置异常
except? -1可能返回-1,不确定

功能说明:异常自动从cdef函数传播到Python调用者。

# 异常自动传播
cdef void outer():
inner() # 如果inner()抛出异常,会自动传播
cdef void inner():
raise RuntimeError("Error from inner")
# 测试
try:
outer()
except RuntimeError as e:
print(f"Caught: {e}")

输出示例:

>>> outer()
RuntimeError: Error from inner

功能说明:将C函数错误码转换为Python异常。

# C函数返回错误码
cdef extern from "math.h":
double sqrt(double)
int isnan(double)
cdef double safe_sqrt(double x) except *:
if isnan(x):
raise ValueError("Cannot sqrt NaN")
if x < 0:
raise ValueError("Cannot sqrt negative")
return sqrt(x)

功能说明:条件检查后抛出异常。

# 使用条件检查
cdef int divide(int a, int b) except *:
if b == 0:
raise ZeroDivisionError("division by zero")
return a // b
# 快速检查宏(不抛异常)
cdef int checked_divide(int a, int b):
return a // b if b != 0 else 0

功能说明:确保异常发生时资源正确释放。

# 异常安全的资源管理
cdef class SafeResource:
cdef void* _handle
def __cinit__(self):
self._handle = allocate()
if self._handle == NULL:
raise MemoryError("Failed to allocate")
def __dealloc__(self):
if self._handle != NULL:
deallocate(self._handle)
# try-finally模式(异常安全)
cdef void raii_pattern() except *:
cdef double* data = <double*>malloc(100 * sizeof(double))
if data == NULL:
raise MemoryError()
try:
process_data(data, 100)
finally:
free(data)

最佳实践:使用RAII模式管理资源,异常发生时自动释放。


功能说明:定义Python异常类用于特定错误。

# 定义Cython异常类
class MyError(Exception):
"""Custom error for my module"""
def __init__(self, str message, int code=0):
self.code = code
super().__init__(message)
# 使用
cpdef void validate(int value) except *:
if value < 0:
raise MyError("Value cannot be negative", code=1001)

输出示例:

>>> validate(-1)
MyError: Value cannot be negative

功能说明:异常类可包含额外属性。

class ConfigError(Exception):
cdef str _key
cdef str _value
def __init__(self, str key, str message):
self._key = key
self._value = message
super().__init__(f"{key}: {message}")
property key:
def __get__(self):
return self._key
property value:
def __get__(self):
return self._value
cpdef void load_config(str key) except *:
if key == "invalid":
raise ConfigError(key, "Key is invalid")

功能说明:异常可以组织成层次结构。

# 异常层次
class ValidationError(Exception):
pass
class RangeError(ValidationError):
pass
class TypeError(ValidationError):
pass
cpdef void validate_range(int x, int min_val, max_val) except *:
if x < min_val or x > max_val:
raise RangeError(f"{x} not in range [{min_val}, {max_val}]")
cpdef void validate_type(x, expected_type) except *:
if not isinstance(x, expected_type):
raise TypeError(f"Expected {expected_type}, got {type(x)}")

功能说明:使用annotate分析生成的C代码。

# 查看生成的C代码
# bash: cython -a mymodule.pyx
# 会生成mymodule.html - 黄色区域需要优化
# 使用assert(仅debug模式)
cdef void debug_version(int x) except *:
assert x >= 0, "x must be non-negative"
# 正常逻辑

功能说明:print语句和C级printf。

# Python风格print
cdef void debug_print():
cdef int x = 10
print(f"Debug: x = {x}")
# C级printf(无GIL开销)
cdef void debug_cprint():
cimport libc.stdio as stdio
stdio.printf("C debug: %d\n", 10)
# pdb调试
# pyximport.install()
# import pdb; pdb.set_trace()

功能说明:使用GDB调试生成的C代码。

Terminal window
# 生成调试信息
cython -a module.pyx
gcc -g -I/usr/include/python3.10 module.c -o module.so
# GDB调试
gdb python
(gdb) break module.c:123
(gdb) run test.py

机制用途
except *未知错误处理
except -1返回值指示错误
except? -1可选错误处理
raise抛出Python异常
with gilGIL区域内抛异常
try-finally异常安全的清理
  1. 使用except *处理可能返回错误的函数
  2. 资源分配在__cinit__中,异常在__dealloc__中释放
  3. 避免在nogil块中抛异常(先用with gil)
  4. 用assert进行开发期检查,生产用条件检查

  1. 实现一个安全的除法函数,处理除零情况
  2. 创建自定义异常类ProcessingError,包含错误码和消息
  3. 使用try-except处理C级内存分配失败
  4. 使用cython -a分析代码,识别黄色热点区域
  5. 实现RAII模式的资源管理类
  6. 用GDB调试生成的C代码,设置断点查看变量