# PEP 797: Shared Object Proxies

**URL:** <https://discuss.python.org/t/pep-797-shared-object-proxies/105709>\
**Category:** PEPs\
**Created:** [January 13, 2026, 9:38pm UTC](https://discuss.python.org/t/pep-797-shared-object-proxies/105709 "2026-01-13T21:38:22Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![ZeroIntensity](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/zerointensity/32/39016_2.png) [@ZeroIntensity](https://discuss.python.org/u/ZeroIntensity)\
**Post date:** [January 13, 2026, 9:38pm UTC](https://discuss.python.org/t/pep-797-shared-object-proxies/105709/1 "2026-01-13T21:38:22Z")

</div>

Hi everyone,

After extensive discussion and delays, I’m excited to present PEP 797, which proposes introducing an object proxy to share arbitrary objects between subinterpreters.

Here’s the full text:

> **[PEP 797 – Shared Object Proxies | peps.python.org](https://peps.python.org/pep-0797/)**
>
> This PEP introduces a new share() function to the concurrent.interpreters module, which allows any arbitrary object to be shared across interpreters using an object proxy, at the cost of being less efficient to concurrently access across multiple...

---

<div class="post-metadata">

**Author:** ![ruro](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ruro/32/7340_2.png) [@ruro](https://discuss.python.org/u/ruro)\
**Post date:** [January 15, 2026, 6:06pm UTC](https://discuss.python.org/t/pep-797-shared-object-proxies/105709/2 "2026-01-15T18:06:48Z")

</div>

I think that the “Interpreter Switching” section is a bit vague at the moment.

For example, what happens if I do this:

```python
def foo(target):
    # This code is supposedly run in the main interpreter,
    # but `unshareable` was originally owned by `interp`
    target.write("Our chief weapon is surprise...")

interp = interpreters.create()
proxy = interpreters.share(foo)
interp.prepare_main(foo=proxy)
interp.exec("""
with open("spanish_inquisition.txt") as unshareable:
    foo(unshareable)
""")

```

Is the `unshareable`/`target` argument implicitly wrapped in its own `SharedObjectProxy` (so the `target.write` call actually ends up executing in the subinterpreter)?

---

<div class="post-metadata">

**Author:** ![ZeroIntensity](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/zerointensity/32/39016_2.png) [@ZeroIntensity](https://discuss.python.org/u/ZeroIntensity)\
**Post date:** [January 15, 2026, 6:38pm UTC](https://discuss.python.org/t/pep-797-shared-object-proxies/105709/3 "2026-01-15T18:38:17Z")

</div>

Thanks for the feedback.

> [@ruro](#):
>
> Is the `unshareable`/`target` argument implicitly wrapped in its own `SharedObjectProxy` (so the `target.write` call actually ends up executing in the subinterpreter)?

Yes, that’s correct. Arguments and return values from functions are implicitly wrapped in a `SharedObjectProxy`, unless the object is natively shareable. So, for example, if `target` were to be a string, then that wouldn’t require the creation of a new proxy. I’ll add a section clarifying this.

---

<div class="post-metadata">

**Author:** ![ruro](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ruro/32/7340_2.png) [@ruro](https://discuss.python.org/u/ruro)\
**Post date:** [January 15, 2026, 8:17pm UTC](https://discuss.python.org/t/pep-797-shared-object-proxies/105709/4 "2026-01-15T20:17:36Z")

</div>

Follow up question:

I am slightly concerned that `SharedObjectProxy` objects might be “viral”. They are seemingly intended to be used “transparently” in place of the wrapped object/type. But since arguments / return values are `share`d automatically, it might be exceedingly easy to accidentally “infect” a performance critical part of the program with shared proxies.

For example, a shared object might be accidentally passed to a library function that performs caching (or other operations that affect the global state). This would then “poison” the cache / global state, causing the library function to start returning values that actually live in a different interpreter.

One mitigation for this might be to automatically “unwrap” the proxies when they are passed / returned _back_ to their originating interpreter. But that doesn’t quite solve all potential “virality” issues.

---

<div class="post-metadata">

**Author:** ![ZeroIntensity](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/zerointensity/32/39016_2.png) [@ZeroIntensity](https://discuss.python.org/u/ZeroIntensity)\
**Post date:** [January 15, 2026, 8:52pm UTC](https://discuss.python.org/t/pep-797-shared-object-proxies/105709/5 "2026-01-15T20:52:29Z")

</div>

> [@ruro](#):
>
> I am slightly concerned that `SharedObjectProxy` objects might be “viral”. They are seemingly intended to be used “transparently” in place of the wrapped object/type. But since arguments / return values are `share`d automatically, it might be exceedingly easy to accidentally “infect” a performance critical part of the program with shared proxies.

I don’t think “virality” is the right term, because objects referenced by shared proxies aren’t also wrapped in a proxy. But anyway, this is why proxies are supposed to be a fallback mechanism. If they’re causing problems, use serialization in your app instead. I don’t expect proxies to be very useful in performance-critical code paths.

> [@ruro](#):
>
> This would then “poison” the cache / global state, causing the library function to start returning values that actually live in a different interpreter.

I’m not sure I understand your example. A function returning a shared object proxy isn’t inherently bad, especially if all you’re doing is looking at a cache. They’re slower when accessed from multiple threads, but that’s true for lots of objects.

More generally, though, global state is a bad idea in concurrency situations, and care is going to be needed regardless of what you’re using.

> [@ruro](#):
>
> One mitigation for this might be to automatically “unwrap” the proxies when they are passed / returned _back_ to their originating interpreter.

Hm, what does “unwrap” mean here? If you mean unpacking the underlying object, then that’s not possible, because it isn’t thread-safe to access the object from another interpreter.

---

<div class="post-metadata">

**Author:** ![BrenBarn](https://avatars.discourse-cdn.com/v4/letter/b/74df32/32.png) [@BrenBarn](https://discuss.python.org/u/BrenBarn)\
**Post date:** [January 16, 2026, 3:51am UTC](https://discuss.python.org/t/pep-797-shared-object-proxies/105709/6 "2026-01-16T03:51:10Z")

</div>

Just a small comment: the specification for `share` should probably explicitly say that it returns the proxy, or else people may think it somehow converts the object into a proxy.\[1\] Perhaps something like:

> If `obj` is natively shareable, return `obj`. Otherwise, returns a SharedObjectProxy that wraps `obj`.

Also, does “natively shareable” include SharedObjectProxy objects themselves? As in, what happens if someone calls `share(obj)` where `obj` is already a SharedObjectProxy?

* * *

1. Or maybe I just think that after reading too much of the other thread about frozen objects! 🙂

---

<div class="post-metadata">

**Author:** ![ruro](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ruro/32/7340_2.png) [@ruro](https://discuss.python.org/u/ruro)\
**Post date:** [January 16, 2026, 1:06pm UTC](https://discuss.python.org/t/pep-797-shared-object-proxies/105709/7 "2026-01-16T13:06:39Z")

</div>

> [@ZeroIntensity](#):
>
> Hm, what does “unwrap” mean here? If you mean unpacking the underlying object, then that’s not possible, because it isn’t thread-safe to access the object from another interpreter.

Yes, by “unwrap” I meant to unpack the underlying object. But I am only suggesting to perform this unwrapping, when the `SharedObjectProxy` ends up **back in it’s original interpreter**.

```python
def round_trip(target):
    # target is a SharedObjectProxy here
    return target

interp = interpreters.create()
proxy = interpreters.share(round_trip)
interp.prepare_main(round_trip=proxy)
interp.exec("""
with open("spanish_inquisition.txt") as unshareable:
    result = round_trip(unshareable)

    # result is automatically unwrapped, because the underlying
    # `unshareable` object is owned by the current interpreter
    assert result is unshareable
""")

```

I don’t see, why this wouldn’t be thread-safe. Any reference to `unshareable` is either wrapped in a `SharedObjectProxy` (that grabs the GIL of the correct interpreter when interacting with it) or is unwrapped in the interpreter that owns it (just like any normal python object).

* * *

Can you clarify, which exact methods / properties / behaviours of the underlying object are proxied / emulated. For example:

1. Are ` __hash__ ` and ` __eq__ ` proxied?

2. Does `SharedObjectProxy` proxy the underlying ` __str__ ` and ` __repr__ ` or does it have its own ` __repr__ `?

3. Does `SharedObjectProxy` use ` __instancecheck__ ` / ` __getattribute__ ` magic to pretend to be an instance of the correct class?

* * *

Also, if non-natively-shareable arguments / return values are automatically `share`d, then there is currently an asymmetry between the two sharing methods (for non-natively-shareable objects).

- when explicitly passing objects between interpreters (via `Interpreter.call` or `interpreters.Queue` or similar), you can either

- when implicitly passing objects (as arguments / return values of `share`d functions)

I suppose, you could always manually `pickle.dumps` / `pickle.loads` the objects, but that’s a bit unwieldy. It would be nice to have a way to either “materialize” `SharedObjectProxy` objects, or to disable the automatic `share`ing of arguments / return values.

---

<div class="post-metadata">

**Author:** ![ZeroIntensity](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/zerointensity/32/39016_2.png) [@ZeroIntensity](https://discuss.python.org/u/ZeroIntensity)\
**Post date:** [January 16, 2026, 1:34pm UTC](https://discuss.python.org/t/pep-797-shared-object-proxies/105709/8 "2026-01-16T13:34:14Z")

</div>

> [@BrenBarn](#):
>
> Just a small comment: the specification for `share` should probably explicitly say that it returns the proxy, or else people may think it somehow converts the object into a proxy.

Okay, I’ll include this in my revision.

> [@BrenBarn](#):
>
> Also, does “natively shareable” include SharedObjectProxy objects themselves? As in, what happens if someone calls `share(obj)` where `obj` is already a SharedObjectProxy?
> 
> * * *
> 
> 1. Or maybe I just think that after reading too much of the other thread about frozen objects! 🙂 [↩︎](#footnote-ref-282970-1)

~~Yeah, `share()` on a `SharedObjectProxy` will just recreate the proxy.~~

Edit: I don’t know what I was thinking here. A `SharedObjectProxy` is “natively shareable”, so the proxy is just returned.

> [@ruro](#):
>
> But I am only suggesting to perform this unwrapping, when the `SharedObjectProxy` ends up **back in it’s original interpreter**.

I guess that’s plausible. I feel that it does create some counterintuitive behavior, but practicality beats purity. Can you provide an example where unwrapping in the original interpreter would be useful?

> [@ruro](#):
>
> Are ` __hash__ ` and ` __eq__ ` proxied?

Yes, _every_ method is proxied.

> [@ruro](#):
>
> Does `SharedObjectProxy` proxy the underlying ` __str__ ` and ` __repr__ ` or does it have its own ` __repr__ `?

It copies the ` __repr__ `. I’ll clarify this in the PEP.

> [@ruro](#):
>
> Does `SharedObjectProxy` use ` __instancecheck__ ` / ` __getattribute__ ` magic to pretend to be an instance of the correct class?

Well, there’s not really any magic involved. The code roughly looks like this:

```py
# This code is actually in C, but I'll use Python for example's sake
def __getattribute__ (self, name):
    with self.switch_interpreter():
        attribute = self.value. __getattribute__ (name)
        return share(attribute)

```

To the wrapped value, there’s no discernible difference between calling ` __getattribute__ ` in the main interpreter and calling it from a `SharedObjectProxy`. This applies to pretty much every other dunder method.

> [@ruro](#):
>
> when implicitly passing objects (as arguments / return values of `share`d functions)
> 
> - the objects are implicitly `share`d, sending thin wrappers
> - (no way to send “normally” via the pickle/unpickle method)

Objects that are natively shareable, such as `bytes` objects from `pickle.dumps`, won’t be wrapped in a `SharedObjectProxy`. You can still use the pickle/unpickle method, as far I can tell.

> [@ruro](#):
>
> It would be nice to have a way to either “materialize” `SharedObjectProxy` objects, or to disable the automatic `share`ing of arguments / return values.

I see what you’re getting at, but I’m worried that will get really annoying. For example, if we required return values to explicitly be shareable (either natively or via a proxy), then that would break any use-case involving unshareable attributes, because ` __getattribute__ ` methods won’t return proxy objects. In other words, it will be really frustrating to expect objects to be written in a special way to support shared object proxies.

Stepping back a little bit, I think it would be a good idea to expose a protocol for serializing/deserializing an object for use across multiple interpreters. Eric has already expressed a desire to have this in C, but a Python-level protocol seems much simpler and perhaps feasible for this PEP. If an object implements such a protocol, then `share()` can consider that object “natively shareable” and skip the creation of a proxy.

---

<div class="post-metadata">

**Author:** ![ruro](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ruro/32/7340_2.png) [@ruro](https://discuss.python.org/u/ruro)\
**Post date:** [January 16, 2026, 5:18pm UTC](https://discuss.python.org/t/pep-797-shared-object-proxies/105709/9 "2026-01-16T17:18:19Z")

</div>

> [@ZeroIntensity](#):
>
> Can you provide an example where unwrapping in the original interpreter would be useful?

Well, the first thing that comes to mind is for passing basically any kind of “callback” function that mutates its arguments. But I’ll admit that’s fairly vague. I’ll try thinking of some more practical / concrete examples for this idea and for my concerns about “proxy poisoning/virality” once I am 100% sure that I completely understand the expected semantics of `SharedObjectProxy` objects.

> [@ZeroIntensity](#):
>
> Well, there’s not really any magic involved. The code roughly looks like this:
> 
> ```python
> # This code is actually in C, but I'll use Python for example's sake
> def __getattribute__ (self, name):
> with self.switch_interpreter():
> attribute = self.value. __getattribute__ (name)
> return share(attribute)
> 
> ```
> 
> To the wrapped value, there’s no discernible difference between calling ` __getattribute__ ` in the main interpreter and calling it from a `SharedObjectProxy`. This applies to pretty much every other dunder method.

TBH, it’s been a while since I touched these parts of python, so I might be misremembering, but IIRC overloading just the ` __getattribute__ ` method is not sufficient to forward all dunder methods, because in a lot of cases the dunder methods are looked up on the type of the object, not on its instance.

Case in point:

```python
class Proxy:
    def __init__ (self, wrapped):
        self._wrapped = wrapped

    def __getattribute__ (self, name):
        return getattr(super()._wrapped, name)

hello = Proxy("hello")

# this prints < __main__.Proxy object at 0x...> instead of 'hello'
print(repr(hello))

# this fails with TypeError: unsupported operand type(s) for +: 'Proxy' and 'str'
hello + " string concatenation"

```

This applies to a bunch of other dunder methods too. Actually, I was surprised that `isinstance(hello, str)` works out of the box in this case. Looking at projects like `wrapt`, implementing “full” object proxies is actually quite involved, because the `ObjectProxy` type itself actually has to overload almost all of the dunders, so that `type(hello). __any_dunder__ ` will actually delegate to the wrapped value.

idk, if this also applies to the “real” C implementation, but I think that the actual semantics should be spelled out explicitly, just to avoid any potential confusion.

> [@ZeroIntensity](#):
>
> I see what you’re getting at, but I’m worried that will get really annoying. For example, if we required return values to explicitly be shareable (either natively or via a proxy), then that would break any use-case involving unshareable attributes, because ` __getattribute__ ` methods won’t return proxy objects. In other words, it will be really frustrating to expect objects to be written in a special way to support shared object proxies.

Hmmm… I agree that explicit / opt-in `share`ing of arguments / return values would be very annoying. What about making it opt-out, though? I see two potential ways to implement such an “opt-out” mechanism.

Either we can opt-out **before** the object is passed into / returned from the `share`d function:

```python
def example(argument):
    # here, `argument` is a copied (pickled-unpickled) dict,
    # NOT a SharedObjectProxy, because the caller "opted out"
    value = [1, 2, 3]
    return interpreters.dont_share(value)

interp = interpreters.create()
interp.prepare_main(example=interpreters.share(example))
interp.exec("""
argument = {"key": "value"}
value = example(interpreters.dont_share(argument))
# here, `value` is a copied (pickled-unpickled) list,
# NOT a SharedObjectProxy, because the example function "opted out"
""")

```

Honestly, this option is quite ugly and I am mostly including it here for completeness sake. It relies on the caller knowing that it is interacting with a `share`d function and presumably `dont_share` would also need to return some kind of special “marked” / “unshared” object.

Or we can “opt-out” **after** the proxy object is already in another interpreter:

```python
def example(argument):
    local_argument = interpreters.materialize(argument)
    return [1, 2, 3]

interp = interpreters.create()
interp.prepare_main(example=interpreters.share(example))
interp.exec("""
value = example({"key": "value"})
local_value = interpreters.materialize(value)
""")

# interpreters.materialize(x) is rougly equivalent to
def materialize(x):
    if type(x) != SharedObjectProxy:
        return x

    # the code below should basically do whatever the current "normal"
    # pickle-based sharing does when sending data between interpreters
    with x.switch_interpreter():
        wrapped = x._wrapped
        data = pickle.dumps(wrapped)
    return pickle.loads(data)

```

IMHO, the second option is significantly more elegant. The only downside of this option is that it still has an overhead for initially creating and sending the shared object (on top of the “normal” overhead of pickling / unpickling).

> [@ZeroIntensity](#):
>
> Objects that are natively shareable, such as `bytes` objects from `pickle.dumps`, won’t be wrapped in a `SharedObjectProxy`. You can still use the pickle/unpickle method, as far I can tell.

Yes. I am essentially proposing to add a convenient wrapper around `pickle.loads(pickle.dumps(x))` with a few asterisks:

- no-op for objects that are natively shareable (or were already materialized)

- the actual implementation of `interpreters.materialize` might be slightly more efficient than the “naive” `loads(dumps(x))`

- the “naive” implementation actually has a subtle bug, because it uses “`pickle.dumps`” from the **current** interpreter, rather than from the interpreter that owns `x._wrapped` (this might have some performance implications and also might produce different results if `copyreg` or monkey-patching of the `pickle` module was used)

> [@ZeroIntensity](#):
>
> Stepping back a little bit, I think it would be a good idea to expose a protocol for serializing/deserializing an object for use across multiple interpreters. Eric has already expressed a desire to have this in C, but a Python-level protocol seems much simpler and perhaps feasible for this PEP. If an object implements such a protocol, then `share()` can consider that object “natively shareable” and skip the creation of a proxy.

Implementing this at the type/class/protocol level unfortunately means that it can’t be easily applied to third-party (or even builtin) types. What if I have a `share`d function/method (that I want to be executed in the main interpreter), but I need to pass it a long list/dict/set with data to be processed.

In this case, it would make more sense to copy this list/dict/set once using the “normal” sharing mechanism instead of switching between interpreters and creating further `SharedObjectProxy` objects on every operation in the main interpreter.

---

<div class="post-metadata">

**Author:** ![ZeroIntensity](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/zerointensity/32/39016_2.png) [@ZeroIntensity](https://discuss.python.org/u/ZeroIntensity)\
**Post date:** [January 16, 2026, 6:15pm UTC](https://discuss.python.org/t/pep-797-shared-object-proxies/105709/10 "2026-01-16T18:15:07Z")

</div>

> [@ruro](#):
>
> TBH, it’s been a while since I touched these parts of python, so I might be misremembering, but IIRC overloading just the ` __getattribute__ ` method is not sufficient to forward all dunder methods, because in a lot of cases the dunder methods are looked up on the type of the object, not on its instance.

Oh, I was using ` __getattribute__ ` as an example. This exact sort of function is replicated for every dunder method. For example, the ` __add__ ` method is roughly equivalent to:

```py
def __add__ (self, other):
    with self.switch_interpreter():
        result = self.value. __add__ (share(other))
        return share(result)

```

> [@ruro](#):
>
> Or we can “opt-out” **after** the proxy object is already in another interpreter

I still feel that this is going to become a huge headache, because now you need `materialize` calls everywhere. For example, to simply call a method on an object and get the result, you need three `materialize` calls:

```py
# Materialize the proxy
proxy = materialize(shared)
# Materialize the method descriptor
method = materialize(shared.method)
# Materialize the result
result = materialize(method())

```

This is an awful lot of boilerplate compared to `result = shared.method()`, and I’m also struggling to see the benefit of having a “materialize” proxy everywhere vs an on object proxy everywhere.

> [@ruro](#):
>
> Implementing this at the type/class/protocol level unfortunately means that it can’t be easily applied to third-party (or even builtin) types.

You would just ask the maintainer(s) nicely to add support for the protocol, or otherwise inherit from the type yourself and implement it. This problem would apply to `pickle` too, so I don’t think it’s too much of an issue in practice.

> [@ruro](#):
>
> What if I have a `share`d function/method (that I want to be executed in the main interpreter), but I need to pass it a long list/dict/set with data to be processed.

As an alternative to something like `materialize` (though I don’t understand how that would solve this issue, since the object would still be an “unmaterialized” proxy object rather the list), we could add something like a `PickleWrapper` to explicitly opt-in to sharing via `pickle`.

For example:

```py
def example(data):
    for item in data:
        print(data)

shared = share(example)
# Pretend this is in another interpreter
shared(PickleWrapper([1, 2, 3]))

```

`PickleWrapper` will be natively shareable (and thus won’t trigger the creation of a proxy) and will simply calls `loads`/`dumps` as part of its crossinterpreter protocol.

---

<div class="post-metadata">

**Author:** ![ruro](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ruro/32/7340_2.png) [@ruro](https://discuss.python.org/u/ruro)\
**Post date:** [January 16, 2026, 7:55pm UTC](https://discuss.python.org/t/pep-797-shared-object-proxies/105709/11 "2026-01-16T19:55:21Z")

</div>

> [@ZeroIntensity](#):
>
> I still feel that this is going to become a huge headache, because now you need `materialize` calls everywhere.

I think, you completely misunderstood what I am proposing. `interpreters.materialize` is an opt- **_out_** mechanism. So the arguments / return values of `share`d functions will still be implicitly `share`d by default.

`interpreters.materialize` is supposed to take existing `SharedObjectProxy` objects and return regular non-proxied python object (by “sending” them to the current interpreter “normally” like via `Interpreter.call` or `interpreters.Queue` - either by pickling/unpickling or “natively sharing” them).

So, for example

```python
def do_work_in_main_interpreter(shared_from_subinterpreter):
    # The `shared_from_subinterpreter` object is owned by the
    # subinterpreter and it might not even be pickleable
    assert type(shared_from_subinterpreter) == SharedObjectProxy
    data = shared_from_subinterpreter.method_that_returns_a_huge_list()

    # `data` is a proxy, the real "huge list" is owned by the sub-interpreter
    assert type(data) == SharedObjectProxy
    assert type(data[0]) == SharedObjectProxy

    # You can interact with this proxy as expected, but it may be slow
    # due to all the interpreter switching and SharedObjectProxy creation.
    print(data[0] + data[1])

    # Now, we send the data from the sub-interpreter to the main interpreter.
    # Of course, the "huge list" must be pickleable or natively shareable.
    local_data = interpreters.materialize(data)

    # `local_data` is just a list of normal python objects
    assert type(local_data) == list
    # Working with local_data (or its elements) is now blazingly fast (tm),
    # because it's just a normal python list of normal python objects,
    # owned by the current interpreter.
    for element in local_data:
        ...

interp = interpreters.create()
proxy = interpreters.share(do_work_in_main_interpreter)
interp.prepare_main(do_work_in_main_interpreter=proxy)
# Sub-interpreter creates an unshareable class.
# Calling the shared `do_work_in_main_interpreter` function
# implicitly calls `interpreter.share()` on its arguments
interp.exec("""
unshareable = MyUnshareableClass(...)
do_work_in_main_interpreter(unshareable)
""")

```

> [@ZeroIntensity](#):
>
> we could add something like a `PickleWrapper` to explicitly opt-in to sharing via `pickle`

Your `PickleWrapper` is what I called `interpreters.dont_share` (the “opt-out **before** ” option). But I still think that the `interpreters.materialize` option has better UX in this case.

---

<div class="post-metadata">

**Author:** ![ruro](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ruro/32/7340_2.png) [@ruro](https://discuss.python.org/u/ruro)\
**Post date:** [January 16, 2026, 9:54pm UTC](https://discuss.python.org/t/pep-797-shared-object-proxies/105709/12 "2026-01-16T21:54:11Z")

</div>

I think there is a terminology issue in the current wording of this PEP. Time for some bikeshedding.

Here’s the problem. Before this PEP, there were 2 ways to “use python objects in multiple interpreters”:

1. Pass a “natively shareable” object to `Interpreter.call` or similar
2. Pass a pickleable object to `Interpreter.call` or similar

The [current python docs](https://docs.python.org/3/library/concurrent.interpreters.html#interp-object-sharing) and the original [PEP 734 Multiple Interpreters in the Stdlib](https://peps.python.org/pep-0734/#shareable-objects) called these two ways “sharing” and the relevant objects “shareable”. Using that terminology, “to share an object” meant “to make it available to another interpreter” and “shareable” objects were those that can be passed to methods like `Interpreter.call` and `Queue.put`.

Now, PEP 797 introduces a third way of using python objects across multiple interpreters:

1. Pass a `SharedObjectProxy`-wrapped object to `Interpreter.call` or similar

In practice, these 3 approaches are quite different, but right now they all are called “sharing”. As a consequence, even the PEP itself often ends up using terms like “shared”, “shareable”, “unshareable” and “to share” in inconsistent or contradictory ways.

Sometimes “share” is used to refer (exclusively) to the first 2 kinds of sharing. For example, the PEP claims that “Many Objects Cannot be Shared Between Subinterpreters” (and gives an example of file objects returned by `open()`).

But then in the main body of the PEP, “share” is used to refer (exclusively) to the new third kind of sharing. The name of the new `interpreters.share` function implies that it shares objects, but that’s not what it actually does. It doesn’t share objects, it makes them share_ **able** _. Recall that “to share” was supposed to mean “to make available to another interpreter”, but `interpreter.share` by itself only prepares a proxy that _ **might be** _ eventually shared.

Additionally, even if it was called something like `interpreters.make_shareable`, it still wouldn’t be accurate for what it does, because calling it with a pickleable object (which is supposed to already be “shareable” according to the established terminology) “changes” how it behaves in terms of mutability. Why is calling `share`/`make_shareable` on an already shareable object change its sharing behaviour from copy/value semantics to reference semantics? To be clear, IMHO, the problem is not in the behaviour of the function, but in its name.

All of this makes talking about these 3 different “sharing” mechanisms **_extremely_** annoying. You basically have to always clarify whether you mean “natively shared”, “non-natively shared” or “shared via proxy”. Without these clarifications, you either end up making contradictory statements (the very first code example in the PEP talks about how to “share” an “unshareable” object) or meaningless statements (literally all objects are “shareable” now, so saying that an object is “shareable” doesn’t convey any information).

I think that it might have been a mistake to call the 2 original PEP 734 ways of passing objects between interpreters “sharing”. It really should have been called “sending” or something, but unless we are prepared to change the terminology that was established in PEP 734, we are stuck with the current meaning of “share”.

Here’s a proposal to change the wording of this PEP to (hopefully) make it a bit less contradictory:

1. Let the words “shareable” / “unshareable” keep their PEP 734 meaning (“shareable” means either “natively shareable” or “non-natively shareable” aka pickleable). File objects and other non-pickleable objects are still considered “unshareable”.

2. Change the PEP name from “Shar_ **ed** _ Object Proxies” to “Share_ **able** _ Object Proxies” (or maybe even just “Shareable Proxies”). The implication is that the Proxies themselves are shareable, even if the objects that they are wrapping might not be.

3. Change the name of the `interpreters.share` function to `interpreters.proxy` or something similar. Some other candidates might be `interpreters.make_proxy`, `interpreters.wrap`, `interpreters.delegate`, `interpreters.expose` or `interpreters.publish`.

---

<div class="post-metadata">

**Author:** ![ZeroIntensity](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/zerointensity/32/39016_2.png) [@ZeroIntensity](https://discuss.python.org/u/ZeroIntensity)\
**Post date:** [January 16, 2026, 10:28pm UTC](https://discuss.python.org/t/pep-797-shared-object-proxies/105709/13 "2026-01-16T22:28:58Z")

</div>

> [@ruro](#):
>
> Your `PickleWrapper` is what I called `interpreters.dont_share` (the “opt-out **before** ” option). But I still think that the `interpreters.materialize` option has better UX in this case.

`interpreters.dont_share` takes an _existing_ `SharedObjectProxy` and creates a copy of the wrapped object – `PickleWrapper` avoids the creation entirely. I’m not a big fan of giving proxy objects any special behavior to reconstruct an object; that’s something `concurrent.interpreters` should have a mechanism for, not a proxy.

> [@ruro](#):
>
> Pass a `SharedObjectProxy`-wrapped object to `Interpreter.call` or similar

I think there’s some confusion about what a `SharedObjectProxy` is. An object proxy _is_ natively shareable. There’s no real difference between sharing a `str` and a `SharedObjectProxy`, except that the latter is a more complicated object.

This basically applies to the rest of your post; there’s no point in any distinction between “native sharing” and “proxy sharing”, because they’re fundamentally the same thing and use the same protocol.

I’m open to renaming `share`. Perhaps `ensure_shareable`?

> [@ruro](#):
>
> Change the PEP name from “Shar_ **ed** _ Object Proxies” to “Share_ **able** _ Object Proxies” (or maybe even just “Shareable Proxies”). The implication is that the Proxies themselves are shareable, even if the objects that they are wrapping might not be.

I think it’s really nitpicky to say that the wrapped object might not be “shared”, because I can’t think of a use case where one would want to create a proxy without ever sending it to another interpreter. But, if others express support for this idea, I’ll defer.

---

<div class="post-metadata">

**Author:** ![ruro](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ruro/32/7340_2.png) [@ruro](https://discuss.python.org/u/ruro)\
**Post date:** [January 16, 2026, 11:42pm UTC](https://discuss.python.org/t/pep-797-shared-object-proxies/105709/14 "2026-01-16T23:42:00Z")

</div>

> [@ZeroIntensity](#):
>
> `interpreters.dont_share` takes an _existing_ `SharedObjectProxy` and creates a copy of the wrapped object

Uh… No it doesn’t? At least not the `interpreters.dont_share` that I suggested. I don’t want to be rude, but please consider re-reading my comment. This is the second time you are making incorrect assertions about what I said in that comment.

I proposed two opt-out mechanisms:

1. `interpreters.dont_share`, which **prevents** the creation of a `SharedObjectProxy`. It is intended to be called **before** using the object in a context that would normally create a proxy (ie passing it as an argument or returning it from a function).
2. `interpreters.materialize`, which coerces an already existing `SharedObjectProxy` to a “proper” local object by doing _approximately_ what `loads(dumps(x))` would do (with a few caveats).

> [@ZeroIntensity](#):
>
> I think there’s some confusion about what a `SharedObjectProxy` is. An object proxy _is_ natively shareable. There’s no real difference between sharing a `str` and a `SharedObjectProxy`, except that the latter is a more complicated object.

No, no. I understand that `SharedObjectProxy` is natively shareable.

I am probably being extremely pedantic, but I really think that establishing these distinctions in terminology is extremely important, if we don’t want to end up with ambiguous / contradictory terminology.

Consider this piece of code

```python
foo = open("somefile")
proxy = interpreters.share(foo) # 2
interp.prep_main(whatever=proxy) # 3

```

1. The name of the `interpreters.share` function and the wording of the PEP makes it seem like this code “shares” the `foo` object. It doesn’t. It shares the `proxy` object.

2. The name of the `SharedObjectProxy` class and the wording of the PEP makes it seem like the “sharing” occurs on line 2 (we called the `share` function and got a `SharedSomething` object in return, so it seems like “sharing” was performed).

Again, sorry for the pedantry.

> [@ZeroIntensity](#):
>
> Perhaps `ensure_shareable`?

Calling the function `ensure_shareable` also doesn’t quite fix the issue, because in this example

```python
list_of_ints = [1, 2, 3]
proxy = interpreters.ensure_shareable(list_of_ints)

```

`list_of_ints` is already shareable (because it’s pickleable). So going by this name, one might expect that `ensure_shareable` should be a no-op in this case.

I guess, it could be called something like `ensure_natively_shareable`. But that’s just horrible.

Actually, I think I just had a great idea! Instead of “Object Proxies”, we can call the new objects “References” (`ShareableReference`) and then name the function `interpreters.share_reference`. These names even automatically imply that this new method of sharing uses reference semantics instead of copy/value semantics.

Edit: Although this still kind of implies that `share_reference` is the function that performs the sharing. Damn.

Edit 2: Ah! I think I got it. How about a `interpreters.make_reference` function that returns a `ShareableReference`.

---

<div class="post-metadata">

**Author:** ![ZeroIntensity](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/zerointensity/32/39016_2.png) [@ZeroIntensity](https://discuss.python.org/u/ZeroIntensity)\
**Post date:** [January 17, 2026, 12:20am UTC](https://discuss.python.org/t/pep-797-shared-object-proxies/105709/15 "2026-01-17T00:20:12Z")

</div>

> [@ruro](#):
>
> Uh… No it doesn’t? At least not the `interpreters.dont_share` that I suggested. I don’t want to be rude, but please consider re-reading my comment. This is the second time you are making incorrect assertions about what I said in that comment.

Yeah, I misunderstood, my bad! I’ll try to pay closer attention in the future. But, please try to be a little more direct in your examples. I think I got tripped up by seeing `dont_share` in both interpreters, so I assumed there was some sort of unwrapping going on; it’d have been enough to see the call in only one interpreter to understand the behavior.

> [@ruro](#):
>
> I proposed two opt-out mechanisms:

Okay, I’m personally much more fond of `dont_share`/`PickleWrapper`, because I don’t think the proxy should have anything to do with manipulating the wrapped object. (The proxy is supposed to feel like it _is_ the wrapped object, and adding a mechanism to magically invoke pickle hurts that intuition.)

> [@ruro](#):
>
> - In fact, `foo` is an open file, which is unshareable, so it can’t be shared (this was true before this PEP and is still true after it). Only the `proxy` object is shareable.

I’m not entirely disagreeing, but again, it’s not really a useful distinction to make because the underlying object will technically be “shared” anyway. (Here with the terminology again; should the object be considered “shared”, or only the proxy? The proxy is supposed to be transparent, so one could argue the former, but physically it’s only the latter. I think this is sort of gray area at the moment.)

I’m not opposed to renaming to `Shareable`, but I’d like to hear someone else voice the same opinion, because I’ve been using `SharedObjectProxy` for however long I’ve worked on this proposal and nobody else has had an issue with it.

> [@ruro](#):
>
> `list_of_ints` is already shareable (because it’s pickleable). So going by this name, one might expect that `ensure_shareable` should be a no-op in this case.

It’s not clear to me whether “pickleable” implies “shareable”. PEP 734 uses that terminology, but [the documentation](https://docs.python.org/3/library/concurrent.interpreters.html#sharing-objects) does not, as pickleable objects are referred to as being “copied” while natively shareable objects use the term “share”.

This is also reflected in the `_interpreters` module:

```pycon
>>> import _interpreters
>>> list_of_ints = [1, 2, 3]
>>> _interpreters.is_shareable(list_of_ints)
False

```

> [@ruro](#):
>
> Actually, I think I just had a great idea! Instead of “Object Proxies”, we can call the new objects “References” (`ShareableReference`) and then name the function `interpreters.share_reference`. These names even automatically imply that this new method of sharing uses reference semantics instead of copy/value semantics.

I see where you’re coming from, but “reference” is already a term in Python that means something else. In C, a reference means `PyObject *`, which isn’t shareable unless the object is immutable and immortal (static types, for example, are shareable references). At the C level, a `PyObject *` pointing to an instance of a `SharedObjectProxy` is not shareable, which is confusing.

---

<div class="post-metadata">

**Author:** ![ruro](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ruro/32/7340_2.png) [@ruro](https://discuss.python.org/u/ruro)\
**Post date:** [January 17, 2026, 8:00pm UTC](https://discuss.python.org/t/pep-797-shared-object-proxies/105709/16 "2026-01-17T20:00:21Z")

</div>

> [@ZeroIntensity](#):
>
> It’s not clear to me whether “pickleable” implies “shareable”. PEP 734 uses that terminology, but [the documentation](https://docs.python.org/3/library/concurrent.interpreters.html#sharing-objects) does not, as pickleable objects are referred to as being “copied” while natively shareable objects use the term “share”.

Oh, wow. I originally learned about interpreters from the PEP, so I have just assumed that the docs kept using the same terminology. But you’re totally right. Now that I am taking a closer look, the current docs consistently use “shared and/or copied” where the PEP used just “shared”. The only exception is the `NotShareableError` exception (ha!), which still kind of uses the old meaning of “shareable”.

Now that I noticed this change in terminology, I have much less of a problem with calling the proxy objects `Shared` and the function - `share`. It might still be benifitial to call the function `ensure_shareable`, but that’s much less of a concern if pickleable objects are “copiable”, not “shareable”. Actually, excluding pickleable objects from the “shareable” category makes much more sense.

Using the new terminology, we can just say that “sharing” always follows reference semantics, because you can only “share” immutable types (`None`, `bool`, `bytes`, `str`, `int`, `float`, `tuple`) and “special” types that explicitly support reference-style mutability (`memoryview`, `Queue` and now `SharedObjectProxy`). All other objects can’t be shared directly, but some of them can be “copied” (if they are pickleable) and you can “indirectly share” any object by wrapping it in a proxy.

_ **Consider my complaints about naming withdrawn.** _

---

<div class="post-metadata">

**Author:** ![steve.dower](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/steve.dower/32/56_2.png) [@steve.dower](https://discuss.python.org/u/steve.dower)\
**Post date:** [January 19, 2026, 2:20pm UTC](https://discuss.python.org/t/pep-797-shared-object-proxies/105709/17 "2026-01-19T14:20:20Z")

</div>

For what it’s worth (vaguely referring back to the fine-grained opt-out mechanisms for this particular proxy type), I’m very much in favour of this being a simple fallback proxy, and if you want to opt out, you do it by implementing a more specific proxy for your type.

As it stands, I’m happy with this PEP. The one thing I _think_ we could add here is a protocol for `.share()` so that types can provide their own proxy. e.g. if the argument provides `. __proxy__ ()` then that gets called and the result must be natively shareable (and natively shareable objects can be defined as “returns `self` from `. __proxy__ ()`”, kind of like what we do for iterators). This gives us space to grow in the future, and hopefully provides an obvious path for how to handle opt-outs.

We might also need something like the pickle shareable as well, to make this a _usefully_ functional proposal. Then custom proxy implementations are probably going to specify a type that can be discovered and instantiated in the other interpreter with the (natively shareable) arguments necessary to call back into the original.

Thinking about that now, it may mean we also need a way to get the fallback proxy type when ` __proxy__ ()` is overridden - perhaps instantiating `SharedObjectProxy` directly needs to be allowed. I’m thinking of a case like this:

```python
class MyType:
    def __init__ (self, a, b, c):
        self.a, self.b, self.c = a, b, c

    class _Proxy:
        def __init__ (self, remote_self):
            self.remote_self = remote_self
            # 'a' and 'b' are assumed constant, so copy them once
            self.a, self.b = self.remote_self.a, self.remote_self.b

        # Always query remote object for 'c', and disallow setting
        @property
        def c(self):
            return self.remote_self.c

    def __proxy__ (self):
        return SomeProxyTypeThatNeedsAName(MyType._Proxy, SharedObjectProxy(self))

```

IMHO, it’s that very last function that needs figuring out before object proxies are going to be broadly useful. We can probably do the bare basics in this PEP, since it’s introducing the `.share()` function.

But overall, +1 on what’s here. This is a critical building block.

---

<div class="post-metadata">

**Author:** ![ZeroIntensity](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/zerointensity/32/39016_2.png) [@ZeroIntensity](https://discuss.python.org/u/ZeroIntensity)\
**Post date:** [January 19, 2026, 3:31pm UTC](https://discuss.python.org/t/pep-797-shared-object-proxies/105709/18 "2026-01-19T15:31:50Z")

</div>

> [@steve.dower](#):
>
> it stands, I’m happy with this PEP. The one thing I _think_ we could add here is a protocol for `.share()` so that types can provide their own proxy. e.g. if the argument provides `. __proxy__ ()` then that gets called and the result must be natively shareable

I like that idea a lot and I think it makes sense to include it in this proposal. Bikeshedding: It’s probably better to call it ` __share__ `, since the result of ` __proxy__ ` might not actually be a proxy.

> [@steve.dower](#):
>
> Thinking about that now, it may mean we also need a way to get the fallback proxy type when ` __proxy__ ()` is overridden - perhaps instantiating `SharedObjectProxy` directly needs to be allowed.

Agreed as well – I initially disallowed it because it would be no different than `share()`, but it makes sense to allow it if that’s no longer true.

---

<div class="post-metadata">

**Author:** ![ZeroIntensity](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/zerointensity/32/39016_2.png) [@ZeroIntensity](https://discuss.python.org/u/ZeroIntensity)\
**Post date:** [January 20, 2026, 3:54pm UTC](https://discuss.python.org/t/pep-797-shared-object-proxies/105709/19 "2026-01-20T15:54:47Z")

</div>

I have [updated](https://github.com/python/peps/pull/4786) the PEP. The proposal now has the ` __share__ ` method that was discussed above.

---

<div class="post-metadata">

**Author:** ![jrfk](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jrfk/32/35074_2.png) [@jrfk](https://discuss.python.org/u/jrfk)\
**Post date:** [April 4, 2026, 4:51pm UTC](https://discuss.python.org/t/pep-797-shared-object-proxies/105709/20 "2026-04-04T16:51:35Z")

</div>

@ZeroIntensity Hi, I’ve been following this PEP with great interest. I opened an issue to optimize the XIData pickle fallback path ([#148072](https://github.com/python/cpython/issues/148072)) — the codepath that `InterpreterPoolExecutor` currently uses for mutable containers like `list` and `dict`. A small per-interpreter cache for `pickle.dumps`/`pickle.loads` references gives a 1.7–3.3x speedup, and I have a draft PR ready.

I wanted to mention it here since this PEP is the real solution for mutable object sharing. Until it lands, the pickle fallback is what users hit today, so this seemed worth addressing in the meantime. If the core team thinks it’s not worth the churn, I’m happy to close it.

Thanks for the great work on this PEP — the ` __share__ ` protocol feels like the right approach.

[Next page](https://discuss.python.org/t/pep-797-shared-object-proxies/105709.md?page=2)
