2012-05-29 10 views
8

El departamento de su tardío-y-im-probable-estúpida presenta:Es multiprocesamiento.Manager(). Dict(). Setdefault() roto?

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

Considerando lo siguiente:

>>> 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?

EDIT: 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 dict no contienen el elemento adicional?

Respuesta

8

Este es un comportamiento bastante interesante, no estoy exactamente seguro de cómo funciona, pero analizaré por qué el comportamiento es así.

En primer lugar, tenga en cuenta que multiprocessing.Manager().dict() no es un dict, es un objeto DictProxy:

>>> d = multiprocessing.Manager().dict() 
>>> d 
<DictProxy object, typeid 'dict' at 0x7fa2bbe8ea50> 

El propósito de la clase DictProxy es para darle una dict que es seguro para compartir a través de los procesos, lo que significa que debe implementar algún bloqueo sobre las funciones normales de dict.

Aparentemente, parte de la implementación aquí es no permitirle acceder directamente a los objetos mutables anidados dentro de un DictProxy, porque si eso estuviera permitido, podría modificar su objeto compartido de manera que omita todo el bloqueo que hace que DictProxy sea seguro de usar.

Aquí algunas pruebas de que no se puede acceder a los objetos mutables, que es similar a lo que está pasando con setdefault():

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

Con un diccionario normal, se puede esperar d['foo']foo y para que apunte a la misma list object, y las modificaciones a uno modificarían el otro. Como ha visto, este no es el caso para la clase DictProxy debido al requisito de seguridad adicional del proceso impuesto por el módulo de multiprocesamiento.

edición: La siguiente nota de la multiprocessing documentation aclara lo que estaba tratando de decir anteriormente:


Nota: Las modificaciones a los valores mutables o elementos en dict y lista de servidores proxy no será propagado a través del administrador, porque el proxy no tiene forma de saber cuándo se modifican sus valores o elementos. Para modificar un elemento tal, puede volver a asignar el objeto modificado al proxy 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 

Con base en la información anterior, aquí es cómo se puede reescribir su código original para trabajar con una DictProxy:

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

Como Edward Loper sugirieron en los comentarios, editado por encima de código para utilizarget()en lugar desetdefault().

+4

+1. Pero creo que sería más claro volver a escribir el código original para usar 'get' en lugar de' setdefault', ya que el comportamiento especial normal de 'setdefault' no se aplica aquí. Es decir .: 'd ['foo'] = d.get ('foo', []) + [{'bar': 'baz'}]' –

+0

Gracias por esta extensa respuesta. Supuse que dado que el proxy dict envolvía la lista que se le había dado, supuse que estaría bien operarlo. Aparentemente no es así. @Edward: eso es un buen consejo. – Bittrance

0

elementos() devuelve una copia. Agregar a una copia no afecta el original. ¿Quisiste decir esto? .

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

El Manager() dict() es un objeto DictProxy:

>>> mgr.dict() 
<DictProxy object, typeid 'dict' at 0x1007bab50> 
>>> type(mgr.dict()) 
<class 'multiprocessing.managers.DictProxy'> 

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

Por lo tanto, parece que tiene que tratar el mgr.dict() de forma diferente a como lo haría con un dict base.

Cuestiones relacionadas