1

In case you want to change other variables in a class on change of an attribute you simply just write a property for it. That works fine, if you have a simple data type. But if your variable contains a complex type like a list (not so untypical), the content itself can be changed without calling the variable.setter again.

Are there any callbacks or events that can be used to track changes to a list attribute of a class? What else can be done to keep the code clean and not destroy the inner functionality of the class?

Example:

class Accumulator(object)
  def __init__(self,all_things):
     # :param all_things: a list of stuff of any kind
     self.__all_things = all_things
  @property
  def all_things(self):
     return self.__all_things
  @all_things.setter
  def all_things(self,input):
      self.__all_things = input

Thinking outside the box is probably the solution. The priority is not to keep the class structure alive, but to find a pattern that works and allows a clean API!

erikbstack
  • 12,878
  • 21
  • 81
  • 115

2 Answers2

2

You would have to use a custom subclass of list to detect changes:

class MyList(list):
    on_change_callback = None

    def _notify(self):
        if self.on_change_callback is not None:
            self.on_change_callback(self)

    def __setitem__(self, index, value):
        super(MyList, self).__setitem__(self, index, value)
        self._notify()

    # Etc, each mutating method needs to be overridden.

You'd need to override each mutating method, call the original method (through super()), then call self._notify(). For a list of methods, see the Emulating container types section.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • While you wrote that answer, I researched a little. Subclassing (or composition if you like it more) both looks like a [lot of work if done correctly](http://stackoverflow.com/questions/3945940/what-to-consider-before-subclassing-list). And after you are done and supposedly put no bugs in it, then you still have a much slower object then the standard list (which is probably optimized C code). – erikbstack Aug 13 '12 at 08:32
  • 1
    @erikb85: but that's the option you have; or indeed, disallow mutating the item altogether. – Martijn Pieters Aug 13 '12 at 08:40
  • Maybe you are right, for this case. I think there still should be a better solution in just not writing the class in the way I do. Otherwise many people would have that problem and there would be commonly known standard solutions for it, that aren't hard to implement. `There should be one-- and preferably only one --obvious way to do it. Although that way may not be obvious at first unless you're Dutch.` - Zen of Python – erikbstack Aug 13 '12 at 08:48
  • 1
    Totally off-topic, but for the record: I am Dutch. ;-) Also: the pythonic way is to *not worry* about these kind of mutations, unless there is a compelling way to do so. For example, the ZODB persistence module *does* care (so it can commit changes to the database), and provides you with the tools to track them (custom classes for both lists and mappings). – Martijn Pieters Aug 13 '12 at 08:53
  • 1
    And as for performance: The list mutations themselves are still handled in the original `list` type (thus in C). Only the *notification* is handled in Python, and the bottleneck will be in the callback itself, not in calling it. I think you are focusing on the wrong problem there. – Martijn Pieters Aug 13 '12 at 09:01
1

To allow discussion and increase creativity I want to offer the following solution myself:

class Accumulator(object):

    def __init__(self,all_things):
       #:param all_things: a sequence or generator of stuff of any kind
       self.__all_things = tuple(all_things)

    @property
    def all_things(self):
        return self.__all_things

    @property
    def all_things(self, all_things):
        self.__all_things = tuple(all_things)

It's clean, it's read-only and nothing can go wrong or be misused. Now the same assumption applies as in the question's structure: You need to reset it, if you want to change it. But you don't have to tell the user, because it's his only chance. If the user still wonders why, he can read the hopefully verbose class docstring.

erikbstack
  • 12,878
  • 21
  • 81
  • 115