¿Cuál es el propósito de las colecciones.ChainMap?

En Python 3.3 se agregó una clase ChainMap al módulo de collections :

Se proporciona una clase ChainMap para vincular rápidamente un número de asignaciones para que puedan tratarse como una sola unidad. A menudo es mucho más rápido que crear un nuevo diccionario y ejecutar varias llamadas de actualización ().

Ejemplo:

 >>> from collections import ChainMap >>> x = {'a': 1, 'b': 2} >>> y = {'b': 10, 'c': 11} >>> z = ChainMap(y, x) >>> for k, v in z.items(): print(k, v) a 1 c 11 b 10 

Fue motivado por este problema y hecho público por este (no se creó ningún PEP ).

Según tengo entendido, es una alternativa a tener un diccionario adicional y mantenerlo con update() s.

Las preguntas son:

  • ¿Qué casos de uso cubre ChainMap ?
  • ¿Hay ejemplos del mundo real de ChainMap ?
  • ¿Se utiliza en bibliotecas de terceros que cambiaron a python3?

Pregunta extra: ¿hay alguna forma de usarlo en Python2.x?


Me he enterado al respecto en Transforming Code into Beautiful, Idiomatic Python PyCon charla de Raymond Hettinger y me gustaría agregarla a mi caja de herramientas, pero no entiendo cuándo debo usarla.

Me gustan los ejemplos de @ b4hand, y de hecho lo he usado en el pasado en estructuras similares a las de ChainMap (pero no en ChainMap) para los dos propósitos que menciona: anulaciones de configuración de múltiples capas y emulación de stack / scope variable.

Me gustaría señalar otras dos motivaciones / ventajas / diferencias de ChainMap , en comparación con el uso de un bucle de actualización de dict, por lo tanto, solo se almacena la “versión final”:

  1. Más información: dado que una estructura ChainMap está “en capas”, admite responder a preguntas como: ¿Estoy obteniendo el valor “predeterminado”, o uno anulado? ¿Cuál es el valor original (“predeterminado”)? ¿En qué nivel se anuló el valor (tomando prestado el ejemplo de configuración de b4hand: user-config o command-line-overrides)? Usando un dictado simple, la información necesaria para responder estas preguntas ya está perdida.

  2. Compensación rápida: suponga que tiene N capas y, como máximo, M teclas en cada una, la construcción de un ChainMap toma O(N) y cada búsqueda en el peor de los casos [*], mientras que la construcción de un dict utilizando un ciclo de actualización toma O(NM) y cada búsqueda O(1) . Esto significa que si construye a menudo y solo realiza algunas búsquedas cada vez, o si M es grande, el enfoque de construcción perezosa de ChainMap funciona a su favor.

[*] El análisis en (2) supone que el acceso dict es O(1) , cuando en realidad es O(1) en promedio, y O(M) peor de los casos. Ver más detalles aquí .

Podría ver el uso de ChainMap para un objeto de configuración en el que tiene varios ámbitos de configuración, como las opciones de línea de comandos, un archivo de configuración de usuario y un archivo de configuración del sistema. Dado que las búsquedas están ordenadas por el orden en el argumento del constructor, puede anular la configuración en los ámbitos inferiores. Personalmente no he usado o visto el uso de ChainMap , pero eso no es sorprendente ya que es una adición bastante reciente a la biblioteca estándar.

También podría ser útil para emular los marcos de stack en los que empuja y abre los enlaces de variables si estuviera intentando implementar un ámbito léxico usted mismo.

Los documentos de la biblioteca estándar para ChainMap dan varios ejemplos y enlaces a implementaciones similares en bibliotecas de terceros. Específicamente, nombra la clase Context de Django y la clase MultiContext de Enthought.

Voy a echar un vistazo a esto:

Chainmap parece una abstracción muy justa. Es una buena solución para un tipo de problema muy especializado. Propongo este caso de uso.

Si usted tiene:

  1. múltiples asignaciones (por ejemplo, dictados)
  2. alguna duplicación de claves en esas asignaciones (la misma clave puede aparecer en múltiples asignaciones, pero no en el caso de que todas las claves aparezcan en todas las asignaciones)
  3. una aplicación consumidora que desea acceder al valor de una clave en el mapeo de “máxima prioridad” donde hay un orden total sobre todos los mapeos para cualquier clave dada (es decir, los mapeos pueden tener la misma prioridad, pero solo si se sabe que no hay duplicaciones de claves dentro de esas asignaciones) (En la aplicación de Python, los paquetes pueden vivir en el mismo directorio (la misma prioridad) pero deben tener nombres diferentes, por lo que, por definición, los nombres de los símbolos en ese directorio no pueden ser duplicados).
  4. La aplicación consumidora no necesita cambiar el valor de una clave
  5. mientras que, al mismo tiempo, las asignaciones deben mantener su identidad independiente y pueden ser cambiadas de forma asíncrona por una fuerza externa
  6. y las asignaciones son lo suficientemente grandes, lo suficientemente caras para acceder o cambian con la frecuencia suficiente entre los accesos a las aplicaciones, por lo que el costo de calcular la proyección (3) cada vez que su aplicación lo necesita es un problema de rendimiento importante para su aplicación …

Luego, podría considerar usar un mapa de cadena para crear una vista sobre la colección de asignaciones.

Pero todo esto es justificación posterior al hecho. Los chicos de Python tuvieron un problema, encontraron una buena solución en el contexto de su código, luego hicieron un trabajo extra para abstraer su solución para que pudiéramos usarla si lo decidimos. Más poder para ellos. Pero si es apropiado para su problema depende de usted decidir.

Para contestar imperfectamente a tu:

Pregunta extra: ¿hay alguna forma de usarlo en Python2.x?

 from ConfigParser import _Chainmap as ChainMap 

Sin embargo, tenga en cuenta que esto no es un ChainMap real, se hereda de DictMixin y solo define:

 __init__(self, *maps) __getitem__(self, key) keys(self) # And from DictMixin: __iter__(self) has_key(self, key) __contains__(self, key) iteritems(self) iterkeys(self) itervalues(self) values(self) items(self) clear(self) setdefault(self, key, default=None) pop(self, key, *args) popitem(self) update(self, other=None, **kwargs) get(self, key, default=None) __repr__(self) __cmp__(self, other) __len__(self) 

Su implementación tampoco parece particularmente eficiente.