2017-10-20 49 views
0

如何编写描述符,该描述符返回具有描述符并返回其他描述符对象的描述符?在下面的代码中,getattr中的__get____set__以不同实例作为参数被调用,但它引用了相同的对象。请告知如何通过附加测试。acces嵌套实例值的嵌套描述符

一般来说,这应该是生成严格模式的JSON报告的帮手。它会生成结构,但这些值在结构节点中很常见。只有当我在一个类中有几个相同类型的类属性(ObjectField)时才会暴露这个问题。

class Uninitialized: 
    pass 


class FieldDescriptor(object): 

    def __init__(self, value_type, json_key, initial_value=Uninitialized): 
     self._value_type = value_type 
     self._storage_key = json_key 
     self._initial_value = initial_value 
     self._parent_attr_name = None 

    def _check_py_value(self, new_value): 
     if new_value is not None and not isinstance(new_value, self._value_type): 
      raise TypeError("Bad type %s" % type(new_value).__name__) 

    def _form_json_value(self, parent_instance): 
     return self.__get__(parent_instance) 

    def __get__(self, parent_instance, _=None): 
     value = getattr(parent_instance, self._parent_attr_name).val 
     return None if value is Uninitialized else value 

    def __set__(self, parent_instance, value): 
     getattr(parent_instance, self._parent_attr_name).val = value 


class StrField(FieldDescriptor): 
    def __init__(self, json_key, initial_value=Uninitialized): 
     super(StrField, self).__init__(str, json_key, initial_value) 


class ListField(FieldDescriptor): 
    def __init__(self, json_key, initial_value=Uninitialized): 
     super(ListField, self).__init__(list, json_key, initial_value) 


class Wrap(object): 
    def __init__(self, val): 
     self.val = val 


class ObjectField(FieldDescriptor): 
    def __init__(self, json_key): 
     for name_, descriptor in self.iterate_descriptors(): 
      attr_name = "_value_of_{}".format(name_) # kind of proxy 
      descriptor._parent_attr_name = attr_name 
      new_field = Wrap(descriptor._initial_value) 
      setattr(self, attr_name, new_field) 

     FieldDescriptor.__init__(self, value_type=self.__class__, json_key=json_key, initial_value=self) 

    @classmethod 
    def iterate_descriptors(cls): 
     for attr_name, descriptor in cls.__dict__.iteritems(): 
      if isinstance(descriptor, FieldDescriptor): 
       yield attr_name, descriptor 

    def _form_json_value(self, _=None): 
     return {dsc._storage_key: dsc._form_json_value(self) for _, dsc in self.iterate_descriptors()} 


def test_it_with_pytest(): 

    class ObjF(ObjectField): 
     txt = StrField("OBJF.StrDO") 
     list = ListField("OBJF.C") 

    class Nest(ObjectField): 
     b1 = ObjF("NEST.B1") 
     b2 = ObjF("NEST.B2") 

    class Root(ObjectField): 
     oo1 = Nest('oo1') 
     oo2 = Nest('oo2') 

    root = Root(None) 
    # assign some values 
    root.oo1.b1.txt = "DIFFERENT" 
    root.oo2.b2.list = [12, 3, 5] 

    assert root.oo1._value_of_b1 != root.oo2._value_of_b1 # that pass 

    a = root.oo1.b1.txt 
    b = root.oo1.b2.txt 
    c = root.oo2.b1.txt 
    assert a != b # that pass 
    assert a != c # that fails, 'DIFFERENT' == 'DIFFERENT' 

    assert root._form_json_value() == { 
     'oo1': { 
      'NEST.B1': { 
       'OBJF.C': None, 
       'OBJF.StrDO': 'DIFFERENT' # ok 
      }, 
      'NEST.B2': { 
       'OBJF.C': None, # that fails, is [12, 3, 5] 
       'OBJF.StrDO': None 
      } 
     }, 
     'oo2': { 
      'NEST.B1': { 
       'OBJF.C': None, 
       'OBJF.StrDO': None # that fails is "DIFFERENT" 
      }, 
      'NEST.B2': { 
       'OBJF.C': [12, 3, 5], # ok 
       'OBJF.StrDO': None 
      } 
     } 
    } 
+0

指定'_parent_attr_name'是真正应该在元类“__init__”中发生的事情。 –

+0

是的,它之前是这样的,但由于效果是相同的,没有使用元类(我相信),没关系。 – Mikaelblomkvistsson

+0

它确实有效,并且对于演示文稿来说更简单,但重复的分配会让我在真实代码中感到困惑。 –

回答

1

的问题是在这里:

# for ... 
     new_field = Wrap(descriptor._initial_value) 
     setattr(self, attr_name, new_field) 

FieldDescriptor.__init__(self, value_type=self.__class__, json_key=json_key, initial_value=self) 

一起,这使得attr_name所属的类商店的所有实例一个描述符对象。因此root.oo1.b1 is root.oo2.b1在你的测试中。 (同样,root.oo1 is Outer(None).oo1。)

您需要为每个ObjectField属性构造新对象;对于这些对象本身而言(即使它们是具有嵌套属性的更多描述符的类型),它们可能不那么令人困惑,因为它们本身不是描述符。当然,如果你要预先构建它们,你可能会让所有的外部对象成为具有属性和没有描述符的普通对象,而让描述符检查叶子上的数据类型。

或者,您可以通过构建并安装新值(适合类型)对__get__中的Uninitialized值作出反应。

+0

谢谢戴维斯。你的根本原因绝对正确。虽然我多次阅读你的建议,但到现在为止我还没有得到任何解决方案。正如你所说的:'ObjectField'不再是一个描述符,它是从'object'派生的。在迭代'sub-ObjectFields'(它是'class attributes')的'__init__'中,我重新创建了完全相同的类,但是实例属性(与类'属于同一个属性名称)。 '_form_json_value'不得不改变,但它的工作。非常感谢你。 – Mikaelblomkvistsson