Una clase de Python que actúa como dic

113

Quiero escribir una clase personalizada que se comporte como dict, entonces, estoy heredando dict.

Mi pregunta, sin embargo, es: ¿Necesito crear un dictmiembro privado en mi __init__()método? No veo el sentido de esto, ya que ya tengo el dictcomportamiento si simplemente heredo de dict.

¿Alguien puede señalar por qué la mayoría de los fragmentos de herencia se parecen al siguiente?

class CustomDictOne(dict):
   def __init__(self):
      self._mydict = {} 

   # other methods follow

En lugar de lo más simple ...

class CustomDictTwo(dict):
   def __init__(self):
      # initialize my other stuff here ...

   # other methods follow

De hecho, creo que sospecho que la respuesta a la pregunta es que los usuarios no pueden acceder directamente a su diccionario (es decir, tienen que utilizar los métodos de acceso que ha proporcionado).

Sin embargo, ¿qué pasa con el operador de acceso a la matriz []? ¿Cómo se implementaría eso? Hasta ahora, no he visto un ejemplo que muestre cómo anular el []operador.

Entonces si un [] no se proporciona función de acceso en la clase personalizada, ¿los métodos base heredados operarán en un diccionario diferente?

Probé el siguiente fragmento para probar mi comprensión de la herencia de Python:

class myDict(dict):
    def __init__(self):
        self._dict = {}

    def add(self, id, val):
        self._dict[id] = val


md = myDict()
md.add('id', 123)
print md[id]

Tuve el siguiente error:

KeyError: <identificación de función incorporada>

¿Qué pasa con el código anterior?

¿Cómo corrijo la clase myDictpara poder escribir código como este?

md = myDict()
md['id'] = 123

[Editar]

He editado el ejemplo de código anterior para deshacerme del tonto error que cometí antes de salir corriendo de mi escritorio. Fue un error tipográfico (debería haberlo visto en el mensaje de error).

skyeagle
fuente

Respuestas:

104
class Mapping(dict):

    def __setitem__(self, key, item):
        self.__dict__[key] = item

    def __getitem__(self, key):
        return self.__dict__[key]

    def __repr__(self):
        return repr(self.__dict__)

    def __len__(self):
        return len(self.__dict__)

    def __delitem__(self, key):
        del self.__dict__[key]

    def clear(self):
        return self.__dict__.clear()

    def copy(self):
        return self.__dict__.copy()

    def has_key(self, k):
        return k in self.__dict__

    def update(self, *args, **kwargs):
        return self.__dict__.update(*args, **kwargs)

    def keys(self):
        return self.__dict__.keys()

    def values(self):
        return self.__dict__.values()

    def items(self):
        return self.__dict__.items()

    def pop(self, *args):
        return self.__dict__.pop(*args)

    def __cmp__(self, dict_):
        return self.__cmp__(self.__dict__, dict_)

    def __contains__(self, item):
        return item in self.__dict__

    def __iter__(self):
        return iter(self.__dict__)

    def __unicode__(self):
        return unicode(repr(self.__dict__))


o = Mapping()
o.foo = "bar"
o['lumberjack'] = 'foo'
o.update({'a': 'b'}, c=44)
print 'lumberjack' in o
print o

In [187]: run mapping.py
True
{'a': 'b', 'lumberjack': 'foo', 'foo': 'bar', 'c': 44}
Ricky Wilson
fuente
37
Si va a una subclase dict, entonces debe usar el objeto en sí (uso super) en lugar de simplemente delegar a la instancia, lo __dict__que esencialmente significa que está creando dos dictados para cada instancia.
Aaron Hall
8
self .__ dict__ no es lo mismo que el contenido real del diccionario. Cada objeto de Python, independientemente de su tipo, tiene un _dict__que contiene todos los atributos del objeto (métodos, campos, etc.). Tu no quiere perder el tiempo con esto a menos que usted quiere escribir código que se modifica a sí mismo ...
Raik
85

Me gusta esto

class CustomDictOne(dict):
   def __init__(self,*arg,**kw):
      super(CustomDictOne, self).__init__(*arg, **kw)

Ahora puede usar las funciones integradas, como dict.get()as self.get().

No es necesario envolver un archivo oculto self._dict. Tu clase ya es un dict.

