【问题标题】:Dynamically update python property setter method动态更新python属性setter方法
【发布时间】:2014-05-26 20:26:22
【问题描述】:

我正在尝试在值上动态添加“可锁定”功能。虽然这个特殊情况看起来微不足道或相当做作,但我想为各种不同的用例扩展我的可锁定混合类。我不想一次性锁定价值;我希望它足够通用以控制任意数量的类属性。

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

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

【问题讨论】:

  • 有趣的练习 :) 让我试着理解... 那么你想锁定一个属性,特别是在具有任意数量属性的类中吗?
  • 一句话:是的。我正在设想一个具有一组属性的组件,我可以在其中锁定组件,包括它的所有属性(可能是列表、元组或集合)。我想让所有属性在某种程度上相互兼容。但有些可能是可锁定的。有些可能被禁用......等等。
  • 等等...如果你在 a2 的值为 2 时锁定它,为什么断言 a2.value == 2 会失败?你的意思是assert a2.value == 3 会失败吗?
  • 如果 a2.value 为 2,然后它被锁定,我不应该将它设置为 a2.value 3。相反,它应该简单地忽略该设置并将值保持在2 而不是更新为 3。
  • Ohhh... 所以 "this will fail" 意味着这实际上是在提高断言(因为 a2.value 是 3)好吧,这就是我的意思,无论如何.. . :-) 让我知道你对我的回答有何看法

标签: python dynamic properties mixins duck-typing


【解决方案1】:

假设您的示例代码中的最后一个断言是一个错字,并且您试图确保a2.value 不是3,因为它之前已被锁定在行上,那么将value 设为LockableAttribute 一个描述符?

我创建了一个使用LockableAttributes 的Foo 类,并且有一种方法可以锁定所有LockableAttributes,另一种方法可以将它们全部解锁。就像您在评论中所说的那样设想一个具有一组属性的组件,我可以在其中锁定组件

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

【讨论】:

  • 我喜欢这个答案,但它不够通用。我希望 mixin 修改所有属性的设置器。我可能应该不厌其烦地像这样布置它,因为我认为它会帮助我找到我正在寻找的答案。我将在我的问题中添加一个组件类。
【解决方案2】:

我相信这行得通:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-04-15
    • 1970-01-01
    • 2019-02-01
    • 2013-08-24
    • 2012-04-18
    • 1970-01-01
    • 2019-12-25
    • 2015-08-24
    相关资源
    最近更新 更多