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

84

Я отлаживаю некоторый код и хочу узнать, когда осуществляется доступ к определенному словарю. Ну, на самом деле это класс, который является подклассом 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и изменил вызов суперконструктора. Тем не менее, похоже, что ничего не происходит. Я думал, что поступаю умно, но мне интересно, стоит ли мне идти в другом направлении.

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

Майкл Майор
источник
Вы пробовали использовать печать вместо журнала? Кроме того, не могли бы вы объяснить, как вы создаете / настраиваете журнал?
pajton 06
2
Не dict.__init__берет *args?
Том Рассел
4
Немного похоже на хороший кандидат в декоратора.
Том Рассел

Ответы:

39

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

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

BrainCore
источник
На самом деле это не лишние паренсы, а недостающие начальные (str(dict.get(self, 'name_label')), str(key), str(val)))
символы
3
Правда. В OP: для справки в будущем вы можете просто выполнить log.info ('% s% s% s', a, b, c) вместо оператора форматирования строки Python.
BrainCore 06
Уровень ведения журнала оказался проблемой. Я отлаживаю чужой код и изначально тестировал в другом файле, который возглавляет другой уровень отладки. Благодаря!
Майкл Миор 06
73

Другая проблема при 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
Мэтт Андерсон
источник
9
Если вы используете Python 3, вы хотите изменить этот пример так , что printэто print()функция и update()метод использует items()вместо iteritems().
Al Sweigart
Я пробовал ваш sol, но кажется, что он работает только для одного уровня индексации (т.е. dict [key], а не dict [key1] [key2] ...) *
Эндрю Нагиб
d [key1] что-то возвращает, например словарь. Второй ключ указывает на это. Этот метод не может работать, если возвращенная вещь также не поддерживает поведение часов.
Мэтт Андерсон
1
@AndrewNaguib: Почему он должен работать с вложенными массивами? Вложенный массив также не работает с обычным Python dict (если вы не реализовали его самостоятельно)
Игорь Чубин
1
@AndrewNaguib: __getitem__нужно будет протестировать valи сделать это только при определенных условиях - то естьif isinstance(val, dict): ...
Мартино
14

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

Эндрю Пэйт
источник
9
Для справки, в документации Python 3.6 говорится: «Необходимость в этом классе частично вытеснена возможностью создавать подклассы непосредственно из dict; однако с этим классом может быть проще работать, поскольку базовый словарь доступен как атрибут».
Шон
@andrew пример может быть полезным.
Васанта Ганеш
2
@VasanthaGaneshK treyhunner.com/2019/04/…
SirDorius
9

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

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

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

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

Макапуф
источник
Меня беспокоит только dict[key]форма доступа, так что это не проблема.
Майкл Миор, 06
1

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

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

ravi404
источник
0

Чтобы завершить ответ Эндрю Пэйта, вот пример, показывающий разницу между 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)будет работать)

Конхиликулятор
источник