Skip to content

Commit e426be8

Browse files
committed
PEP 797: Revisions based on in-person feedback
1 parent 9cc461d commit e426be8

1 file changed

Lines changed: 37 additions & 75 deletions

File tree

peps/pep-0797.rst

Lines changed: 37 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ Post-History: `01-Jul-2025 <https://discuss.python.org/t/97306>`__,
1313
Abstract
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

2121
For 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
4545
Motivation
4646
==========
4747

48-
Many Objects Cannot be Shared Between Subinterpreters
48+
Many objects cannot be shared between subinterpreters
4949
-----------------------------------------------------
5050

5151
In Python 3.14, the new :mod:`concurrent.interpreters` module can be used to
@@ -66,7 +66,7 @@ ideal for multithreaded applications.
6666
Rationale
6767
=========
6868

69-
A Fallback for Object Sharing
69+
A fallback for object sharing
7070
-----------------------------
7171

7272
A 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

13994
When 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

161116
Methods 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

167122
For example, the ``__add__`` method on an object proxy is roughly equivalent
168123
to 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

181136
To 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

231186
Contrary 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

257212
Accessing 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
303258
variables and similar state will not disappear.
304259

305260

306-
Memory Management
261+
Memory management
307262
-----------------
308263

309264
All 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

356311
When an interpreter is destroyed, shared object proxies wrapping objects
357312
owned by that interpreter may still exist elsewhere. To prevent this
358313
from 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

361316
To 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
410361
Rejected 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

416378
The initial revision of this proposal took an approach where an instance of

0 commit comments

Comments
 (0)