Прокси-объекты создаются менеджерами процесса 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
Вместо этого следует просто использовать копию референта при проведении сравнений.