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

Python子类未重写基类方法时,装饰器如何显示子类方法限定名?

问题解决思路

核心问题在于:基类方法被子类实例调用时,装饰器内的func仍是基类的函数对象,导致func.__qualname__始终指向基类方法。要获取实际调用的子类限定名,需从方法的第一个参数(实例self或类cls)中提取当前类的信息。

修改后的装饰器代码

import functools
import logging

def log(_func=None, *, logger):
    def decorator_log(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # 动态获取当前调用的类限定名+方法名
            current_qualname = func.__qualname__
            if args:
                first_arg = args[0]
                # 处理实例方法(第一个参数是self)
                if hasattr(first_arg, '__class__'):
                    current_qualname = f"{first_arg.__class__.__qualname__}.{func.__name__}"
                # 处理类方法(第一个参数是cls)
                elif isinstance(first_arg, type):
                    current_qualname = f"{first_arg.__qualname__}.{func.__name__}"

            try:
                logger.info(f"Entering function {current_qualname}")
                args_repr = [repr(a) for a in args]
                kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]
                signature = ", ".join(args_repr + kwargs_repr)
                logger.debug(f"function {current_qualname} called with args {signature}")
                
                result = func(*args, **kwargs)
                
                logger.info(f"Leaving function {current_qualname}")
                return result
            except Exception as e:
                logger.exception(f"Exception raised in {current_qualname}. exception: {str(e)}")
                raise
        return wrapper

    if _func is None:
        return decorator_log
    else:
        return decorator_log(_func)

关键修改说明

  • 动态生成限定名:
    检查方法的第一个参数:如果是实例(self),则用self.__class__.__qualname__获取子类的限定名;如果是类(cls,类方法场景),直接用cls.__qualname__。无有效第一个参数时, fallback 到原func.__qualname__,兼容静态方法和普通函数。

  • 修复不可达代码:
    原装饰器中logger.info("Leaving...")位于return/raise之后,永远无法执行。现在调整到方法正常执行后的return前,确保离开日志能输出。

  • 优化异常处理:
    移除外层冗余的try-except(原代码会隐藏装饰器自身错误),仅保留业务方法的异常捕获,保证异常能正常向上抛出。

使用验证示例

基类定义

class BaseController:
    logger = logging.getLogger(__name__)

    @log(logger=logger)
    def initialize_components(self):
        print("Base initialize")

子类定义(未重写基类方法)

class MainController(BaseController):
    logger = logging.getLogger(__name__)

    def __init__(self):
        pass

# 测试调用
if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    mc = MainController()
    mc.initialize_components()

输出结果

INFO:__main__:Entering function MainController.initialize_components
Base initialize
INFO:__main__:Leaving function MainController.initialize_components

可见,即使子类未重写基类方法,日志仍会显示子类的限定名MainController.initialize_components。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 06:54:51