Як правильно підкласувати 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

2
Не dict.__init__приймає *args?
Том Рассел

4
Схоже на хорошого кандидата в декоратори.
Том Рассел,

Відповіді:


39

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

По-друге, __getitem__і __setitem__викликаються лише під час []доступу. Тому переконайтеся , що доступ тільки DictWatchчерез d[key], а не d.get()таd.set()


Насправді це не зайві дужки, а відсутність навколо них(str(dict.get(self, 'name_label')), str(key), str(val)))
cobbal

3
Правда. До ОП: Для подальшого довідкового запиту ви можете просто зробити log.info ('% s% s% s', a, b, c), замість оператора форматування рядків Python.
BrainCore

Проблемою виявився рівень реєстрації. Я налагоджую чужий код, і спочатку тестував в іншому файлі, який відповідає іншому рівню набору налагодження. Дякую!
Michael Mior

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

Я спробував ваш соль, але, схоже, він працює лише для одного рівня індексації (тобто dict [key], а не dict [key1] [key2] ...) *
Ендрю Нагіб,

d [key1] повертає щось, можливо, словник. Другий ключ індексує це. Ця техніка не може працювати, якщо повернута річ також не підтримує поведінку годинника.
Метт Андерсон,

1
@AndrewNaguib: Чому він повинен працювати з вкладеними масивами? Вкладений масив також не працює із звичайним python dict (якщо ви його не реалізували самостійно)
Ігор Чубін,

1
@AndrewNaguib: __getitem__потрібно було б протестувати valі робити це лише умовно - тобтоif isinstance(val, dict): ...
martineau

14

Розглянемо підкласифікацію UserDictабо UserList. Ці класи призначені для підкласів , тоді як нормальні dictі listне є, і містять оптимізації.


9
Для довідки в документації Python 3.6 сказано: "Потреба в цьому класі частково витіснена можливістю підкласу безпосередньо з dict; однак з цим класом може бути простіше працювати, оскільки базовий словник доступний як атрибут".
Шон

Приклад @andrew може бути корисним.
Vasantha Ganesh K


9

Це насправді не повинно змінити результат (що має спрацювати, для хороших порогових значень реєстрації): ваш init повинен бути:

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

натомість, оскільки якщо ви викликаєте свій метод за допомогою DictWatch ([(1,2), (2,3)]) або DictWatch (a = 1, b = 2), це не вдасться.

(або, краще, не визначайте для цього конструктор)


Мене турбує лише dict[key]форма доступу, тому це не проблема.
Michael Mior

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


0

Щоб заповнити відповідь на паштет andrew, ось приклад, що показує різницю між 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)буде працювати)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.