递归调用是指一个函数或方法在执行的过程中,直接或间接地调用了自身,从而形成了无限循环的调用关系,导致程序崩溃或陷入死循环。在 Python 中,由于方法和属性的调用方式不同,很容易出现递归调用的问题,特别是在使用 __setattr__ 和 __getattr__ 等特殊方法时。
RecursionError: maximum recursion depth exceeded
在相关开发的项目中,为了便于数据库和业务的交互,定义了一些Model来处理任务,为了简便完成Model的开发,采用了 __setattr__ 和 __getattr__ ,但却因此产生了递归调用的问题。以下是出错的代码:
class Person(object):
def __init__(self, *args, **kwargs):
self.document = {"genre": "XXX", "age": "XXX"}
def __getattr__(self, name):
if name in self.document:
return self.document[name]
else:
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
def __setattr__(self, name, value):
# 这里的self.document会触发__getattr__方法
# 从而在__getattr__里面的self.document又继续
# 触发__getattr__,从而产生递归调用问题
if name in self.document:
self.document[name] = value
else:
super().__setattr__(name, value)
person = Person()
person.age = 18
为了解决这个问题,我们可以在类中使用 self.__dict__.setdefault() 方法或者在 init 方法中使用 self._document = {} 来初始化实例属性。这样做的好处是,实例属性和类属性的作用域得到了清晰的界定,避免了递归调用的问题。此外,如果要修改实例属性,我们可以直接使用 setattr() 方法,而不是直接赋值,这样也可以避免递归调用的问题。
下面是一个示例代码,演示了如何使用 getattr 和 setattr 方法,避免递归调用的问题:
class Person(object):
def __init__(self, *args, **kwargs):
# 初始化实例下的__dict__
self.__dict__.setdefault('document', {})
self.document = {"genre": "XXX", "age": "XXX"}
def __getattr__(self, name):
if name in self.document:
return self.document[name]
else:
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
def __setattr__(self, name, value):
if name in self.document:
self.document[name] = value
else:
super().__setattr__(name, value)
在这个示例代码中,我们使用了 self.__dict__.setdefault() 方法来初始化实例属性。这样,就可以避免递归调用的问题,确保程序能够正确地运行。
另外在类属性这里初始化一个document = {}也能解决问题,但是需要注意类属性和实例属性的作用域,避免产生混淆,导致程序出错。
class Person(object):
document = {"genre": "XXX", "age": "XXX"}
def __init__(self, *args, **kwargs):
pass
def __getattr__(self, name):
if name in self.document:
return self.document[name]
else:
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
def __setattr__(self, name, value):
if name in self.document:
self.document[name] = value
else:
super().__setattr__(name, value)
Python的__getattr__和__setattr__是在开发中很好用的方法,能大大提高开发效率,但是递归调用的问题不能忽视,以上便是针对这个问题的解决方案。