Динамически обновлять метод установки свойств 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__()