组合__setattr__和__getattr__会导致无限循环

时间:2017-08-30 16:12:27

标签: python class infinite-loop getattr setattr

我正在尝试构建一个系统,其中基类用于每个其他对象。每个基础对象都在内部有一个_fields字典,基类的实现可以存储它们的信息。

基类实现非常简单:

class A(object):
    def __init__(self, fields=dict()):
        self._fields = fields

该类的实现可以将__init__调用中的字段设置为super()

我想补充的是,这些字段可以作为属性访问,而无需将@property装饰器添加到一大堆函数中。我已经在基类中为此目的覆盖__getattr__,如下所示:

class A(object):
    def __init__(self, fields=dict()):
        self._fields = fields

    def __getattr__(self, name):
        if hasattr(self, name):
            return object.__getattribute__(self, name)
        elif name in self._fields:
            return self._fields.get(name)
        else:
            raise AttributeError

现在这个类的实现可以这样工作:

class A_impl(A):
    def __init__(self):
        super(A_impl, self).__init__(
                    fields=dict(
                    val_1="foo",
                    val_2="",
                    val_3="bar",
                )
            )

创建此类的实现可以为您提供以下选项:

test = A_imp()
print test.val_1
print test.val_2
print test.val_3

返回

foo

bar

我甚至可以通过@property装饰器覆盖它,改变类如下:

class A_impl(A):
    def __init__(self):
        super(A_impl, self).__init__(
                    fields=dict(
                    val_1="foo",
                    val_2="",
                    val_3="bar",
                )
            )

    @property
    def val_1(self):
        return self._fields.get('val_1') + "_getter"

这允许我操纵数据以便返回。唯一的问题是,如果我想能够设置这些字段变量之一,我必须实现描述符setter函数,这也需要我创建属性描述符,这会创建大量重复的工作(即我必须为我的所有字段定义描述符,这是我想要避免的。

我为基类实现了__setattr__函数来解决如果我手动实现描述符setter函数的问题,应该选择默认值为self._field[name] = value的问题。基类现在看起来像这样(类似于__getattr__):

class A(object):
    def __init__(self, fields=dict()):
        self._fields = fields

    def __getattr__(self, name):
        if hasattr(self, name):
            return object.__getattribute__(self, name)
        elif name in self._fields:
            return self._fields.get(name)
        else:
            raise AttributeError

    def __setattr__(self, name, value):
        if hasattr(self, name):
            object.__setattr__(self, name, value)
        elif name in self._fields:
            self._fields[name] = value
        else:
            raise AttributeError

现在,如果我再次运行相同的代码测试:

test = A_imp()
print test.val_1
print test.val_2
print test.val_3

它会立即卡在无限循环中,它从__setattr__开始,但在之后跳转到__getattr__并继续循环。

我一直在阅读有关stackoverflow的很多问题并且无法解决这个问题,这就是为什么我构建这个测试用例来干净利落地解决它。希望有人能够为我澄清这一点并帮助我解决问题。

1 个答案:

答案 0 :(得分:3)

无法检查对象是否具有除实际尝试检索之外的属性。因此,

hasattr(self, 'val_1')

实际上是尝试访问self.val_1。如果通过正常方式找不到self.val_1,则会返回__getattr__,在无限递归中再次调用hasattr

hasattr实际上会捕获任何异常,包括RuntimeError: maximum recursion depth exceeded,并返回False,因此这种显示的确切方式取决于嵌套了多少__getattr__次调用。一些__getattr__次调用遇到object.__getattribute__个案,并提出AttributeError;一些人遇到elif name in self._fields: return self._fields.get(name)案件。如果self._fields存在,则会返回一个值,但使用__setattr__,有时self._fields不存在!

当您__setattr__尝试处理self._fields中的__init__作业时,会调用hasattr(self, '_fields'),其中会调用__getattr__。现在,一些__getattr__次呼叫会进行两次递归__getattr__次呼叫,一次在hasattr,一次在elif name in self._fields。由于hasattr正在捕获异常,因此这会导致递归调用在递归深度呈指数形式,而不是很快就会起作用或引发异常。

请勿在{{1​​}}或hasattr中使用__getattr__。通常,在处理属性访问的方法中访问属性的所有尝试都要非常小心。