如何解决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__则能正常运行,这是为什么?该怎么解决?
原因分析
递归的核心逻辑链:
__repr__被元类自动装饰,调用p.__repr__()时会先进入装饰器的inner函数- 装饰器里执行
print(f'LOG: ... {args} ...'),而args里包含Person实例p - 格式化字符串时,对实例
p会自动调用repr(p),也就是触发被装饰后的__repr__方法 - 这又会进入新一轮的装饰器逻辑,循环往复直到超出递归深度
而__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
相关产品推荐
相关产品推荐

