2017-10-20 48 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