Подклассификация словаря Python для переопределения __setitem__
Я создаю класс, который подклассы dict
и переопределяет __setitem__
. Я хотел бы быть уверенным, что мой метод будет вызываться во всех случаях, где могут быть установлены словарные статьи.
Я обнаружил три ситуации, когда Python (в данном случае, 2.6.4) не вызывает мой переопределенный метод __setitem__
при настройке значений и вместо этого вызывает PyDict_SetItem
напрямую
- В конструкторе
- В методе
setdefault
- В методе
update
Как очень простой тест:
class MyDict(dict):
def __setitem__(self, key, value):
print "Here"
super(MyDict, self).__setitem__(key, str(value).upper())
>>> a = MyDict(abc=123)
>>> a['def'] = 234
Here
>>> a.update({'ghi': 345})
>>> a.setdefault('jkl', 456)
456
>>> print a
{'jkl': 456, 'abc': 123, 'ghi': 345, 'def': '234'}
Вы можете видеть, что переопределенный метод вызывается только при явной установке элементов. Чтобы Python всегда вызывал мой метод __setitem__
, мне пришлось переопределить эти три метода, например:
class MyUpdateDict(dict):
def __init__(self, *args, **kwargs):
self.update(*args, **kwargs)
def __setitem__(self, key, value):
print "Here"
super(MyUpdateDict, self).__setitem__(key, value)
def update(self, *args, **kwargs):
if args:
if len(args) > 1:
raise TypeError("update expected at most 1 arguments, got %d" % len(args))
other = dict(args[0])
for key in other:
self[key] = other[key]
for key in kwargs:
self[key] = kwargs[key]
def setdefault(self, key, value=None):
if key not in self:
self[key] = value
return self[key]
Существуют ли какие-либо другие методы, которые мне нужно переопределить, чтобы знать, что Python всегда будет называть мой метод __setitem__
?
UPDATE
Per gs suggestion, я пробовал подклассирование UserDict (на самом деле, IterableUserDict, так как я хочу перебирать ключи) следующим образом:
from UserDict import *;
class MyUserDict(IterableUserDict):
def __init__(self, *args, **kwargs):
UserDict.__init__(self,*args,**kwargs)
def __setitem__(self, key, value):
print "Here"
UserDict.__setitem__(self,key, value)
Этот класс, по-видимому, правильно вызывает мой __setitem__
на setdefault
, но он не вызывает его на update
или когда исходные данные предоставляются конструктору.
ОБНОВЛЕНИЕ 2
Предложение Peter Hansen заставило меня более внимательно посмотреть на dictobject.c, и я понял, что метод обновления можно немного упростить, поскольку встроенный конструктор слова просто вызывает встроенный метод обновления. Теперь он выглядит следующим образом:
def update(self, *args, **kwargs):
if len(args) > 1:
raise TypeError("update expected at most 1 arguments, got %d" % len(args))
other = dict(*args, **kwargs)
for key in other:
self[key] = other[key]
Ответы
Ответ 1
Я отвечаю на свой вопрос, так как в конце концов решил, что я действительно хочу подклассифицировать Dict, а не создавать новый класс сопоставления, а UserDict в некоторых случаях по-прежнему игнорирует основной объект Dict, вместо того, чтобы использовать предоставленный __setitem__
.
После прочтения и повторного чтения источника Python 2.6.4 (в основном Objects/dictobject.c
, но я нашел evey где-то еще, чтобы увидеть, где используются различные методы), я понимаю, что следующего кода достаточно, чтобы мой вызов __setitem__ каждый раз, когда объект изменяется, и в противном случае ведет себя точно как Dython Python:
Предложение Peter Hansen заставило меня более внимательно посмотреть на dictobject.c
, и я понял, что метод обновления в моем первоначальном ответе может быть немного упрощен, так как встроенный конструктор слова просто вызывает встроенный метод обновления в любом случае, Итак, второе обновление в моем ответе добавлено в код ниже (некоторым полезным человеком; -).
class MyUpdateDict(dict):
def __init__(self, *args, **kwargs):
self.update(*args, **kwargs)
def __setitem__(self, key, value):
# optional processing here
super(MyUpdateDict, self).__setitem__(key, value)
def update(self, *args, **kwargs):
if args:
if len(args) > 1:
raise TypeError("update expected at most 1 arguments, "
"got %d" % len(args))
other = dict(args[0])
for key in other:
self[key] = other[key]
for key in kwargs:
self[key] = kwargs[key]
def setdefault(self, key, value=None):
if key not in self:
self[key] = value
return self[key]
Я тестировал его с помощью этого кода:
def test_updates(dictish):
dictish['abc'] = 123
dictish.update({'def': 234})
dictish.update(red=1, blue=2)
dictish.update([('orange', 3), ('green',4)])
dictish.update({'hello': 'kitty'}, black='white')
dictish.update({'yellow': 5}, yellow=6)
dictish.setdefault('brown',7)
dictish.setdefault('pink')
try:
dictish.update({'gold': 8}, [('purple', 9)], silver=10)
except TypeError:
pass
else:
raise RunTimeException("Error did not occur as planned")
python_dict = dict([('b',2),('c',3)],a=1)
test_updates(python_dict)
my_dict = MyUpdateDict([('b',2),('c',3)],a=1)
test_updates(my_dict)
и он проходит. Все другие реализации, которые я пробовал, потерпели неудачу в какой-то момент. Я все равно соглашусь с любыми ответами, которые показывают мне, что я что-то пропустил, но в остальном я отмечаю галочку рядом с этим через пару дней и называя это правильным ответом:)
Ответ 2
Каков ваш прецедент для подкласса dict?
Вам не нужно делать это для реализации диктоподобного объекта, и в вашем случае может быть проще записать обычный класс, а затем добавить поддержку для необходимого подмножества интерфейса dict.
Лучший способ выполнить то, что вам нужно, - это, вероятно, базовый класс MutableMapping. PEP 3119 - Представление абстрактных базовых классов
Это также поможет вам ответить на вопрос "Существуют ли какие-либо другие методы, которые мне нужно переопределить?". Вам нужно будет переопределить все абстрактные методы. Для MutableMapping: абстрактные методы включают setitem, delitem. Конкретные методы включают pop, popitem, clear, update.
Ответ 3
Я нашел ответы и комментарии Яна очень полезными и понятными. Я просто хотел бы отметить, что, возможно, первый вызов метода суперкласса __init__
может быть более безопасным, если не нужно: мне недавно нужно было реализовать пользовательский OrderedDict (я работаю с Python 2.7): после реализации и модификации моего кода в соответствии с предлагаемой реализацией MyUpdateDict
я обнаружил, что просто заменив
class MyUpdateDict(dict):
с:
from collections import OrderedDict
class MyUpdateDict(OrderedDict):
то тестовый код, отправленный выше, не удалось:
Traceback (most recent call last):
File "Desktop/test_updates.py", line 52, in <module>
my_dict = MyUpdateDict([('b',2),('c',3)],a=1)
File "Desktop/test_updates.py", line 5, in __init__
self.update(*args, **kwargs)
File "Desktop/test_updates.py", line 18, in update
self[key] = other[key]
File "Desktop/test_updates.py", line 9, in __setitem__
super(MyUpdateDict, self).__setitem__(key, value)
File "/usr/lib/python2.7/collections.py", line 59, in __setitem__
root = self.__root
AttributeError: 'MyUpdateDict' object has no attribute '_OrderedDict__root'
Глядя на collections.py code, выясняется, что OrderedDict нуждается в методе __init__
, который будет вызываться для инициализации и настройки необходимых пользовательских атрибутов.
Поэтому, просто добавив первый вызов метода super __init__
,
from collections import OrderedDict
class MyUpdateDict(Orderedict):
def __init__(self, *args, **kwargs):
super(MyUpdateDict, self).__init__() #<-- HERE call to super __init__
self.update(*args, **kwargs)
у нас есть более общее решение, которое, по-видимому, работает как для dict, так и для OrderedDict.
Я не могу сказать, действительно ли это решение действительно, потому что я тестировал его только с помощью OrderedDict. Тем не менее, вполне вероятно, что вызов метода super __init__
является либо безвредным, либо необходимым, а не вредным, при попытке расширить другие подклассы dict
Ответ 4
Используйте object.keyname = значение вместо объекта [ "keyname" ] = значение