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

如何解决Python元类结合__repr__时的无限递归问题

元类实现日志时__repr__触发递归错误的原因与解决方法

问题重现

尝试用元类给类的所有方法自动添加日志装饰器,代码如下:

from functools import wraps

def custom_logger(fn):
  @wraps(fn)
  def inner(*args,**kwargs):
    result=fn(*args,**kwargs)
    print(f'LOG: {fn.__qualname__} {args},{kwargs}, result={result}')
    return result
  return inner

class custom_meta(type):
  def __new__(mcls,name,bases,namespace):
    new_obj=super().__new__(mcls,name,bases,namespace)
    for n,o in vars(new_obj).items():
      if callable(o):
        decorated_=custom_logger(o)
        setattr(new_obj,n,decorated_)
    return new_obj

class Person(metaclass=custom_meta):
  def __init__(self,name,age):
    self.name=name
    self.age=age
    
  def say_hello(self):
    print('hello')

  def __repr__(self):
    return 'hi'
    
p=Person('naxi',29);
p.say_hello()
print(p)

运行后触发递归错误:

File "main.py", line 9, in inner
    print(f'LOG: {fn.__qualname__} {args},{kwargs}, result={result}')
  [Previous line repeated 247 more times]
RecursionError: maximum recursion depth exceeded while getting the str of an object

但如果把__repr__换成__str__则能正常运行,这是为什么?该怎么解决?

原因分析

递归的核心逻辑链:

  1. __repr__被元类自动装饰,调用p.__repr__()时会先进入装饰器的inner函数
  2. 装饰器里执行print(f'LOG: ... {args} ...'),而args里包含Person实例p
  3. 格式化字符串时,对实例p会自动调用repr(p),也就是触发被装饰后的__repr__方法
  4. 这又会进入新一轮的装饰器逻辑,循环往复直到超出递归深度

而__str__不会触发递归的原因是:当执行print(p)调用被装饰的__str__时,装饰器格式化args中的实例会调用默认的__repr__(未被元类装饰),因此不会形成循环。

解决方法

有两种常见的解决思路:

思路1:元类中跳过装饰特殊方法

在元类遍历类成员时,排除以双下划线开头和结尾的特殊方法(或明确排除__repr__),避免装饰这些可能触发递归的方法:

class custom_meta(type):
  def __new__(mcls,name,bases,namespace):
    new_obj=super().__new__(mcls,name,bases,namespace)
    for n,o in vars(new_obj).items():
      # 跳过双下划线包裹的特殊方法
      if callable(o) and not (n.startswith('__') and n.endswith('__')):
        decorated_=custom_logger(o)
        setattr(new_obj,n,decorated_)
    return new_obj

思路2:修改装饰器,避免格式化时触发递归

如果需要保留对__repr__的装饰,可以修改装饰器的输出逻辑,避免直接格式化包含实例的args,比如调用对象原始的repr方法:

def custom_logger(fn):
  @wraps(fn)
  def inner(*args,**kwargs):
    result=fn(*args,**kwargs)
    # 处理args,用object的原始repr方法避免触发被装饰的__repr__
    safe_args = tuple(
        object.__repr__(arg) if isinstance(arg, Person) else arg 
        for arg in args
    )
    print(f'LOG: {fn.__qualname__} {safe_args},{kwargs}, result={result}')
    return result
  return inner

这种方式需要针对性处理实例,适合必须装饰特殊方法的场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 03:25:30