0

您如何编写返回具有描述符的对象的描述符,该描述符返回具有描述符的其他对象?在下面的代码中,getattrin__get____set__以不同的实例作为参数调用,但它引用相同的对象。请告知如何使它通过所附的测试。

一般来说,这应该是生成严格模式的 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
            }
        }
    }
4

1 回答 1

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属性构造新对象;这些对象本身不是描述符(即使它们是具有更多嵌套属性描述符的类型)可能不会让人感到困惑。当然,如果您要预先构造它们,您不妨让所有外部对象成为具有属性且没有描述符的普通对象,让描述符在叶子处检查数据类型。

或者,您可以通过构造和安装新值(在适合类型时)来对Uninitialized值做出反应。__get__

于 2017-10-20T17:22:42.757 回答