Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to subclass list and trigger an event whenever the data change?

Tags:

python

I would like to subclass list and trigger an event (data checking) every time any change happens to the data. Here is an example subclass:

class MyList(list):

   def __init__(self, sequence):
        super().__init__(sequence)
        self._test()

    def __setitem__(self, key, value):
        super().__setitem__(key, value)
        self._test()

    def append(self, value):
        super().append(value)
        self._test()

    def _test(self):
        """ Some kind of check on the data. """
        if not self == sorted(self):
            raise ValueError("List not sorted.")

Here, I am overriding methods __init__, __setitem__ and __append__ to perform the check if data changes. I think this approach is undesirable, so my question is: Is there a possibilty of triggering data checking automatically if any kind of mutation happens to the underlying data structure?

like image 762
NoBackingDown Avatar asked Aug 28 '16 09:08

NoBackingDown


1 Answers

As you say, this is not the best way to go about it. To correctly implement this, you'd need to know about every method that can change the list.

The way to go is to implement your own list (or rather a mutable sequence). The best way to do this is to use the abstract base classes from Python which you find in the collections.abc module. You have to implement only a minimum amount of methods and the module automatically implements the rest for you.

For your specific example, this would be something like this:

from collections.abc import MutableSequence

class MyList(MutableSequence):

    def __init__(self, iterable=()):
        self._list = list(iterable)

    def __getitem__(self, key):
        return self._list.__getitem__(key)

    def __setitem__(self, key, item):
        self._list.__setitem__(key, item)
        # trigger change handler

    def __delitem__(self, key):
        self._list.__delitem__(key)
        # trigger change handler

    def __len__(self):
        return self._list.__len__()

    def insert(self, index, item):
        self._list.insert(index, item)
        # trigger change handler

Performance

Some methods are slow in their default implementation. For example __contains__ is defined in the Sequence class as follows:

def __contains__(self, value):
    for v in self:
        if v is value or v == value:
            return True
    return False

Depending on your class, you might be able to implement this faster. However, performance is often less important than writing code which is easy to understand. It can also make writing a class harder, because you're then responsible for implementing the methods correctly.

like image 161
Georg Schölly Avatar answered Oct 19 '22 07:10

Georg Schölly