Динамически обновлять метод установки свойств python

Я пытаюсь динамически добавить "блокируемую" функцию к значению. В то время как этот конкретный случай кажется тривиальным или скорее надуманным, я хочу расширить свой блокируемый смешанный класс для множества различных вариантов использования. Я не хочу делать одноразовое блокируемое значение; Я хочу, чтобы это было достаточно универсальным, чтобы контролировать любое количество атрибутов класса.

После того, как я закончу, я ожидаю, что последнее утверждение пройдет.

Я пытался использовать супер вместо себя.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 ответа

Предполагая, что последнее утверждение в вашем примере кода является опечаткой, и вы пытались убедиться, что a2.value не было 3 потому что он был заблокирован на линии раньше, как насчет того, чтобы сделать value из LockableAttribute дескриптор?

Я создал 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

Это похоже на то, что вы просили... Нет? Я не знаю, может быть, я что-то не так понял. Если это так, дайте мне знать (мне довольно любопытно о дескрипторах и метаклассах)

Это выводит:

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

Я считаю, что это работает:

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__()
Другие вопросы по тегам