动态更新python属性setter方法

时间:2014-04-12 17:16:48

标签: python dynamic properties mixins duck-typing

我正在尝试动态添加一个可锁定的'一个值的特征。虽然这个特殊情况看似微不足道或者说是设计的,但我想扩展我的可锁定混合类以用于各种不同的用例。我不想做一次性的可锁定价值;我希望它足够通用,可以控制任意数量的类属性。

我完成后的期望是最后一个断言将通过。

我曾尝试使用super而不是self。 setattr ,但我收到错误,该属性是只读的。这让我想知道我是否能做我想做的事。

任何帮助将不胜感激,并提前感谢!

一些代码:

from collections import OrderedDict as OD


def lockable(func, locked=None):
    def wrapper(*args, **kwds):
        if locked:
            val = None
        else:
            val = func(*args, **kwds)
        return val
    return wrapper


class Mixin(object):

    @property
    def meta(self):
        attr = "__meta__"
        if not hasattr(self, attr):
            setattr(self, attr, OD())
        return getattr(self, attr)


class LockableMixin(Mixin):

    @property
    def locked(self):
        self.meta.setdefault("locked", False)
        return self.meta.get("locked")

    @locked.setter
    def locked(self, value):
        value = value if value in [None, True, False] else self.meta['locked']
        self.meta['locked'] = value

    def lock(self):
        self.locked = True

    def unlock(self):
        self.locked = False

    def is_locked(self):
        return self.locked

    def __init__(self):
        super(LockableMixin, self).__init__()
        self.__setattr__ = lockable(self.__setattr__, self.locked)


class Attribute(object):

    @property
    def value(self):
        attr = "__value__"
        if not hasattr(self, attr):
            setattr(self, attr, False)
        return getattr(self, attr)

    @value.setter
    def value(self, value):
        self.__value__ = value

    def __init__(self, value):
        self.value = value
        super(Attribute, self).__init__()

    def __get__(self, instance, owner):
        return self.value

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

    def __str__(self):
        return str(self.value)

    def __repr__(self):
        cname = self.__class__.__name__
        value = str(self.value)
        return "<%s %s>" % (cname, value)


class LockableAttribute(Attribute, LockableMixin):
    pass

if __name__ == "__main__":
    a1 = Attribute(1)
    a2 = LockableAttribute(1)
    assert a2.locked is False
    assert a2.value == 1
    a2.lock()
    assert a2.locked is True
    a2.unlock()
    assert a2.locked is False
    a2.value = 2
    assert a2.value == 2
    a2.locked = True
    a2.value = 3
    assert a2.value == 2    # This will raise an exception, but it shouldn't.

以下是组件类的一个用例:

class Component(object):

    @property
    def attributes(self):
        attrs = {}
        for field in self.__fields__:
            attrs[field] = self.get(field)
        return attrs

    def __init__(self, **attributes):
        super(Component, self).__init__()
        self.__fields__ = []
        for name, val in attributes.iteritems():
            if name not in self.__fields__:
                self.__fields__.append(name)
                setattr(self, name, val)

    def __setattr__(self, name, value):
        if not name.startswith("__"):
            if not isinstance(value, Attribute):
                value = Attribute(value)
        super(Component, self).__setattr__(name, value)

    def __getitem__(self, name):
        return getattr(self, name, None)

    def get(self, name, default=None):
        return getattr(self, name, default)

# Case 1:  a lockable attribute
c = Component(name="Joe Schmoe", dob=LockableDateAttribute("04/12/2014"))

c.dob.lock()
c.dob.unlock()

# Case 2:  a lockable component class containing arbitrary number of lockable attributes
c2 = LockableComponent(name="Jill Pill", dob=LockableDateAttribute("04/12/2014))
c2.lock()   #  locks all of the lockable attributes

2 个答案:

答案 0 :(得分:0)

假设您的示例代码中的最后一个断言是拼写错误,并且您试图确保a2.value不是3因为它之前已被锁定在线,那么如何制作value描述符的LockableAttribute

我创建了一个使用Foo的{​​{1}}类,并且有一种方法可以锁定所有LockableAttribute,另一种方法可以解锁所有LockableAttribute。你在评论设想一个具有一组属性的组件时所说的内容,我可以锁定组件

class LockableValue(object):
    def __get__(self, instance, owner):
        return instance.__dict__['value']
    def __set__(self, instance, value):
        if not(instance.locked):
            instance.__dict__['value'] = value

class LockableAttribute(object):
    value = LockableValue()
    def __init__(self, value=None):
        self.locked = False
        self.value = value
    def lock(self):
        self.locked = True
    def unlock(self):
        self.locked = False

class Foo(object):
    def __init__(self):
        self.a = LockableAttribute()
        self.b = LockableAttribute()
    def lock_all(self):
        for k, v in vars(self).iteritems():
            if isinstance(v, LockableAttribute):
                v.lock()
    def unlock_all(self):
        for k, v in vars(self).iteritems():
            if isinstance(v, LockableAttribute):
                v.unlock()


if __name__ == "__main__":
    foo = Foo()
    foo.a.value = 1
    foo.b.value = "hello"
    assert foo.a.locked is False
    assert foo.a.value == 1
    assert foo.b.locked is False
    assert foo.b.value == "hello"
    foo.lock_all()
    assert foo.a.locked is True
    assert foo.b.locked is True
    foo.a.unlock()
    assert foo.a.locked is False
    assert foo.b.locked is True
    foo.a.value = 2
    assert foo.a.value == 2
    foo.a.value += 1
    assert foo.a.value == 3
    foo.a.locked = True
    foo.a.value = 4
    print "foo.a.value: %s" % foo.a.value
    assert foo.a.value == 4

这似乎做了你要求的......不是吗?我不知道,也许我误会了什么。如果是这样的话,请告诉我(我自己对描述符和元类很好奇)

输出:

foo.a.value: 3
Traceback (most recent call last):
  File "./stack31.py", line 56, in <module>
    assert foo.a.value == 4
AssertionError

答案 1 :(得分:0)

我相信这有效:

def lockable(func):
    def _lockable(self, *args, **kwds):
        locked = getattr(self, 'locked', None)
        val = None if locked else func(self, *args, **kwds)
        return val
    return _lockable


class LockableMixin(Mixin):

    @property
    def locked(self):
        value = None
        if hasattr(self, 'meta'):
            self.meta.setdefault("locked", False)
            value = self.meta.get("locked")
        return value

    @locked.setter
    def locked(self, value):
        locked = None
        if hasattr(self, 'locked'):
            if value in [None, True, False]:
                locked = value
            self.meta['locked'] = locked

    def lock(self):
        self.locked = True

    def unlock(self):
        self.locked = False

    def is_locked(self):
        return self.locked

    def __setattr__(self, name, value):
        func = super(LockableMixin, self).__setattr__
        locked = getattr(self, 'locked', None)
        if not locked or name == 'locked':
            func(name, value)

    def __init__(self):
        super(LockableMixin, self).__init__()