2017-10-20 16 views
0

記述子を持つオブジェクトを返す記述子を持つオブジェクトを返す記述子はどのように記述しますか?以下のコードではgetattr__get____set__がパラメータとして異なるインスタンスで呼び出されますが、同じオブジェクトを参照します。添付のテストに合格する方法を教えてください。入れ子になったインスタンス値の入れ子になったネストされた記述子

一般に、これは厳密なスキーマのJSONレポートを生成するためのヘルパーになるはずです。構造体を生成しますが、値は構造体ノード全体で共通です。この問題は、1つのクラスに同じ型の複数のクラス属性(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の下で所有しているクラスの店のすべてインスタンス1つの記述子オブジェクトを作成します。したがって、あなたのテストでroot.oo1.b1 is root.oo2.b1。 (同様にroot.oo1 is Outer(None).oo1

ObjectField属性ごとに新しいオブジェクトを作成する必要があります。これらのオブジェクトが(たとえそれらがネストされた属性のためのより多くの記述子を持つ型のものであっても)記述子自体ではないことはあまり混乱しないかもしれません。もちろん、それらを事前構築する場合は、アトリビュートとディスクリプタのないすべての外部オブジェクトを通常のオブジェクトにして、ディスクリプタで葉のデータ型をチェックするようにすることもできます。

また、Uninitializedの値を__get__に反応させるには、新しい値(適切な場合)を作成してインストールします。

+0

デイビスありがとうございます。あなたは根本的な原因について絶対に正しかったです。私はあなたの助言を何度か読んだが、私は今まで何の解決策も得られなかった。あなたが言ったように、 'ObjectField'はもはやディスクリプタではなく、' object'から派生しています。 'sub-ObjectFields'(' class attributes')を反復しながら、 '__init__'において、私はまったく同じクラスを再作成しますが、(class 'と同じ属性名で)インスタンス属性として再作成します。 '_form_json_value'は変更する必要がありましたが、動作します。どうもありがとうございました。 – Mikaelblomkvistsson

関連する問題