@@ -13,10 +13,10 @@ Post-History: `01-Jul-2025 <https://discuss.python.org/t/97306>`__,
1313Abstract
1414========
1515
16- This PEP introduces a new :func: `~concurrent.interpreters.share ` function to
17- the :mod: `concurrent.interpreters ` module, which allows any arbitrary object
18- to be shared across interpreters using an object proxy, at the cost of being
19- less efficient to concurrently access across multiple interpreters.
16+ This PEP introduces a new :func: `~concurrent.interpreters.SharedObjectProxy `
17+ type to the :mod: `concurrent.interpreters ` module, which allows any arbitrary
18+ object to be shared across interpreters using an object proxy, at the cost of
19+ being less efficient to concurrently access across multiple interpreters.
2020
2121For example:
2222
@@ -26,7 +26,7 @@ For example:
2626
2727 with open (" spanish_inquisition.txt" ) as unshareable:
2828 interp = interpreters.create()
29- proxy = interpreters.share (unshareable)
29+ proxy = interpreters.SharedObjectProxy (unshareable)
3030 interp.prepare_main(file = proxy)
3131 interp.exec(" file.write('I didn't expect the Spanish Inquisition')" )
3232
@@ -45,7 +45,7 @@ the list of natively shareable objects can be found in :ref:`the documentation
4545Motivation
4646==========
4747
48- Many Objects Cannot be Shared Between Subinterpreters
48+ Many objects cannot be shared between subinterpreters
4949-----------------------------------------------------
5050
5151In Python 3.14, the new :mod: `concurrent.interpreters ` module can be used to
@@ -66,7 +66,7 @@ ideal for multithreaded applications.
6666Rationale
6767=========
6868
69- A Fallback for Object Sharing
69+ A fallback for object sharing
7070-----------------------------
7171
7272A shared object proxy is designed to be a fallback for sharing an object
@@ -82,58 +82,13 @@ Specification
8282=============
8383
8484
85- .. function :: concurrent.interpreters.share(obj)
86-
87- Ensure *obj * is natively shareable.
88-
89- If *obj * is natively shareable, this function does not create a proxy and
90- simply returns *obj *. Otherwise, *obj * is wrapped in an instance of
91- :class: `~concurrent.interpreters.SharedObjectProxy ` and returned.
92-
93- If *obj * has a :meth: `~object.__share__ ` method, the default behavior of
94- this function is overridden; the object's ``__share__ `` method will be
95- called to convert *obj * into a natively shareable version of itself, which
96- will be returned by this function. If the object returned by ``__share__ ``
97- is not natively shareable, this function raises an exception.
98-
99- The behavior of this function is roughly equivalent to:
100-
101- .. code-block :: python
102-
103- def share (obj ):
104- if _is_natively_shareable(obj):
105- return obj
106-
107- if hasattr (obj, " __share__" ):
108- shareable = obj.__share__()
109- if not _is_natively_shareable(shareable):
110- raise TypeError (f " __share__() returned unshareable object: { shareable!r } " )
111-
112- return shareable
113-
114- return SharedObjectProxy(obj)
115-
116-
11785.. class :: concurrent.interpreters.SharedObjectProxy(obj)
11886
11987 A proxy type that allows access to an object across multiple interpreters.
12088 Instances of this object are natively shareable between subinterpreters.
12189
122- Unlike :func: `~concurrent.interpreters.share `, *obj * will always be wrapped,
123- even if it is natively shareable already or already a ``SharedObjectProxy ``
124- instance. The object's :meth: `~object.__share__ ` method is not invoked if
125- it is available. Thus, prefer using ``share `` where possible.
12690
127-
128- .. function :: object.__share__()
129-
130- Return a natively shareable version of the current object. This includes
131- shared object proxies, as they are also natively shareable. Objects composed
132- of shared object proxies are also allowed, such as a :class: `tuple ` whose
133- elements are :class: `~concurrent.interpreters.SharedObjectProxy ` instances.
134-
135-
136- Interpreter Switching
91+ Interpreter switching
13792---------------------
13893
13994When interacting with the wrapped object, the proxy will switch to the
@@ -155,14 +110,14 @@ accessed in subinterpreters through a proxy:
155110 interp.exec(" foo()" )
156111
157112
158- Method Proxying
113+ Method proxying
159114---------------
160115
161116Methods on a shared object proxy will switch to their owning interpreter when
162- accessed. In addition, any arguments passed to the method are implicitly called
163- with :func: ` ~concurrent.interpreters.share ` to ensure they are shareable (only
164- types that are not natively shareable are wrapped in a proxy) . The same happens
165- to the return value of the method.
117+ accessed. In addition, any arguments passed to the method are implicitly
118+ ensured to be shareable. If they aren't natively shareable, they are wrapped
119+ in an instance of `` SharedObjectProxy `` . The same happens to the return value
120+ of the method.
166121
167122For example, the ``__add__ `` method on an object proxy is roughly equivalent
168123to the following code:
@@ -175,7 +130,7 @@ to the following code:
175130 return share(result)
176131
177132
178- Multithreaded Scaling
133+ Multithreaded scaling
179134---------------------
180135
181136To switch to a wrapped object's interpreter, an object proxy must swap the
@@ -219,13 +174,13 @@ performing the computation can still execute while accessing the proxy.
219174
220175 thread.join()
221176
222- proxy = interpreters.share (write_log)
177+ proxy = interpreters.SharedObjectProxy (write_log)
223178 for n in range (4 ):
224179 interp = interpreters.create()
225180 interp.call_in_thread(execute, n, proxy)
226181
227182
228- Proxy Copying
183+ Proxy copying
229184-------------
230185
231186Contrary to what one might think, a shared object proxy itself can only be used
@@ -242,7 +197,7 @@ For example, in the following code, there are two proxies created, not just one.
242197
243198 interp = interpreters.create()
244199 foo = object ()
245- proxy = interpreters.share (foo)
200+ proxy = interpreters.SharedObjectProxy (foo)
246201
247202 # The proxy crosses an interpreter boundary here. 'proxy' is *not* directly
248203 # send to 'interp'. Instead, a new proxy is created for 'interp', and the
@@ -251,7 +206,7 @@ For example, in the following code, there are two proxies created, not just one.
251206 interp.prepare_main(proxy = proxy)
252207
253208
254- Thread-local State
209+ Thread-local state
255210------------------
256211
257212Accessing an object proxy will retain information stored on the current
@@ -271,7 +226,7 @@ This allows the following case to work correctly:
271226 assert thread_local.value == 1
272227
273228 interp = interpreters.create()
274- proxy = interpreters.share (foo)
229+ proxy = interpreters.SharedObjectProxy (foo)
275230 interp.prepare_main(foo = proxy)
276231 interp.exec(" foo()" )
277232
@@ -303,7 +258,7 @@ the thread. In other words, a shared object proxy ensures that thread local
303258variables and similar state will not disappear.
304259
305260
306- Memory Management
261+ Memory management
307262-----------------
308263
309264All proxy objects hold a :term: `strong reference ` to the object that they
@@ -350,16 +305,16 @@ in the wrapped object's interpreter. To visualize:
350305 interp.exec(" import gc; print(gc.get_referents(proxy))" )
351306
352307
353- Interpreter Lifetimes
354- *********************
308+ Interpreter lifetime management
309+ -------------------------------
355310
356311When an interpreter is destroyed, shared object proxies wrapping objects
357312owned by that interpreter may still exist elsewhere. To prevent this
358313from causing crashes, an interpreter will invalidate all proxies pointing
359- to any object it owns by overwriting the proxy's wrapped object with `` None `` .
314+ to any object it owns, so any subsequent access to a proxy will raise an exception .
360315
361316To demonstrate, the following snippet first prints out ``Alive ``, and then
362- `` None `` after deleting the interpreter:
317+ raises a `` RuntimeError `` after deleting the interpreter:
363318
364319.. code-block :: python
365320
@@ -378,11 +333,7 @@ To demonstrate, the following snippet first prints out ``Alive``, and then
378333 wrapped = interp.call(test)
379334 print (wrapped) # Alive
380335 interp.close()
381- print (wrapped) # None
382-
383- Note that the proxy is not physically replaced (``wrapped `` in the above example
384- is still a ``SharedObjectProxy `` instance), but instead has its wrapped object
385- replaced with ``None ``.
336+ print (wrapped) # RuntimeError
386337
387338
388339 Backwards Compatibility
@@ -410,7 +361,18 @@ A reference implementation of this PEP can be found at
410361Rejected Ideas
411362==============
412363
413- Directly Sharing Proxy Objects
364+ Introducing a generic sharing protocol
365+ --------------------------------------
366+
367+ This PEP used to specify a ``share() `` function that would call a
368+ ``__share__() `` method on an object, or otherwise implicitly wrap the object
369+ in a ``SharedObjectProxy ``.
370+
371+ It was deemed that this wasn't necessary for this proposal to work, so this
372+ protocol is left to be done by a future PEP.
373+
374+
375+ Directly sharing proxy objects
414376------------------------------
415377
416378The initial revision of this proposal took an approach where an instance of
0 commit comments