Subclasificando el diccionario de Python para anular __setitem__

Estoy creando una clase que las subclases dict , y reemplaza a __setitem__ . Me gustaría estar seguro de que mi método se activará en todos los casos en los que posiblemente se puedan configurar los elementos del diccionario.

Descubrí tres situaciones en las que Python (en este caso, 2.6.4) no llama a mi método __setitem__ invalidado al configurar valores, y en su lugar llama a PyDict_SetItem directamente

  1. En el constructor
  2. En el método setdefault
  3. En el método de update

Como una prueba muy simple:

 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'} 

Puede ver que solo se llama al método anulado cuando se configuran los elementos explícitamente. Para que Python siempre llame a mi método __setitem__ , he tenido que volver a __setitem__ esos tres métodos, como este:

 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] 

¿Hay algún otro método que deba anular para saber que Python siempre llamará a mi método __setitem__ ?

    ACTUALIZAR

    Según la sugerencia de gs, he intentado subclasificar UserDict (en realidad, IterableUserDict, ya que quiero iterar sobre las claves) de esta manera:

     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) 

    Esta clase parece llamar correctamente a mi __setitem__ en setdefault , pero no la llama en la update , o cuando se proporcionan datos iniciales al constructor.

    ACTUALIZACIÓN 2

    La sugerencia de Peter Hansen me llevó a examinar con mayor detenimiento dictobject.c, y me di cuenta de que el método de actualización podría simplificarse un poco, ya que el constructor del diccionario incorporado simplemente llama al método de actualización incorporado de todos modos. Ahora se ve así:

     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] 

    Estoy respondiendo a mi propia pregunta, ya que finalmente decidí que realmente quiero subclasificar Dict, en lugar de crear una nueva clase de mapeo, y UserDict aún difiere al objeto Dict subyacente en algunos casos, en lugar de usar el __setitem__ proporcionado.

    Después de leer y releer la fuente de Python 2.6.4 (en su mayoría, Objects/dictobject.c , pero busqué en todos los demás sitios para ver dónde se utilizan los diversos métodos), entiendo que el siguiente código es suficiente para que mi __setitem__ se llame a todos la hora en que se cambia el objeto, y para comportarse de otra manera exactamente como un dictado de Python:

    La sugerencia de Peter Hansen me llevó a analizar con más dictobject.c , y me di cuenta de que el método de actualización en mi respuesta original podría simplificarse un poco, ya que el constructor del diccionario incorporado simplemente llama al método de actualización integrado de todos modos. Así que la segunda actualización de mi respuesta se agregó al código a continuación (por alguna persona útil ;-).

     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] 

    Lo he probado con este código:

     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) 

    y pasa. Todas las otras implementaciones que he probado han fallado en algún momento. Seguiré aceptando cualquier respuesta que me muestre que me he perdido algo, pero de lo contrario, en un par de días estoy marcando la marca de verificación junto a esta, y la llamo la respuesta correcta 🙂

    ¿Cuál es su caso de uso para subclasificar dict?

    No necesita hacer esto para implementar un objeto similar a un dict, y en su caso podría ser más simple escribir una clase ordinaria, luego agregar soporte para el subconjunto requerido de la interfaz de dict.

    La mejor manera de lograr lo que está buscando es probablemente la clase base abstracta de MutableMapping. PEP 3119 – Introducción de clases básicas abstractas

    Esto también le ayudará a responder la pregunta “¿Hay algún otro método que deba anular?”. Tendrá que anular todos los métodos abstractos. Para MutableMapping: los métodos abstractos incluyen setitem , delitem . Los métodos concretos incluyen pop, popitem, clear, update.

    Encontré la respuesta y los comentarios de Ian muy útiles y claros. Simplemente señalaría que tal vez una primera llamada al método de __init__ podría ser más segura, cuando no sea necesaria: recientemente necesité implementar un OrderedDict personalizado (estoy trabajando con Python 2.7): después de implementar y modificar mi código de acuerdo a la implementación propuesta de MyUpdateDict , descubrí que simplemente reemplazando

     class MyUpdateDict(dict): 

    con:

     from collections import OrderedDict class MyUpdateDict(OrderedDict): 

    entonces el código de prueba publicado anteriormente falló:

     Traceback (most recent call last): File "Desktop/test_updates.py", line 52, in  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' 

    Al observar el código collections.py , resulta que OrderedDict necesita que se llame su método __init__ para inicializar y configurar los atributos personalizados necesarios.

    Por lo tanto, simplemente agregando una primera llamada al método 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) 

    Tenemos una solución más general que aparentemente funciona tanto para dict como para OrderedDict.

    No puedo decir si esta solución es generalmente válida, porque la probé solo con OrderedDict. Sin embargo, es probable que una llamada al método super __init__ sea ​​inofensiva o necesaria en lugar de perjudicial, cuando se intenta extender otras subclases dict.

    Use object.keyname = value en lugar de object [“keyname”] = value