¿Cómo puedo hacer una subclase de dict tan "perfecta" como sea posible? El objetivo final es tener un dict simple en el que las teclas están en minúsculas.
Parece que debería haber un pequeño conjunto de primitivas que puedo anular para que esto funcione, pero de acuerdo con todas mis investigaciones e intentos, parece que este no es el caso:
Si anulo
__getitem__
/__setitem__
, entoncesget
/set
no funciona. ¿Cómo puedo hacer que funcionen? ¿Seguramente no necesito implementarlos individualmente?¿Estoy evitando que funcione el decapado y necesito implementar,
__setstate__
etc.?Cómo me necesito
repr
,update
y__init__
?¿Debería usar mutablemapping (parece que uno no debería usar
UserDict
oDictMixin
)? ¿Si es así, cómo? Los documentos no son exactamente esclarecedores.
Aquí está mi primer intento, get()
no funciona y, sin duda, hay muchos otros problemas menores:
class arbitrary_dict(dict):
"""A dictionary that applies an arbitrary key-altering function
before accessing the keys."""
def __keytransform__(self, key):
return key
# Overridden methods. List from
# /programming/2390827/how-to-properly-subclass-dict
def __init__(self, *args, **kwargs):
self.update(*args, **kwargs)
# Note: I'm using dict directly, since super(dict, self) doesn't work.
# I'm not sure why, perhaps dict is not a new-style class.
def __getitem__(self, key):
return dict.__getitem__(self, self.__keytransform__(key))
def __setitem__(self, key, value):
return dict.__setitem__(self, self.__keytransform__(key), value)
def __delitem__(self, key):
return dict.__delitem__(self, self.__keytransform__(key))
def __contains__(self, key):
return dict.__contains__(self, self.__keytransform__(key))
class lcdict(arbitrary_dict):
def __keytransform__(self, key):
return str(key).lower()
fuente
Respuestas:
Puede escribir un objeto que se comporte con
dict
bastante facilidad con ABC s (clases base abstractas) desde elcollections.abc
módulo. Incluso te dice si te perdiste un método, así que a continuación se muestra la versión mínima que cierra el ABC.Obtiene algunos métodos gratuitos del ABC:
No subclase
dict
(u otras construcciones) directamente. A menudo no tiene sentido, porque lo que realmente quieres hacer es implementar la interfaz de adict
. Y para eso es exactamente el ABC.fuente
__keytransform__()
porque viola la guía de estilo PEP 8 que aconseja "Nunca invente tales nombres; solo utilícelos como se documenta" al final de la sección Descriptivo: Nomenclatura de estilos .if isinstance(t, collections.MutableMapping): print t, "can be used like a dict"
. No verifique el tipo de objeto, verifique la interfaz.La respuesta aceptada sería mi primer enfoque, pero dado que tiene algunos problemas, y dado que nadie ha abordado la alternativa, en realidad subclasificando a
dict
, voy a hacer eso aquí.¿Qué hay de malo con la respuesta aceptada?
Esto me parece una solicitud bastante simple:
La respuesta aceptada en realidad no es una subclase
dict
, y una prueba para esto falla:Idealmente, cualquier código de verificación de tipo estaría probando la interfaz que esperamos, o una clase base abstracta, pero si nuestros objetos de datos se pasan a funciones que están probando
dict
, y no podemos "arreglar" esas funciones, este código fallará.Otras objeciones que uno podría hacer:
fromkeys
.La respuesta aceptada también tiene redundancia
__dict__
, por lo tanto, ocupa más espacio en la memoria:Realmente subclases
dict
Podemos reutilizar los métodos dict a través de la herencia. Todo lo que necesitamos hacer es crear una capa de interfaz que garantice que las claves se pasen al dict en minúsculas si son cadenas.
Bueno, implementarlos individualmente es la desventaja de este enfoque y la ventaja de usarlo
MutableMapping
(ver la respuesta aceptada), pero en realidad no es mucho más trabajo.Primero, factoricemos la diferencia entre Python 2 y 3, cree un singleton (
_RaiseKeyError
) para asegurarnos de saber si realmente tenemos un argumentodict.pop
y creemos una función para garantizar que nuestras teclas de cadena estén en minúsculas:Ahora implementamos: estoy usando
super
los argumentos completos para que este código funcione para Python 2 y 3:Utilizamos un enfoque casi caldera de la placa por cualquier método o método especial que hace referencia a una clave, pero por lo demás, por herencia, obtenemos métodos:
len
,clear
,items
,keys
,popitem
, yvalues
de forma gratuita. Si bien esto requirió un pensamiento cuidadoso para hacerlo bien, es trivial ver que esto funciona.(Tenga en cuenta que
haskey
fue obsoleto en Python 2, eliminado en Python 3.)Aquí hay algunos usos:
decapado
Y la dict subclase encurtidos bien:
__repr__
Definimos
update
y__init__
, pero tienes una hermosa__repr__
por defecto:Sin embargo, es bueno escribir un
__repr__
para mejorar la depuración de su código. La prueba ideal eseval(repr(obj)) == obj
. Si es fácil de hacer para su código, lo recomiendo encarecidamente:Verá, es exactamente lo que necesitamos para recrear un objeto equivalente; esto es algo que podría aparecer en nuestros registros o en las trazas inversas:
Conclusión
Sí, estas son algunas líneas más de código, pero están destinadas a ser exhaustivas. Mi primera inclinación sería usar la respuesta aceptada, y si hubiera problemas con ella, entonces miraría mi respuesta, ya que es un poco más complicado y no hay un ABC que me ayude a tener mi interfaz correcta.
La optimización prematura busca una mayor complejidad en la búsqueda de rendimiento.
MutableMapping
es más simple, por lo que obtiene una ventaja inmediata, todo lo demás es igual. Sin embargo, para exponer todas las diferencias, comparemos y contrastemos.Debo agregar que hubo un impulso para poner un diccionario similar en el
collections
módulo, pero fue rechazado . Probablemente deberías hacer esto en su lugar:Debería ser mucho más fácilmente debugable.
Comparar y contrastar
Hay 6 funciones de interfaz implementadas con
MutableMapping
(que faltafromkeys
) y 11 con ladict
subclase. No necesitará implementar__iter__
o__len__
, pero en lugar de eso tiene que aplicarget
,setdefault
,pop
,update
,copy
,__contains__
, yfromkeys
- pero estos son bastante trivial, ya que puedo utilizar la herencia para la mayoría de las implementaciones.Los
MutableMapping
implementos algunas cosas en Python quedict
implementa en C - por lo que se puede esperar de unadict
subclase sea más performante en algunos casos.Obtenemos una libertad
__eq__
en ambos enfoques, los cuales asumen la igualdad solo si otro dict es todo en minúsculas, pero nuevamente, creo que ladict
subclase se comparará más rápidamente.Resumen:
MutableMapping
es más simple, con menos oportunidades para errores, pero más lenta, requiere más memoria (ver dict redundante) y fallaisinstance(x, dict)
dict
es más rápida, usa menos memoria y pasaisinstance(x, dict)
, pero tiene una mayor complejidad para implementar.¿Cuál es más perfecto? Eso depende de tu definición de perfecto.
fuente
__slots__
o quizás reutilizarlo__dict__
como la tienda, pero eso mezcla la semántica, otro punto potencial de crítica.ensure_lower
primer argumento (que siempre es la clave)? Entonces sería el mismo número de anulaciones, pero todas tendrían la forma__getitem__ = ensure_lower_decorator(super(LowerDict, self).__getitem__)
.copy
- Creo que debería hacerlo, ¿no? Creo que debería probar la interfaz, por ejemplo, el objeto Pandas DataFrame no es una instancia de Mapping (en la última comprobación) pero tiene elementos / iteritems.Mis requisitos eran un poco más estrictos:
Mi pensamiento inicial fue sustituir nuestra clase de Path torpe por una subclase Unicode insensible a mayúsculas y minúsculas, pero:
some_dict[CIstr(path)]
es feo)Así que finalmente tuve que escribir ese dict insensible a mayúsculas y minúsculas. Gracias al código de @AaronHall que se hizo 10 veces más fácil.
Lo implícito frente a lo explícito sigue siendo un problema, pero una vez que el polvo se asienta, el cambio de nombre de los atributos / variables para comenzar con ci (y un gran comentario de documentación que explica que ci significa mayúsculas y minúsculas) Creo que es una solución perfecta, ya que los lectores del código deben Tenga en cuenta que estamos tratando con estructuras de datos subyacentes que no distinguen entre mayúsculas y minúsculas. Con suerte, esto solucionará algunos errores difíciles de reproducir, que sospecho se reducen a mayúsculas y minúsculas.
Comentarios / correcciones bienvenidos :)
fuente
__repr__
deberían usar la clase padre__repr__
para pasar la prueba eval (repr (obj)) == obj (no creo que lo haga ahora) y no confiar en ella__str__
.total_ordering
decorador de clases , que eliminará 4 métodos de tu subclase Unicode. Pero la subclase dict parece implementada de manera muy inteligente. : PCIstr.__repr__
, en su caso, puede pasar la prueba de repr con muy poca molestia, y debería hacer que la depuración sea mucho más agradable. También agregaría un__repr__
para su dict. Lo haré en mi respuesta para demostrar.__slots__
en CIstr: hace una diferencia en el rendimiento (CIstr no está destinado a ser subclasificado o, de hecho, debe usarse fuera de LowerDict, debería ser una clase final anidada estática). Todavía no estoy seguro de cómo resolver con elegancia el problema de repr (la picadura puede contener una combinación de'
y"
citas)Todo lo que tendrás que hacer es
O
Una muestra de uso para mi uso personal
Nota : probado solo en python3
fuente
Después de probar los dos principales dos sugerencias, me he decidido por una ruta central de aspecto sombrío para Python 2.7. Quizás 3 es más sano, pero para mí:
que realmente odio, pero parece satisfacer mis necesidades, que son:
**my_dict
dict
, esto omite su código . Pruébalo.isinstance(my_dict, dict)
dict
Si necesita diferenciarse de los demás, personalmente uso algo como esto (aunque recomendaría mejores nombres):
Mientras solo necesite reconocerse internamente, de esta manera es más difícil llamar accidentalmente
__am_i_me
debido al cambio de nombre de Python (esto se renombra_MyDict__am_i_me
de cualquier cosa que llame fuera de esta clase). Ligeramente más privado que_method
s, tanto en la práctica como culturalmente.Hasta el momento no tengo quejas, aparte de la anulación seriamente sombría
__class__
. sería encantada de oír de cualquier problema que otros encuentran con esto, sin embargo, no entiendo plenamente las consecuencias. Pero hasta ahora no he tenido ningún problema, y esto me permitió migrar una gran cantidad de código de calidad media en muchas ubicaciones sin necesidad de ningún cambio.Como evidencia: https://repl.it/repls/TraumaticToughCockatoo
Básicamente: copie la opción actual # 2 , agregue
print 'method_name'
líneas a cada método, y luego intente esto y observe el resultado:Verá un comportamiento similar para otros escenarios. Di tu falso
dict
es una envoltura alrededor de algún otro tipo de datos, por lo que no hay una forma razonable de almacenar los datos en el dictado de respaldo;**your_dict
estará vacío, independientemente de lo que haga cualquier otro método.Esto funciona correctamente para
MutableMapping
, pero tan pronto como herede dedict
él se vuelve incontrolable.Editar: como una actualización, esto se ha estado ejecutando sin un solo problema durante casi dos años, en varios cientos de miles (eh, podrían ser un par de millones) de líneas de python complicado y heredado. Así que estoy bastante feliz con eso :)
Edición 2: aparentemente copié mal esto o algo hace mucho tiempo.
@classmethod __class__
no funciona paraisinstance
cheques -@property __class__
sí: https://repl.it/repls/UnitedScientificSequencefuente
**your_dict
estará vacío" (si subclase dedict
)? No he visto ningún problema con el desempaquetado de dict ...**your_dict
no ejecuta su código, por lo que no puede mostrar nada "especial". Por ejemplo, no puede contar "lecturas" porque no ejecuta su código de recuento de lectura. MutableMapping hace el trabajo para esto (use si usted puede!), Pero noisinstance(..., dict)
así que no podía usarlo. yay software heredado.**your_dict
, pero me parece muy interesante queMutableMapping
lo haga.**some_dict
Es bastante común. Como mínimo, ocurre con mucha frecuencia en los decoradores, por lo que si tiene alguno , corre el riesgo inmediato de un comportamiento aparentemente imposible si no lo tiene en cuenta.def __class__()
truco no parece funcionar con Python 2 o 3, al menos para el código de ejemplo en la pregunta ¿Cómo registrar la implementación de abc.MutableMapping como una subclase dict? (modificado para funcionar de otra manera en las dos versiones). Quieroisinstance(SpreadSheet(), dict)
volverTrue
.