Как правильно создать подкласс dict и переопределить __getitem__ & __setitem__

Я отлаживаю некоторый код и хочу узнать, когда к конкретному словарю обращаются. Ну, это на самом деле класс, который подкласс dict и реализует пару дополнительных функций. Во всяком случае, что я хотел бы сделать, это подкласс dict сам и добавить переопределение __getitem__ а также __setitem__ произвести некоторый отладочный вывод. Прямо сейчас у меня есть

class DictWatch(dict):
    def __init__(self, *args):
        dict.__init__(self, args)

    def __getitem__(self, key):
        val = dict.__getitem__(self, key)
        log.info("GET %s['%s'] = %s" % str(dict.get(self, 'name_label')), str(key), str(val)))
        return val

    def __setitem__(self, key, val):
        log.info("SET %s['%s'] = %s" % str(dict.get(self, 'name_label')), str(key), str(val)))
        dict.__setitem__(self, key, val)

'name_label' является ключом, который в конечном итоге будет установлен, который я хочу использовать для определения вывода. Затем я изменил класс, который я использую для подкласса DictWatch вместо dict и изменил вызов суперконструктору. Тем не менее, кажется, ничего не происходит. Я думал, что я умен, но мне интересно, должен ли я идти в другом направлении.

Спасибо за помощь!

5 ответов

Решение

То, что вы делаете, должно работать. Я проверил ваш класс, и, кроме пропущенных открывающих скобок в ваших логах, он работает просто отлично. Есть только две вещи, о которых я могу думать. Во-первых, правильно ли настроен вывод вашего оператора журнала? Возможно, вам придется поставить logging.basicConfig(level=logging.DEBUG) в верхней части вашего сценария.

Во-вторых, __getitem__ а также __setitem__ вызываются только во время [] доступ. Поэтому убедитесь, что у вас есть доступ только DictWatch с помощью d[key], скорее, чем d.get() а также d.set()

Еще одна проблема при создании подклассов dict является то, что встроенный __init__ не звонит updateи встроенный update не звонит __setitem__, Итак, если вы хотите, чтобы все операции setitem проходили через ваш __setitem__ Функция, вы должны убедиться, что она вызывается самостоятельно:

class DictWatch(dict):
    def __init__(self, *args, **kwargs):
        self.update(*args, **kwargs)

    def __getitem__(self, key):
        val = dict.__getitem__(self, key)
        print 'GET', key
        return val

    def __setitem__(self, key, val):
        print 'SET', key, val
        dict.__setitem__(self, key, val)

    def __repr__(self):
        dictrepr = dict.__repr__(self)
        return '%s(%s)' % (type(self).__name__, dictrepr)

    def update(self, *args, **kwargs):
        print 'update', args, kwargs
        for k, v in dict(*args, **kwargs).iteritems():
            self[k] = v

Рассмотрим подклассы UserDict или же UserList, Эти классы предназначены для деления на подклассы, тогда как нормальные dict а также list нет, и содержат оптимизацию.

Это не должно действительно изменить результат (который должен работать, для хороших пороговых значений регистрации): ваш init должен быть:

def __init__(self,*args,**kwargs) : dict.__init__(self,*args,**kwargs) 

вместо этого, потому что если вы вызываете свой метод с помощью DictWatch([(1,2),(2,3)]) или DictWatch(a=1,b=2), это не будет выполнено.

(или, лучше, не определяйте конструктор для этого)

Чтобы завершить ответ Эндрю Пэйта, вот пример, показывающий разницу между dict и UserDict:

Правильно перезаписать dict сложно:

class MyDict(dict):

  def __setitem__(self, key, value):
    super().__setitem__(key, value * 10)


d = MyDict(a=1, b=2)  # Bad! MyDict.__setitem__ not called
d.update(c=3)  # Bad! MyDict.__setitem__ not called
d['d'] = 4  # Good!
print(d)  # {'a': 1, 'b': 2, 'c': 3, 'd': 40}

UserDict унаследовать от collections.abc.MutableMapping, поэтому настроить гораздо проще:

class MyDict(collections.UserDict):

  def __setitem__(self, key, value):
    super().__setitem__(key, value * 10)


d = MyDict(a=1, b=2)  # Good: MyDict.__setitem__ correctly called
d.update(c=3)  # Good: MyDict.__setitem__ correctly called
d['d'] = 4  # Good
print(d)  # {'a': 10, 'b': 20, 'c': 30, 'd': 40}

Точно так же вам нужно только реализовать __getitem__ автоматически быть совместимым с key in my_dict, my_dict.get,...

Заметка: UserDict не является подклассом dict, так isinstance(UserDict(), dict) потерпит неудачу (но isinstance(UserDict(), collections.abc.MutableMapping) заработает)

Все, что вам нужно сделать, это

class BatchCollection(dict):
    def __init__(self, inpt={}):
        super(BatchCollection, self).__init__(inpt)

Пример использования для моего личного использования

### EXAMPLE
class BatchCollection(dict):
    def __init__(self, inpt={}):
        super(BatchCollection, self).__init__(inpt)

    def __setitem__(self, key, item):
        if (isinstance(key, tuple) and len(key) == 2
                and isinstance(item, collections.Iterable)):
            # self.__dict__[key] = item
            super(BatchCollection, self).__setitem__(key, item)
        else:
            raise Exception(
                "Valid key should be a tuple (database_name, table_name) "
                "and value should be iterable")

Примечание: проверено только в python3

Другие вопросы по тегам