Skip to content

Commit 9f81f82

Browse files
mmingyupicnixzgpshead
authored
gh-129948: Add set() to multiprocessing.managers.SyncManager (#129949)
The SyncManager provided support for various data structures such as dict, list, and queue, but oddly, not set. This introduces support for set by defining SetProxy and registering it with SyncManager. --- Co-authored-by: Bénédikt Tran <[email protected]> Co-authored-by: Gregory P. Smith <[email protected]>
1 parent a65366e commit 9f81f82

File tree

5 files changed

+199
-3
lines changed

5 files changed

+199
-3
lines changed

Doc/library/multiprocessing.rst

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -380,35 +380,40 @@ However, if you really do need to use some shared data then
380380
proxies.
381381

382382
A manager returned by :func:`Manager` will support types
383-
:class:`list`, :class:`dict`, :class:`~managers.Namespace`, :class:`Lock`,
383+
:class:`list`, :class:`dict`, :class:`set`, :class:`~managers.Namespace`, :class:`Lock`,
384384
:class:`RLock`, :class:`Semaphore`, :class:`BoundedSemaphore`,
385385
:class:`Condition`, :class:`Event`, :class:`Barrier`,
386386
:class:`Queue`, :class:`Value` and :class:`Array`. For example, ::
387387

388388
from multiprocessing import Process, Manager
389389

390-
def f(d, l):
390+
def f(d, l, s):
391391
d[1] = '1'
392392
d['2'] = 2
393393
d[0.25] = None
394394
l.reverse()
395+
s.add('a')
396+
s.add('b')
395397

396398
if __name__ == '__main__':
397399
with Manager() as manager:
398400
d = manager.dict()
399401
l = manager.list(range(10))
402+
s = manager.set()
400403

401-
p = Process(target=f, args=(d, l))
404+
p = Process(target=f, args=(d, l, s))
402405
p.start()
403406
p.join()
404407

405408
print(d)
406409
print(l)
410+
print(s)
407411

408412
will print ::
409413

410414
{0.25: None, 1: '1', '2': 2}
411415
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
416+
{'a', 'b'}
412417

413418
Server process managers are more flexible than using shared memory objects
414419
because they can be made to support arbitrary object types. Also, a single
@@ -1942,6 +1947,15 @@ their parent process exits. The manager classes are defined in the
19421947

19431948
Create a shared :class:`list` object and return a proxy for it.
19441949

1950+
.. method:: set()
1951+
set(sequence)
1952+
set(mapping)
1953+
1954+
Create a shared :class:`set` object and return a proxy for it.
1955+
1956+
.. versionadded:: next
1957+
:class:`set` support was added.
1958+
19451959
.. versionchanged:: 3.6
19461960
Shared objects are capable of being nested. For example, a shared
19471961
container object such as a shared list can contain other shared objects

Doc/whatsnew/3.14.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,11 @@ multiprocessing
700700

701701
(Contributed by Roy Hyunjin Han for :gh:`103134`.)
702702

703+
* Add support for shared :class:`set` objects via
704+
:meth:`SyncManager.set() <multiprocessing.managers.SyncManager.set>`.
705+
The :func:`set` in :func:`multiprocessing.Manager` method is now available.
706+
(Contributed by Mingyu Park in :gh:`129949`.)
707+
703708

704709
operator
705710
--------

Lib/multiprocessing/managers.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,36 @@ def __ior__(self, value):
11951195

11961196
collections.abc.MutableMapping.register(_BaseDictProxy)
11971197

1198+
_BaseSetProxy = MakeProxyType("_BaseSetProxy", (
1199+
'__and__', '__class_getitem__', '__contains__', '__iand__', '__ior__',
1200+
'__isub__', '__iter__', '__ixor__', '__len__', '__or__', '__rand__',
1201+
'__ror__', '__rsub__', '__rxor__', '__sub__', '__xor__',
1202+
'__ge__', '__gt__', '__le__', '__lt__',
1203+
'add', 'clear', 'copy', 'difference', 'difference_update', 'discard',
1204+
'intersection', 'intersection_update', 'isdisjoint', 'issubset',
1205+
'issuperset', 'pop', 'remove', 'symmetric_difference',
1206+
'symmetric_difference_update', 'union', 'update',
1207+
))
1208+
1209+
class SetProxy(_BaseSetProxy):
1210+
def __ior__(self, value):
1211+
self._callmethod('__ior__', (value,))
1212+
return self
1213+
def __iand__(self, value):
1214+
self._callmethod('__iand__', (value,))
1215+
return self
1216+
def __ixor__(self, value):
1217+
self._callmethod('__ixor__', (value,))
1218+
return self
1219+
def __isub__(self, value):
1220+
self._callmethod('__isub__', (value,))
1221+
return self
1222+
1223+
__class_getitem__ = classmethod(types.GenericAlias)
1224+
1225+
collections.abc.MutableMapping.register(_BaseSetProxy)
1226+
1227+
11981228
ArrayProxy = MakeProxyType('ArrayProxy', (
11991229
'__len__', '__getitem__', '__setitem__'
12001230
))
@@ -1245,6 +1275,7 @@ class SyncManager(BaseManager):
12451275
SyncManager.register('Pool', pool.Pool, PoolProxy)
12461276
SyncManager.register('list', list, ListProxy)
12471277
SyncManager.register('dict', dict, DictProxy)
1278+
SyncManager.register('set', set, SetProxy)
12481279
SyncManager.register('Value', Value, ValueProxy)
12491280
SyncManager.register('Array', Array, ArrayProxy)
12501281
SyncManager.register('Namespace', Namespace, NamespaceProxy)

Lib/test/_test_multiprocessing.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6441,6 +6441,150 @@ def test_namespace(self):
64416441
o.y = 1
64426442
self.run_worker(self._test_namespace, o)
64436443

6444+
@classmethod
6445+
def _test_set_operator_symbols(cls, obj):
6446+
case = unittest.TestCase()
6447+
obj.update(['a', 'b', 'c'])
6448+
case.assertEqual(len(obj), 3)
6449+
case.assertIn('a', obj)
6450+
case.assertNotIn('d', obj)
6451+
result = obj | {'d', 'e'}
6452+
case.assertSetEqual(result, {'a', 'b', 'c', 'd', 'e'})
6453+
result = {'d', 'e'} | obj
6454+
case.assertSetEqual(result, {'a', 'b', 'c', 'd', 'e'})
6455+
obj |= {'d', 'e'}
6456+
case.assertSetEqual(obj, {'a', 'b', 'c', 'd', 'e'})
6457+
case.assertIsInstance(obj, multiprocessing.managers.SetProxy)
6458+
6459+
obj.clear()
6460+
obj.update(['a', 'b', 'c'])
6461+
result = {'a', 'b', 'd'} - obj
6462+
case.assertSetEqual(result, {'d'})
6463+
result = obj - {'a', 'b'}
6464+
case.assertSetEqual(result, {'c'})
6465+
obj -= {'a', 'b'}
6466+
case.assertSetEqual(obj, {'c'})
6467+
case.assertIsInstance(obj, multiprocessing.managers.SetProxy)
6468+
6469+
obj.clear()
6470+
obj.update(['a', 'b', 'c'])
6471+
result = {'b', 'c', 'd'} ^ obj
6472+
case.assertSetEqual(result, {'a', 'd'})
6473+
result = obj ^ {'b', 'c', 'd'}
6474+
case.assertSetEqual(result, {'a', 'd'})
6475+
obj ^= {'b', 'c', 'd'}
6476+
case.assertSetEqual(obj, {'a', 'd'})
6477+
case.assertIsInstance(obj, multiprocessing.managers.SetProxy)
6478+
6479+
obj.clear()
6480+
obj.update(['a', 'b', 'c'])
6481+
result = obj & {'b', 'c', 'd'}
6482+
case.assertSetEqual(result, {'b', 'c'})
6483+
result = {'b', 'c', 'd'} & obj
6484+
case.assertSetEqual(result, {'b', 'c'})
6485+
obj &= {'b', 'c', 'd'}
6486+
case.assertSetEqual(obj, {'b', 'c'})
6487+
case.assertIsInstance(obj, multiprocessing.managers.SetProxy)
6488+
6489+
obj.clear()
6490+
obj.update(['a', 'b', 'c'])
6491+
case.assertSetEqual(set(obj), {'a', 'b', 'c'})
6492+
6493+
@classmethod
6494+
def _test_set_operator_methods(cls, obj):
6495+
case = unittest.TestCase()
6496+
obj.add('d')
6497+
case.assertIn('d', obj)
6498+
6499+
obj.clear()
6500+
obj.update(['a', 'b', 'c'])
6501+
copy_obj = obj.copy()
6502+
case.assertSetEqual(copy_obj, obj)
6503+
obj.remove('a')
6504+
case.assertNotIn('a', obj)
6505+
case.assertRaises(KeyError, obj.remove, 'a')
6506+
6507+
obj.clear()
6508+
obj.update(['a'])
6509+
obj.discard('a')
6510+
case.assertNotIn('a', obj)
6511+
obj.discard('a')
6512+
case.assertNotIn('a', obj)
6513+
obj.update(['a'])
6514+
popped = obj.pop()
6515+
case.assertNotIn(popped, obj)
6516+
6517+
obj.clear()
6518+
obj.update(['a', 'b', 'c'])
6519+
result = obj.intersection({'b', 'c', 'd'})
6520+
case.assertSetEqual(result, {'b', 'c'})
6521+
obj.intersection_update({'b', 'c', 'd'})
6522+
case.assertSetEqual(obj, {'b', 'c'})
6523+
6524+
obj.clear()
6525+
obj.update(['a', 'b', 'c'])
6526+
result = obj.difference({'a', 'b'})
6527+
case.assertSetEqual(result, {'c'})
6528+
obj.difference_update({'a', 'b'})
6529+
case.assertSetEqual(obj, {'c'})
6530+
6531+
obj.clear()
6532+
obj.update(['a', 'b', 'c'])
6533+
result = obj.symmetric_difference({'b', 'c', 'd'})
6534+
case.assertSetEqual(result, {'a', 'd'})
6535+
obj.symmetric_difference_update({'b', 'c', 'd'})
6536+
case.assertSetEqual(obj, {'a', 'd'})
6537+
6538+
@classmethod
6539+
def _test_set_comparisons(cls, obj):
6540+
case = unittest.TestCase()
6541+
obj.update(['a', 'b', 'c'])
6542+
result = obj.union({'d', 'e'})
6543+
case.assertSetEqual(result, {'a', 'b', 'c', 'd', 'e'})
6544+
case.assertTrue(obj.isdisjoint({'d', 'e'}))
6545+
case.assertFalse(obj.isdisjoint({'a', 'd'}))
6546+
6547+
case.assertTrue(obj.issubset({'a', 'b', 'c', 'd'}))
6548+
case.assertFalse(obj.issubset({'a', 'b'}))
6549+
case.assertLess(obj, {'a', 'b', 'c', 'd'})
6550+
case.assertLessEqual(obj, {'a', 'b', 'c'})
6551+
6552+
case.assertTrue(obj.issuperset({'a', 'b'}))
6553+
case.assertFalse(obj.issuperset({'a', 'b', 'd'}))
6554+
case.assertGreater(obj, {'a'})
6555+
case.assertGreaterEqual(obj, {'a', 'b'})
6556+
6557+
def test_set(self):
6558+
o = self.manager.set()
6559+
self.run_worker(self._test_set_operator_symbols, o)
6560+
o = self.manager.set()
6561+
self.run_worker(self._test_set_operator_methods, o)
6562+
o = self.manager.set()
6563+
self.run_worker(self._test_set_comparisons, o)
6564+
6565+
def test_set_init(self):
6566+
o = self.manager.set({'a', 'b', 'c'})
6567+
self.assertSetEqual(o, {'a', 'b', 'c'})
6568+
o = self.manager.set(["a", "b", "c"])
6569+
self.assertSetEqual(o, {"a", "b", "c"})
6570+
o = self.manager.set({"a": 1, "b": 2, "c": 3})
6571+
self.assertSetEqual(o, {"a", "b", "c"})
6572+
self.assertRaises(RemoteError, self.manager.set, 1234)
6573+
6574+
def test_set_contain_all_method(self):
6575+
o = self.manager.set()
6576+
set_methods = {
6577+
'__and__', '__class_getitem__', '__contains__', '__iand__', '__ior__',
6578+
'__isub__', '__iter__', '__ixor__', '__len__', '__or__', '__rand__',
6579+
'__ror__', '__rsub__', '__rxor__', '__sub__', '__xor__',
6580+
'__ge__', '__gt__', '__le__', '__lt__',
6581+
'add', 'clear', 'copy', 'difference', 'difference_update', 'discard',
6582+
'intersection', 'intersection_update', 'isdisjoint', 'issubset',
6583+
'issuperset', 'pop', 'remove', 'symmetric_difference',
6584+
'symmetric_difference_update', 'union', 'update',
6585+
}
6586+
self.assertLessEqual(set_methods, set(dir(o)))
6587+
64446588

64456589
class TestNamedResource(unittest.TestCase):
64466590
@only_run_in_spawn_testsuite("spawn specific test.")
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add support for shared :class:`set` to :class:`multiprocessing.managers.SyncManager`
2+
via :meth:`SyncManager.set() <multiprocessing.managers.SyncManager.set>`.

0 commit comments

Comments
 (0)