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