动态更新Python属性设置方法

1 投票
2 回答
1757 浏览
提问于 2025-04-18 02:30

我想给一个值动态添加一个“可锁定”的功能。虽然这个例子看起来很简单,甚至有点牵强,但我想把我的可锁定混合类扩展到不同的使用场景。我不想只做一个一次性的可锁定值,而是希望它足够通用,可以控制多个类的属性。

我希望在完成后,最后的检查能通过。

我尝试用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

我觉得这个可以用:

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__()
0

假设你代码里的最后一个断言是个笔误,你其实是想确保 a2.value 不是 3,因为它在之前的代码行中已经被锁定了。那么,为什么不把 LockableAttributevalue 变成一个描述符呢?

我创建了一个 Foo 类,这个类使用了 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

这似乎实现了你想要的功能……对吧?我不太确定,也许我理解错了。如果真是这样,请告诉我(我自己对描述符和 metaclass 也挺好奇的)

它输出:

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

撰写回答