Сообщить об ошибке.

Прокси-объекты менеджера, модуля multiprocessing в Python

Что такое прокси-объект и как его использовать в процессах

Прокси-объекты создаются менеджерами процесса multiprocessing.Manager().

Прокси-объект - это объект, который ссылается на общий объект, который (предположительно) живет в другом процессе. Общий объект называется референтом прокси. Один и тот же референт может иметь несколько прокси-объектов.

Прокси-объект имеет методы, которые вызывают соответствующие методы его референта, хотя не каждый метод референта обязательно будет доступен через прокси. Таким образом, прокси можно использовать так же, как и его референт:

>>> from multiprocessing import Manager
>>> manager = Manager()
>>> l = manager.list([i*i for i in range(10)])
>>> print(l)
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> print(repr(l))
# <ListProxy object, typeid 'list' at 0x...>
>>> l[4]
# 16
>>> l[2:5]
# [4, 9, 16]

Обратите внимание, что применение функции str() к прокси вернет представление референта, тогда как применение [функции repr() вернет представление прокси.

Важная особенность прокси-объектов заключается в том, что они упаковываются модулем pickle, поэтому их можно передавать между процессами. Таким образом, референт может содержать прокси-объекты. Прокси-объектам разрешается иметь вложенные управляемые объекты: это относится к спискам, словарям и другим прокси-объектам:

>>> a = manager.list()
>>> b = manager.list()
>>> a.append(b)
# референт `a` теперь содержит референт `b`
>>> print(a, b)
# [<ListProxy object, typeid 'list' at ...>] []
>>> b.append('hello')
>>> print(a[0], b)
# ['hello'] ['hello']

Аналогично, прокси-объекты dict и list могут быть вложены друг в друга:

>>> l_outer = manager.list([ manager.dict() for i in range(2) ])
>>> d_first_inner = l_outer[0]
>>> d_first_inner['a'] = 1
>>> d_first_inner['b'] = 2
>>> l_outer[1]['c'] = 3
>>> l_outer[1]['z'] = 26
>>> print(l_outer[0])
# {'a': 1, 'b': 2}
>>> print(l_outer[1])
# {'c': 3, 'z': 26}

Если стандартный (не прокси) список list или словарь dict содержатся в референте, то изменения этих изменяемых значений не будут распространяться через менеджер, поскольку прокси не имеет возможности узнать, когда изменены содержащиеся в нем значения.

Однако сохранение значения в прокси-объекте контейнера (которое запускает __setitem__ на объекте-прокси) распространяется через менеджер, поэтому для эффективного изменения такого элемента можно повторно назначить измененное значение прокси-объекту контейнера:

# создаем прокси-объект списка и добавляем
# изменяемый объект (словарь)
lproxy = manager.list()
lproxy.append({})
# теперь изменяем словарь
d = lproxy[0]
d['a'] = 1
d['b'] = 2
# на данный момент изменения в словаре `d` еще не
# синхронизированы, но при переназначении словаря 
# прокси-сервер получает уведомление об этом изменении
lproxy[0] = d
# теперь изменения распространены на все процессы

Этот подход, для большинства случаев использования, менее удобен, чем использование вложенных прокси-объектов, но он демонстрирует уровень контроля над синхронизацией.

Примечание. Типы прокси в модуле multiprocessing ничего не делают для поддержки сравнения по значению.

Так, например есть:

>>> manager.list([1,2,3]) == [1,2,3]
# False

Вместо этого следует просто использовать копию референта при проведении сравнений.