You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python自定义异常为何显示模块名?异常处理写法优化咨询

Python类型检查装饰器相关问题解答

1. 自定义异常显示模块名的原因

Python中自定义异常类如果定义在某个模块(比如你的strict.py)里,默认打印异常时会带上模块名.类名,这是Python的默认规则——而内置异常属于builtins模块,Python对其做了特殊处理,不会显示模块名。

如果想让自定义异常打印时只显示类名,可以重写__repr__和__str__方法:

class TypeNotFoundError(TypeError):
    def __repr__(self):
        return self.__class__.__name__
    
    def __str__(self):
        return self.__class__.__name__

要是需要附带错误信息,就把内容整合进去:

class TypeNotFoundError(TypeError):
    def __init__(self, msg="未找到类型注解"):
        super().__init__(msg)
    
    def __repr__(self):
        return f"{self.__class__.__name__}: {self.args[0]}"
    
    def __str__(self):
        return f"{self.__class__.__name__}: {self.args[0]}"

2. 回溯显示报错行的差异

内置异常抛出时,Python会自动隐藏部分内部调用栈(比如装饰器、内置函数的调用行);而自定义异常的回溯会完整展示所有调用链路,包括装饰器里抛出异常的那一行。

如果想让回溯更简洁,可以用raise ... from None切断异常链,减少冗余信息:

def type_check_decorator(func):
    def wrapper(*args, **kwargs):
        if not func.__annotations__:
            raise TypeNotFoundError("函数未定义类型注解") from None
        # 其他类型检查逻辑
        return func(*args, **kwargs)
    return wrapper

from None会让Python只显示当前异常的回溯,效果更接近内置异常。

3. 直接使用raise是否可行

当然可行,这才是更合理的写法。如果你的try-except只是用来捕获自身逻辑的错误再重抛,完全没必要多余包裹,直接在条件不满足时用raise抛出即可:

# 不推荐的生硬写法
def wrapper(*args, **kwargs):
    try:
        if not func.__annotations__:
            raise TypeNotFoundError()
    except Exception as e:
        raise e

# 推荐的直接写法
def wrapper(*args, **kwargs):
    if not func.__annotations__:
        raise TypeNotFoundError("函数未提供类型提示")
    # 后续检查逻辑

直接raise代码更清晰,避免无意义的异常捕获和重抛。

4. 类型检查装饰器的标准实现模式

自己实现的话,利用inspect模块处理参数和注解是更可靠的方案,能覆盖位置参数、关键字参数、默认参数等场景。示例实现如下:

import inspect

class TypeNotFoundError(TypeError):
    def __init__(self, func_name):
        super().__init__(f"函数'{func_name}'未定义类型注解")
    
    def __repr__(self):
        return f"{self.__class__.__name__}: {self.args[0]}"
    
    def __str__(self):
        return self.args[0]

def type_check(func):
    sig = inspect.signature(func)
    annotations = func.__annotations__
    if not annotations:
        raise TypeNotFoundError(func.__name__)
    
    def wrapper(*args, **kwargs):
        # 绑定参数,自动处理默认值
        bound_args = sig.bind(*args, **kwargs)
        bound_args.apply_defaults()
        
        # 逐个检查参数类型
        for param_name, value in bound_args.arguments.items():
            if param_name in annotations:
                expected_type = annotations[param_name]
                if not isinstance(value, expected_type):
                    raise TypeError(
                        f"参数'{param_name}'预期类型为{expected_type.__name__},实际传入{type(value).__name__}"
                    )
        return func(*args, **kwargs)
    return wrapper

# 测试用例
@type_check
def test(a: int, b: str):
    print(a, b)

# 未加注解的函数,应用装饰器时会直接抛出异常
# @type_check
# def test_no_hint(a, b):
#     print(a, b)

如果不想重复造轮子,typeguard这类成熟库已经实现了完善的运行时类型检查,直接用就行,但自己实现确实是很好的学习过程。

内容的提问来源于stack exchange,提问作者gberenzy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 10:16:18