2017-08-30 53 views
1

我想构建一个系统,其中每个其他对象都使用一个基类。每个基础对象在内部都有一个_fields字典,其中基类的实现可以存储其信息。结合__setattr__和__getattr__导致无限循环

基类的实现很简单:

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

类的实现可以设置在__init__调用其super()领域。

我想补充说的是,这些字段可以作为属性访问,而不必将@property修饰器添加到一大堆函数中。我已经重写了__getattr__用于此目的的基类,像这样:

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

通过它创建该类的实现提供:

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 

现在对于这个类实现可以这样工作你选择做:

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" 

,让我来操纵数据的回报。唯一的问题是,如果我想能够设置这些字段变量之一,我必须实现描述符设置函数,这也需要我做属性描述符,它创造了很多重复的工作(即我必须定义我的所有字段描述这是我想避免在这里。

我实现了__setattr__功能的基类来解决地方如果我手动实现它应该在默认这是self._field[name] = value选择一个描述符setter函数问题基类现在看起来像这样(类似于__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的问题,无法弄清楚,这就是为什么我建立这个测试用例,以清楚地弄清楚它。希望有人能够为我澄清这一点,并帮助我解决问题。

+0

'hasattr'是不必要的。 – o11c

回答

3

无法检查某个对象是否具有实际尝试检索它的属性。因此,

hasattr(self, 'val_1') 

实际上试图访问self.val_1。如果self.val_1没有通过正常方式找到,它会回退__getattr__,它再次以无限递归方式调用hasattr

hasattr实际上捕捉到任何异常,包括RuntimeError: maximum recursion depth exceeded,并且返回False,因此这种体现的确切方式取决于调用嵌套的调用次数。某些__getattr__调用遇到了object.__getattribute__的情况并引发了AttributeError;一些打到elif name in self._fields: return self._fields.get(name)的情况。如果self._fields存在,则返回一个值,但与您的__setattr__,有时self._fields不存在!


当你__setattr__试图处理在__init__self._fields分配,它调用hasattr(self, '_fields'),这就要求__getattr__。现在一些__getattr__调用进行了两次递归__getattr__调用,一个调用hasattr,另一个调用elif name in self._fields。由于hasattr正在捕捉异常,因此这会导致递归调用在递归深度中呈指数形式,而不是快速地看似工作或引发异常。


不要在__getattr____getattribute__使用hasattr。一般来说,要非常小心所有尝试访问处理属性访问的方法内的属性。

+0

那么我会怎么做呢?我希望优先于自我实现的描述符,而不是始终提供self._field条目。 – Yonathan

+0

@Yonathan:只有在“正常”查找(通过__getattribute__')找不到值时才会调用__getattr__',所以您实际上不必再次检查。至于如何对通常的属性访问“非常小心”,通常需要委派给'super().__ getattribute__'或'super().__ setattr__'来进行任何需要绕过特殊逻辑的属性检索或分配。 – user2357112

+0

但是在set中使用类似'if name in self._fields'的方法,并将其设置为'super().__(set | set)attr __(self,name [,value])'并获取基类的attr函数导致相同的递归问题。我看不出我该如何解决“标准行为第一,否则检查self._fields”问题。现在优先考虑的是'__(get | set)attr__'函数,而不是实现的描述符。对不起,我只是想我怎么能解决这个问题。 – Yonathan