Proxy Object (прокси-объект) - это специальный объект, который ссылается на разделяемый объект, находящийся (обычно) в другом процессе. Такой разделяемый объект называют referent (референт). Несколько прокси-объектов могут ссылаться на один и тот же референт.
Прокси-объект предоставляет методы, которые вызывают соответствующие методы референта (но не обязательно все методы референта доступны через прокси). Благодаря этому прокси-объект можно использовать почти так же, как и сам референт.
Прокси-объекты создаются менеджерами процесса multiprocessing.Manager().
import multiprocessing mp_context = multiprocessing.get_context('spawn') manager = mp_context.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...> print(l[4]) # 16 print(l[2:5]) # [4, 9, 16]
str(l) возвращает строковое представление референта (например, [0, 1, 4, ...]).repr(l) возвращает представление самого прокси-объекта (<ListProxy object, ...>).Прокси-объекты можно сериализовать (pickle), поэтому их можно передавать между процессами (это происходит "под капотом" при передаче между процессами). Более того, референт может содержать другие прокси-объекты, что позволяет создавать вложенные структуры:
a = manager.list() b = manager.list() # теперь референт a содержит референт b a.append(b) print(a, b) # [<ListProxy object, ...>] [] b.append('hello') print(a[0], b) # ['hello'] ['hello']
Можно создавать вложенные списки и словари, используя прокси-объекты:
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}
Если внутри референта (например, списка) лежит обычный (не прокси) изменяемый объект (например, dict), то изменения этого объекта не будут автоматически синхронизироваться между процессами. Прокси не узнает, что объект внутри изменился.
Пример:
lproxy = manager.list() lproxy.append({}) # добавляем обычный dict d = lproxy[0] d['a'] = 1 d['b'] = 2 # Изменения в d не синхронизированы! # Чтобы синхронизировать, нужно присвоить обратно: lproxy[0] = d
Рекомендация: Для вложенных структур лучше использовать вложенные прокси-объекты (
manager.list(),manager.dict()), чтобы изменения автоматически синхронизировались.
list и dict получили ранее отсутствовавшие методыlist-прокси:clear()copy()Пример:
l = manager.list([1, 2, 3]) l.clear() print(l) # [] l2 = manager.list([4, 5]) l3 = l2.copy() print(l3) # [4, 5]
fromkeys()reversed(d)d | {}, { } | d, d |= {'b': 2}Пример:
d = manager.dict({'a': 1, 'b': 2}) d2 = d.copy() print(d2) # {'a': 1, 'b': 2} d3 = d | {'c': 3} print(d3) # {'a': 1, 'b': 2, 'c': 3} d |= {'d': 4} print(d) # {'a': 1, 'b': 2, 'd': 4} print(list(reversed(d))) # ['d', 'b', 'a']
Прокси-объекты не поддерживают сравнение по значению:
manager.list([1,2,3]) == [1,2,3] # False
Рекомендация: Для сравнения используйте копию референта:
list(manager.list([1,2,3])) == [1,2,3] # True
import multiprocessing def worker(shared_list): for i in range(5): shared_list.append(i) if __name__ == '__main__': with multiprocessing.Manager() as manager: l = manager.list() p = multiprocessing.Process(target=worker, args=(l,)) p.start() p.join() print(list(l)) # [0, 1, 2, 3, 4]
pickle)Прокси-объекты автоматически поддерживают сериализацию через pickle. Это значит, что когда передаётся прокси-объект между процессами (например, через очередь, Pipe, аргумент функции процесса), то Python сам сериализует (pickle) этот объект, а в другом процессе - десериализует (unpickle). То есть это происходит "под капотом" при передаче между процессами.
import multiprocessing def worker(q): # Получаем прокси-объект из очереди shared_list = q.get() shared_list.append('worker') print("В процессе worker:", list(shared_list)) if __name__ == '__main__': with multiprocessing.Manager() as manager: l = manager.list(['main']) q = multiprocessing.Queue() q.put(l) # Прокси-объект l сериализуется (pickle) автоматически p = multiprocessing.Process(target=worker, args=(q,)) p.start() p.join() print("В главном процессе:", list(l)) # В процессе worker: ['main', 'worker'] # В главном процессе: ['main', 'worker']
Пояснение: Прокси-объект l был передан через очередь в другой процесс. Python автоматически сериализовал (pickle) и десериализовал (unpickle) его. Оба процесса работают с одним и тем же референтом.
import multiprocessing def worker(shared_dict): shared_dict['worker'] = 42 if __name__ == '__main__': with multiprocessing.Manager() as manager: d = manager.dict() p = multiprocessing.Process(target=worker, args=(d,)) p.start() p.join() print(d) # {'worker': 42}
Пояснение: Прокси-объект d передаётся как аргумент в другой процесс. Python сам сериализует его через pickle.
import multiprocessing def worker(conn): shared_list = conn.recv() # Получаем прокси-объект shared_list.append('from worker') conn.send(shared_list) if __name__ == '__main__': with multiprocessing.Manager() as manager: l = manager.list(['main']) parent_conn, child_conn = multiprocessing.Pipe() p = multiprocessing.Process(target=worker, args=(child_conn,)) p.start() parent_conn.send(l) # Автоматический pickle l2 = parent_conn.recv() p.join() print(list(l2)) # ['main', 'from worker']