我有一个具有多个属性的可变对象(我们称之为mutable
)。该对象本身是类Foo
中的属性。 Foo
是用户定义的,而mutable
则不是,因此无法更改。
每当有人试图在mutable
中设置属性时,我都需要进行计算。我面临的问题是,使用mutable
的属性仅在mutable
本身设置时才有效,而不是其属性。我已经设法解决了这个问题,但有些东西看起来比合理的Python代码更糟糕。
class Mutable(object): # Example class.
def __init__(self):
self.attr0 = 0
self.attr1 = 1
def __repr__(self):
return str(self.__dict__)[1:-1]
class Foo(object):
def __init__(self):
self._mutable = Mutable()
@property
def mutable(self):
print('mutable was read')
return self._mutable
@mutable.setter
def mutable(self, attr_value_pair):
attribute, value = attr_value_pair
setattr(self._mutable, attribute, value)
print('mutable.' + attribute, 'was set to', value)
bar = Foo()
print(bar.mutable) # 'mutable was read'
bar.mutable = ('attr0', 5) # 'mutable.attr0 was set to 5'
bar.mutable = ('attr1', 10) # 'mutable.attr1 was set to 10'
print(bar.mutable) # 'mutable was read'
# This is what I want to do but it only calls the getter.
bar.mutable.attr0 = 0 # 'mutable was read'
bar.mutable.attr1 = 1 # 'mutable was read'
有没有办法可以识别mutable
中的属性何时以更加Pythonic的方式设置?
编辑:澄清:Foo
需要知道mutable
何时发生变化,因为Foo
的属性取决于mutable
。 Mutable
可以继承。
答案 0 :(得分:3)
最简单的干净解决方案是子类mutable
- 但我认为这不是一个选项。
最简单的“快速和肮脏”解决方案是monkeypatch mutable
- 但这确实是最后的解决方案,并且从长远来看寻找麻烦。
因此,如果您不能将mutable
子类化,也不能控制它的实例化并且不想对它进行monkeypatch,那么您将保留proxy
模式。
编辑:哦,是的,因为它是Foo
,需要通知self._mutable
的更改,你必须将它与观察者模式(这里以非常有限的形式)结合起来:
class Mutable(object): # Example class.
def __init__(self):
self.attr0 = 0
self.attr1 = 1
def __repr__(self):
return str(self.__dict__)[1:-1]
class MutableProxy(object):
def __init__(self, mutable, owner):
self._mutable = mutable
self._owner = owner
@property
def attr0(self):
return self._mutable.attr0
@attr0.setter
def attr0(self, value):
self._mutable.attr0 = value
self._owner.notify("set", "attr0", value)
@property
def attr1(self):
return self._mutable.attr1
@attr1.setter
def attr1(self, value):
self._mutable.attr1 = value
self._owner.notify("attr1", value)
def __repr__(self):
return "<MutableProxy for {}>".format(repr(self._mutable))
class Foo(object):
def __init__(self):
self.mutable = Mutable()
@property
def mutable(self):
#print('mutable was read')
return self._mutable
@mutable.setter
def mutable(self, value):
self._mutable = MutableProxy(value, self)
def notify(self, attrname, value):
print('self._mutable.{} was set to {}'.format(attrname, value))
注意:我没有在MutableProxy.__init__
中添加任何类型检查,具体取决于您的实际mutable
实际上是什么,您可能希望确保至少获得兼容的内容......
NB2:我在ProxyMutable
上使用了显式属性,因为它使事情变得更清晰,但您可能希望使用__getattr__
/ __setattr__
个钩子(至少对mutable
属性你不需要控制对它的访问。
NB3:我们现在在Foo
和MutableProxy
之间有一个循环引用。 Python normalluy知道如何摆脱循环引用,但如果它仍然是您的具体用例的问题,您可能希望MutableProxy._owner
改为weak reference。
现在让我烦恼的问题是:为什么要公开mutable
?完全隐藏它并且仅通过Foo
方法或属性提供对它的属性的访问将使得更简单的代码(并且更容易推理并且不太可能具有意外的副作用)。
答案 1 :(得分:2)
<强> 修 强>
基本上我同意@ Alessandro的derived-class approach,即你应该扩展Mutable
类 - 但是有一些显着的差异。一个是,鉴于它是从基类派生的,因此不需要包含基类的单独(未使用)实例。我在早期版本的答案中错过了这个。
更重要的是,它支持用户提供的回调函数,只要读取或写入其中一个属性,就会调用这些函数。这允许将通知发送回包含类中的方法 - Foo
在这种情况下 - 我相信它们的更改确实需要处理。
注意:这不必然意味着您可以移除Foo
属性mutable
。如果您希望支持已实施的元组分配操作,则仍然需要它,以允许诸如bar.mutable = ('attr0', 5)
之类的语句设置mutable
&#39; s {{1} }属性。如果没有该属性,您需要编写:attr0
代替(无论如何可能更清楚)。
bar.mutable.attr0 = 5
答案 2 :(得分:1)
这是另一种有效地将__setattr__()
方法替换为现有类的实例的方法。我将@Martijn Pieters的this answer代码导出到一个标题为Decorating a class to monitor attribute changes的相关问题。
以下代码支持在id
通知功能上传递的实例notify()
。具有这不是必需的,并且仅存在以验证仅在设置/修改关联的实例对象时才调用该函数。如果您不需要它,可以将其删除。
from types import FunctionType, MethodType
class Mutable(object): # Example class (unchangeable).
def __init__(self):
self.attr0 = 0
self.attr1 = 1
def __repr__(self):
return str(self.__dict__)[1:-1]
def monitor_attr_changes(obj, id, notify):
""" Change class of obj to one that supports attribute notifications. """
old_setattr = getattr(obj, '__setattr__')
old_classname = obj.__class__.__name__
class NewClass(obj.__class__):
def __setattr__(self, name, value):
old_setattr(name, value)
notify(id, name, value)
def __repr__(self): # Not required -- here only for demo purposes.
data_attrs = {name: value for name, value in self.__dict__.items()
if not isinstance(value, (FunctionType, MethodType))}
return old_classname + ': ' + str(data_attrs)[1:-1]
obj.__class__ = NewClass
return obj
class Foo(object):
def __init__(self, id):
print('creating instance {!r} of Mutable class'.format(id))
self.mutable = monitor_attr_changes(Mutable(), id, self._callback)
def _callback(self, id, name, value):
print('{} notification: {} has been set to {}'.format(id, name, value))
foo = Foo('foo')
bar = Foo('bar')
print(foo.mutable) # -> Mutable: 'attr0': 0, 'attr1': 1
foo.mutable.attr0 = 5 # -> foo notification: attr0 has been set to 5
bar.mutable.attr0 = 42 # -> bar notification: attr0 has been set to 42
foo.mutable.attr1 = 10 # -> foo notification: attr1 has been set to 10
print(foo.mutable) # -> Mutable: 'attr0': 5, 'attr1': 10
foo.mutable.attr0 = 1 # -> foo notification: attr0 has been set to 1
foo.mutable.attr1 = 0 # -> foo notification: attr1 has been set to 0
print(foo.mutable) # -> Mutable: 'attr0': 1, 'attr1': 0
print(foo.mutable.attr0) # -> 1
print(bar.mutable.attr0) # -> 42 x
答案 3 :(得分:0)
Foo类无法检测Mutable何时发生变化。
您需要扩展Mutable类并检测其中的更改或将其包装为从包装器读取/写入。
{{1}}