如何识别何时设置属性的属性?

时间:2016-12-26 12:02:13

标签: python properties attributes

我有一个具有多个属性的可变对象(我们称之为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的属性取决于mutableMutable可以继承。

4 个答案:

答案 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:我们现在在FooMutableProxy之间有一个循环引用。 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}}