diff --git a/peps/pep-0797.rst b/peps/pep-0797.rst index 3bd131278ad..2b62db3cfe3 100644 --- a/peps/pep-0797.rst +++ b/peps/pep-0797.rst @@ -30,6 +30,18 @@ For example: interp.prepare_main(file=proxy) interp.exec("file.write('I didn't expect the Spanish Inquisition')") +Terminology +=========== + +This PEP uses the term "share", "sharing", and "shareable" to refer to objects +that are *natively* shareable between interpreters. This differs from :pep:`734`, +which uses these terms to also describe an object that supports the :mod:`pickle` +module. + +In addition to the new :class:`~concurrent.interpreters.SharedObjectProxy` type, +the list of natively shareable objects can be found in :ref:`the documentation +`. + Motivation ========== @@ -69,21 +81,56 @@ implementing other methods to share objects between interpreters. Specification ============= -.. class:: concurrent.interpreters.SharedObjectProxy + +.. function:: concurrent.interpreters.share(obj) + + Ensure *obj* is natively shareable. + + If *obj* is natively shareable, this function does not create a proxy and + simply returns *obj*. Otherwise, *obj* is wrapped in an instance of + :class:`~concurrent.interpreters.SharedObjectProxy` and returned. + + If *obj* has a :meth:`~object.__share__` method, the default behavior of + this function is overridden; the object's ``__share__`` method will be + called to convert *obj* into a natively shareable version of itself, which + will be returned by this function. If the object returned by ``__share__`` + is not natively shareable, this function raises an exception. + + The behavior of this function is roughly equivalent to: + + .. code-block:: python + + def share(obj): + if _is_natively_shareable(obj): + return obj + + if hasattr(obj, "__share__"): + shareable = obj.__share__() + if not _is_natively_shareable(shareable): + raise TypeError(f"__share__() returned unshareable object: {shareable!r}") + + return shareable + + return SharedObjectProxy(obj) + + +.. class:: concurrent.interpreters.SharedObjectProxy(obj) A proxy type that allows access to an object across multiple interpreters. - This cannot be constructed from Python; instead, use the - :func:`~concurrent.interpreters.share` function. + Instances of this object are natively shareable between subinterpreters. + Unlike :func:`~concurrent.interpreters.share`, *obj* will always be wrapped, + even if it is natively shareable already or already a ``SharedObjectProxy`` + instance. The object's :meth:`~object.__share__` method is not invoked if + it is available. Thus, prefer using ``share`` where possible. -.. function:: concurrent.interpreters.share(obj) - Wrap *obj* in a :class:`~concurrent.interpreters.SharedObjectProxy`, - allowing it to be used in other interpreter APIs as if it were natively - shareable. +.. function:: object.__share__() - If *obj* is natively shareable, this function does not create a proxy and - simply returns *obj*. + Return a natively shareable version of the current object. This includes + shared object proxies, as they are also natively shareable. Objects composed + of shared object proxies are also allowed, such as a :class:`tuple` whose + elements are :class:`~concurrent.interpreters.SharedObjectProxy` instances. Interpreter Switching @@ -108,6 +155,26 @@ accessed in subinterpreters through a proxy: interp.exec("foo()") +Method Proxying +--------------- + +Methods on a shared object proxy will switch to their owning interpreter when +accessed. In addition, any arguments passed to the method are implicitly called +with :func:`~concurrent.interpreters.share` to ensure they are shareable (only +types that are not natively shareable are wrapped in a proxy). The same happens +to the return value of the method. + +For example, the ``__add__`` method on an object proxy is roughly equivalent +to the following code: + +.. code-block:: python + + def __add__(self, other): + with self.switch_interpreter(): + result = self.value.__add__(share(other)) + return share(result) + + Multithreaded Scaling ---------------------