S.Lott
fuente
3
Esta. No tiene sentido heredar de dictsin llamar primero a su constructor.
sykora
1
Tenga en cuenta que su heredado en dictrealidad contiene 2 dict-instance: el primero es el contenedor heredado y el segundo es el dict que contiene los atributos de clase; puede evitarlo utilizando ranuras .
ankostis
1
En __dict__realidad, solo se crea cuando se accede por primera vez, por lo que siempre que los usuarios no intenten usarlo, está bien. __slots__aunque estaría bien.
Aaron Hall
9
¡Usa espacios después de las comas, salvaje! ;-)
James Burke
10

En aras de la integridad, aquí está el enlace a la documentación mencionada por @ björn-pollex para la última versión de Python 2.x (2.7.7 en el momento de escribir este artículo):

Emulación de tipos de contenedores

(Lo siento por no usar la función de comentarios, pero stackoverflow no me permite hacerlo).

he1ix
fuente
3

El problema con este fragmento de código:

class myDict(dict):
    def __init__(self):
        self._dict = {}

    def add(id, val):
        self._dict[id] = val


md = myDict()
md.add('id', 123)

... es que su método 'agregar' (... y cualquier método que desee que sea miembro de una clase) necesita tener un 'self' explícito declarado como su primer argumento, como:

def add(self, 'id', 23):

Para implementar la sobrecarga del operador para acceder a elementos por clave, busque en los documentos los métodos mágicos __getitem__y __setitem__.

Tenga en cuenta que debido a que Python usa Duck Typing, es posible que en realidad no haya razón para derivar su clase dict personalizada de la clase dict del lenguaje, sin saber más sobre lo que está tratando de hacer (por ejemplo, si necesita pasar una instancia de esto class en algún código en algún lugar que se rompa a menos que isinstance(MyDict(), dict) == True), es mejor que simplemente implemente la API que hace que su clase sea lo suficientemente similar a un dictado y se detenga allí.

bgporter
fuente
3

Aquí hay una solución alternativa:

class AttrDict(dict):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.__dict__ = self

a = AttrDict()
a.a = 1
a.b = 2
张小诚
fuente
Esto es malo porque no define ningún método personalizado y también hay otros problemas, como ha dicho otra respuesta.
Shital Shah
esto es exactamente lo que necesitaba. ¡Gracias!
jakebrinkmann
2

Realmente no veo la respuesta correcta a esto en ningún lado

class MyClass(dict):
    
    def __init__(self, a_property):
        self[a_property] = a_property

Todo lo que realmente tienes que hacer es definir el tuyo __init__, eso es todo lo que hay.

Otro ejemplo (un poco más complejo):

class MyClass(dict):

    def __init__(self, planet):
        self[planet] = planet
        info = self.do_something_that_returns_a_dict()
        if info:
            for k, v in info.items():
                self[k] = v

    def do_something_that_returns_a_dict(self):
        return {"mercury": "venus", "mars": "jupiter"}

Este último ejemplo es útil cuando desea incrustar algún tipo de lógica.

De todos modos ... en resumen class GiveYourClassAName(dict)es suficiente para hacer que tu clase actúe como un dictado. Cualquier operación de dictado que realice selfserá como un dictado normal.

Danny Meijer
fuente
1

Ésta es mi mejor solución. Usé esto muchas veces.

class DictLikeClass:
    ...
    def __getitem__(self, key):
        return getattr(self, key)

    def __setitem__(self, key, value):
        setattr(self, key, value)
    ...

Puedes usar como:

>>> d = DictLikeClass()
>>> d["key"] = "value"
>>> print(d["key"])
madogan
fuente
0

¡Nunca heredes del dict incorporado de Python! por ejemplo, el updatemétodo no se usa __setitem__, hacen mucho para la optimización. Utilice UserDict.

from collections import UserDict

class MyDict(UserDict):
    def __delitem__(self, key):
        pass
    def __setitem__(self, key, value):
        pass
usuario2674414
fuente
1
¿Basado en lo que alguien nunca debería heredar del dictado incorporado? De la documentación ( docs.python.org/3/library/collections.html#collections.UserDict ): "La necesidad de esta clase ha sido parcialmente suplantada por la capacidad de crear subclases directamente desde dict; sin embargo, esta clase puede ser más fácil de trabajar con porque el diccionario subyacente es accesible como un atributo. " También en la misma página: El módulo de colecciones está "En desuso desde la versión 3.3, se eliminará en la versión 3.9: Clases base abstractas de colecciones movidas al módulo collections.abc" ... que no tiene UserDict.
NumesSanguis
Sospecho que esto está inspirado, o al menos resuena con, treyhunner.com/2019/04/…
tripleee