当设置为实例时,描述符不像描述符那样 - 为什么不呢?

时间:2015-02-13 16:57:51

标签: python get set descriptor new-style-class

我有一个枚举的描述符,我希望能够动态地添加到类中。这是

class TypedEnum:
    '''Class to make setting of enum types valid and error checked'''
    def __init__(self, enum, default=None):
        self._enum = enum
        self._enum_by_value = {e.value: e for e in enum}
        self._enum_by_name = {e.name: e for e in enum}
        self._value = next(iter(enum))
        if default is not None:
            self.value = default

    @property
    def value(self):
        return self._value.value

    @value.setter
    def value(self, value):
        if value in self._enum:
            value = getattr(self._enum, value.name)
        elif value in self._enum_by_name:
            value = self._enum_by_name[value]
        elif value in self._enum_by_value:
            value = self._enum_by_value[value]
        else:
            raise ValueError("Value does not exist in enum: {}".format(value))
        self._value = value

    @property
    def name(self):
        return self._value.name

    def __str__(self):
        return repr(self._value.name)

    def __get__(self, obj, type=None):
        return self._value.name

    def __set__(self, obj, value):
        self.value = value

为了尝试理解这种行为,我编写了一个单元测试来覆盖它

from enum import Enum
from unittest import TestCase
class abc(Enum):
    a = 0
    b = 1
    c = 2

def test_descriptor():
    '''test mostly the features of __get__ and __set__
    Basically when they are just objects they are fair game,
    but when they become a member of a class they are super
    protected (I can't even figure out a way to get access to
    the base object)'''
    venum = TypedEnum(abc)
    assert isinstance(venum, TypedEnum)
    # set works normally when not a member of an object
    venum = 0
    assert isinstance(venum, int)

    venum = TypedEnum(abc)
    x = venum
    assert isinstance(x, TypedEnum)

    # But when it is a member of a class/object, it is hidden
    class C:
        e = venum

    c = C()
    assert c.e == 'a' and isinstance(c.e, str)
    c.e = 'b'
    assert c.e == 'b' and isinstance(c.e, str)
    TestCase.assertRaises(None, ValueError, setattr, c, 'e', 'z')

    # However, when it is added on in init it doesn't behave the same
    # way
    class C:
        def __init__(self):
            self.e = venum

    c = C()
    # This would not have been true before
    assert c.e != 'a' and isinstance(c.e, TypedEnum)

    # to implement this, it seems we need to overload the getattribute
    class D(C):
        def __getattribute__(self, attr):
            obj = object.__getattribute__(self, attr)
            if hasattr(obj, '__get__'):
                print('getting __get__', obj, attr)
                return obj.__get__(self)
            else:
                return obj

        def __setattr__(self, attr, value):
            if not hasattr(self, attr):
                object.__setattr__(self, attr, value)
                return
            obj = object.__getattribute__(self, attr)
            if hasattr(obj, '__set__'):
                print('setting __set__', obj, attr, value)
                obj.__set__(self, value)
            else:
                setattr(self, attr, value)

    c = D()
    c.e = 'b'
    assert c.e == 'b' and isinstance(c.e, str)
    TestCase.assertRaises(None, ValueError, setattr, c, 'e', 'z')
test_descriptor()

这一切都是理智的吗?无论它是__dict__还是class.__dict__

的成员,为什么它会以不同的方式工作?

请告诉我有更好的方法来做到这一点!

0 个答案:

没有答案