¿Está multiprocessing.Manager (). Dict (). Setdefault () roto?

El departamento de “Es tarde y probablemente estúpido” presenta:

>>> import multiprocessing >>> mgr = multiprocessing.Manager() >>> d = mgr.dict() >>> d.setdefault('foo', []).append({'bar': 'baz'}) >>> print d.items() [('foo', [])] <-- Where did the dict go? 

Mientras:

 >>> e = mgr.dict() >>> e['foo'] = [{'bar': 'baz'}] >>> print e.items() [('foo', [{'bar': 'baz'}])] 

Versión:

 >>> sys.version '2.7.2+ (default, Jan 20 2012, 23:05:38) \n[GCC 4.6.2]' 

Bug o wug?

EDITAR: Más de lo mismo, en python 3.2:

 >>> sys.version '3.2.2rc1 (default, Aug 14 2011, 21:09:07) \n[GCC 4.6.1]' >>> e['foo'] = [{'bar': 'baz'}] >>> print(e.items()) [('foo', [{'bar': 'baz'}])] >>> id(type(e['foo'])) 137341152 >>> id(type([])) 137341152 >>> e['foo'].append({'asdf': 'fdsa'}) >>> print(e.items()) [('foo', [{'bar': 'baz'}])] 

¿Cómo puede la lista en el proxy de dict no contener el elemento adicional?

Este es un comportamiento bastante interesante, no estoy exactamente seguro de cómo funciona, pero voy a entender por qué el comportamiento es como es.

Primero, note que multiprocessing.Manager().dict() no es un dict , es un objeto DictProxy :

 >>> d = multiprocessing.Manager().dict() >>> d  

El propósito de la clase DictProxy es proporcionarle un dict que sea seguro compartir entre procesos, lo que significa que debe implementar algún locking sobre las funciones normales de dict .

Al parecer, parte de la implementación aquí es no permitirle acceder directamente a objetos mutables nesteds dentro de un DictProxy , ya que si se permitiera, podría modificar su objeto compartido de una manera que evite todo el locking que hace que DictProxy sea ​​seguro utilizar.

Aquí hay algunas pruebas de que no puede acceder a objetos mutables, que es similar a lo que sucede con setdefault() :

 >>> d['foo'] = [] >>> foo = d['foo'] >>> id(d['foo']) 140336914055536 >>> id(foo) 140336914056184 

Con un diccionario normal, se esperaría que d['foo'] y foo apunten al mismo objeto de lista, y las modificaciones a uno modificarían al otro. Como ha visto, este no es el caso para la clase DictProxy debido a los requisitos de seguridad del proceso adicionales impuestos por el módulo de multiprocesamiento.

edición: la siguiente nota de la documentación de multiprocesamiento aclara lo que intentaba decir anteriormente:


Nota: Las modificaciones de los valores o elementos mutables en los proxies dict y list no se propagarán a través del administrador, ya que el proxy no tiene forma de saber cuándo se modifican sus valores o elementos. Para modificar un elemento de este tipo, puede volver a asignar el objeto modificado al proxy de contenedor:

 # create a list proxy and append a mutable object (a dictionary) lproxy = manager.list() lproxy.append({}) # now mutate the dictionary d = lproxy[0] d['a'] = 1 d['b'] = 2 # at this point, the changes to d are not yet synced, but by # reassigning the dictionary, the proxy is notified of the change lproxy[0] = d 

Basado en la información anterior, aquí es cómo podría reescribir su código original para trabajar con un DictProxy :

 # d.setdefault('foo', []).append({'bar': 'baz'}) d['foo'] = d.get('foo', []) + [{'bar': 'baz'}] 

Como Edward Loper sugirió en los comentarios, setdefault() código anterior para usar get() lugar de setdefault() .

El Administrador (). Dict () es un objeto DictProxy:

 >>> mgr.dict()  >>> type(mgr.dict())  

DictProxy es una subclase del tipo BaseProxy, que no se comporta completamente como un dict: http://docs.python.org/library/multiprocessing.html?highlight=multiprocessing#multiprocessing.managers.BaseProxy

Por lo tanto, parece que tienes que dirigir el comando mgr.dict () de manera diferente a como lo harías con un dict de base

items () devuelve una copia. Anexar a una copia no afecta al original. Quiso decir esto

 >>> d['foo'] =({'bar': 'baz'}) >>> print d.items() [('foo', {'bar': 'baz'})]