Как список подкласса и инициировать событие всякий раз, когда данные меняются?

Я хотел бы подклассом list и запускать событие (проверку данных) каждый раз, когда происходят какие-либо изменения с данными. Ниже приведен пример подкласса:

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.")

Здесь я переопределяю методы __init__, __setitem__ и __append__, чтобы выполнить проверку, если данные будут изменены. Я думаю, что этот подход нежелателен, поэтому мой вопрос: есть ли возможность запускать проверку данных автоматически, если какая-либо мутация происходит с базовой структурой данных?

Ответ 1

Как вы говорите, это не лучший способ сделать это. Чтобы правильно реализовать это, вам нужно знать о каждом методе, который может изменить список.

Путь к реализации - реализовать свой собственный список (или, скорее, изменяемую последовательность). Лучший способ сделать это - использовать абстрактные базовые классы из Python, которые вы найдете в модуле collections.abc. Вы должны реализовать только минимальное количество методов, и модуль автоматически реализует остальное для вас.

В вашем конкретном примере это будет примерно так:

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

Производительность

Некоторые методы медленны в реализации по умолчанию. Например, __contains__ определяется в Sequence class следующим образом:

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

В зависимости от вашего класса вы сможете реализовать это быстрее. Однако производительность часто менее важна, чем написание кода, который легко понять. Это также может сделать запись класса сложнее, потому что тогда вы ответственны за правильность реализации методов.