How to determine when an attribute attribute is set?

I have a mutable object (which we will call mutable) that has several attributes. This object itself is an attribute in the class Foo. Foouser-defined and mutablenot is and therefore cannot be modified.

Whenever someone tries to set attributes in mutable, I will need to do the calculations. The problem I am facing is that using the property for mutableonly works when it is set mutable, not its attributes. I managed to solve the problem, but with something that seems more like a hack than reasonable Python code.

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 ?

: : Foo , mutable, Foo , mutable. mutable .

+4
4

, @Alessandro , , Mutable, . , , , , () . .

, , , . include- Foo , , .

: , Foo Mutable. - , , , : bar.mutable = ('attr0', 5) Mutable attr0. : bar.mutable.attr0 = 5 ( ).

class Mutable(object):  # Example class (unchangeable).
    def __init__(self):
        self.attr0 = 0
        self.attr1 = 1

    def __repr__(self):
        return str(self.__dict__)[1:-1]

class MonitoredMutable(Mutable):
    _get_callback = _set_callback = lambda *_: None  # no-op placeholders

    def __init__(self, get_callback, set_callback):
        # use superclass to avoid infinite recursion when setting attributes
        super_delegate = super(MonitoredMutable, self)
        super_delegate.__init__()
        super_delegate.__setattr__('_get_callback', get_callback)
        super_delegate.__setattr__('_set_callback', set_callback)

    def __setattr__(self, name, value):
        super(MonitoredMutable, self).__setattr__(name, value)
        self._set_callback(name, value)  # write notification

    def __getattr__(self, name):
        self._get_callback(name)  # read notification
        return super(MonitoredMutable, self).__getattr__(name, value)

    def __repr__(self):  # optional
        # override to only display the public attributes of the instance
        public_attrs = {k:v for k,v in self.__dict__.items()
                            if not k.startswith('_')}
        # assuming single inheritance (only one base class)
        base_classname = self.__class__.__bases__[0].__name__
        return base_classname + ': ' + (str(public_attrs)[1:-1] if public_attrs
                                        else 'No pub attributes')

class Foo(object):
    def __init__(self):
        self._mutable = MonitoredMutable(self._get_callback, self._set_callback)

    def _get_callback(self, name):
        print('mutable.' + name + ' was read')

    def _set_callback(self, name, value):
        print('mutable.' + name, 'was set to', value)

    @property
    def mutable(self):
        return self._mutable

    @mutable.setter
    def mutable(self, attr_value_pair):
        attribute, value = attr_value_pair
        setattr(self._mutable, attribute, value)

bar = Foo()
print(bar.mutable)           # -> Mutable: 'attr0': 0, 'attr1': 1
bar.mutable = ('attr0', 5)   # -> mutable.attr0 was set to 5
bar.mutable = ('attr1', 10)  # -> mutable.attr1 was set to 10
print(bar.mutable)           # -> Mutable: 'attr0': 5, 'attr1': 10

# These now work
bar.mutable.attr0 = 1        # -> mutable.attr0 was set to 1
bar.mutable.attr1 = 0        # -> mutable.attr1 was set to 0
print(bar.mutable)           # -> Mutable: 'attr0': 1, 'attr1': 0
+2

mutable - , .

" " monkeypatch mutable - .

, mutable instanciation , , proxy.

edit: oh yes, 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))

NB: MutableProxy.__init__, , mutable , , - ...

NB2: ProxyMutable, , __getattr__/__setattr__ ( , mutable ).

NB3: Foo MutableProxy. Python normalluy , , , MutableProxy._owner a .

, : mutable? , Foo ( , ).

+3

, __setattr__() . @Martijn Pieters .

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
+1

Foo , Mutable.

Mutable, , / .

class MutableWrapper(Mutable):

    def __init__(self):
        self._mutable = Mutable()

    def __setattr__(self, key, value):
        print('mutable.' + key, 'was set to', value)
        super().__setattr__(key, value)

    def __getattr__(self, item):
        print('mutable was read')
        super().__getattribute__(item)

    def __repr__(self):
        return str(self._mutable.__dict__)[1:-1]


class Foo(object):

    def __init__(self):
        self._mutable = MutableWrapper()

    @property
    def mutable(self):
        return self._mutable

    @mutable.setter
    def mutable(self, attr_value_pair):
        attribute, value = attr_value_pair
        setattr(self._mutable, attribute, value)
0

Source: https://habr.com/ru/post/1664905/


All Articles