Skip to content

WeakSet is not pickleable #74876

Open
Open

Description

@serhiy-storchaka
BPO 30691
Nosy @Yhg1s, @birkenfeld, @rhettinger, @pitrou, @serhiy-storchaka

Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

Show more details

GitHub fields:

assignee = None
closed_at = None
created_at = <Date 2017-06-17.18:54:10.235>
labels = ['library']
title = 'WeakSet is not pickleable'
updated_at = <Date 2017-06-18.06:09:54.301>
user = 'https://github.com/serhiy-storchaka'

bugs.python.org fields:

activity = <Date 2017-06-18.06:09:54.301>
actor = 'rhettinger'
assignee = 'none'
closed = False
closed_date = None
closer = None
components = ['Library (Lib)']
creation = <Date 2017-06-17.18:54:10.235>
creator = 'serhiy.storchaka'
dependencies = []
files = []
hgrepos = []
issue_num = 30691
keywords = []
message_count = 4.0
messages = ['296243', '296256', '296262', '296264']
nosy_count = 5.0
nosy_names = ['twouters', 'georg.brandl', 'rhettinger', 'pitrou', 'serhiy.storchaka']
pr_nums = []
priority = 'normal'
resolution = None
stage = None
status = 'open'
superseder = None
type = None
url = 'https://bugs.python.org/issue30691'
versions = []

Linked PRs

Activity

  1. serhiy-storchaka commented on Jun 17, 2017

    @serhiy-storchaka
    MemberAuthor

    WeakSet contains the __reduce__ method, but it isn't pickleable (and never was), because the pickle state contains the value of the __dict__ dict attribute which contains a reference to unpickleable local function _remove().

    >>> pickle.dumps(weakref.WeakSet())
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: Can't pickle local object 'WeakSet.__init__.<locals>._remove'

    I wondering whether WeakSet should be made pickleable or the __reduce__ method should be removed. __reduce__() can be used also for copying, but there are no tests for this feature.

  2. added
    stdlibStandard Library Python modules in the Lib/ directory
    on Jun 17, 2017
  3. rhettinger commented on Jun 18, 2017

    @rhettinger
    Contributor

    I wondering whether WeakSet should be made pickleable or
    the __reduce__ method should be removed.

    When considering whether to remove a method from long published code, if the method isn't broken, our guidance should come from whether user's have actually taken advantage of __reduce__. Determining that answer involves searching published code (Google's code search used to be a good tool for this, now we've got Github's code search tools).

    In general, the burden is high for removing an existing feature (even if untested); otherwise, we risk breaking people's code for no good reason other than the joy that comes with churning code to solve an invented problem (one that has never arisen in real code and has never been reported by an actual user).

    When considering whether to add pickle support, the bar is much lower. Roughly the question is amounts to balancing the potential benefits (whether someone might need to pickle a weakset someday even though we have no evidence that anyone has ever wanted this) versus the costs (risk of introducing bugs, creating cross-version incompatabilities, increasing future maintenance costs, increasing the total code volume, etc).

    If adding pickling capability is easy and clean, then it seems reasonable. On the other hand, if it is even slightly tricky, we should skip adding a feature that no one has ever asked for. The
    weak reference containers have long been a source of bugs, some of which were challenging to fix.

  4. serhiy-storchaka commented on Jun 18, 2017

    @serhiy-storchaka
    MemberAuthor

    The method is broken (and always was). Pickling doesn't work because the state contains unpickleable object. Copying works incorrectly, since the pickle state contains references to the original WeakSet and it overrides the __dict__ of the copy, making its state inconsistent. If the purpose of implementing the __reduce__ method was the support of the copying, the __reduce__ method should be fixed or the copying should be implemented with implementing the __copy__ and __deepcopy__ methods.

    However there is a subtle moment with pickling WeakSet (as well as with pickling weakref.proxy, see bpo-18068). The problem is that if you pickled a WeakSet, but not hard references to its items, the items will be disappeared just after unpickling, since they have no hard references. This may confuse. If you pickle also hard refereces to the items, a WeakSet can be pickled and unpickled as expected (after fixing __reduce__).

  5. rhettinger commented on Jun 18, 2017

    @rhettinger
    Contributor

    If you pickle also hard refereces to the items,
    a WeakSet can be pickled and unpickled as expected (after
    fixing __reduce__).

    That seems contrary to the original intent of a WeakSet. I recommend not going down the path of adding pickle support.

    If copying is also broken, then it is reasonable to either fix it or remove it depending on whether a fix is straight-forward.

  6. transferred this issue fromon Apr 10, 2022
  7. serhiy-storchaka commented on Jun 11, 2022

    @serhiy-storchaka
    MemberAuthor

    Copying is broken too. But instead of failing it produces incorrect result: two WeakSets share the same content.

    >>> import weakref, copy
    >>> class A(float): pass
    ... 
    >>> s = {A(1)}
    >>> ws = weakref.WeakSet(s)
    >>> ws2 = copy.copy(ws)
    >>> list(ws)
    [1.0]
    >>> list(ws2)
    [1.0]
    >>> x = A(2)
    >>> ws.add(x)
    >>> list(ws)
    [1.0, 2.0]
    >>> list(ws2)
    [1.0, 2.0]
  8. added
    type-bugAn unexpected behavior, bug, or error
    3.11only security fixes
    3.12only security fixes
    on Jun 11, 2022
  9. added a commit that references this issue on Jun 11, 2022
  10. webberfc commented on Jun 19, 2023

    @webberfc

    Hello, I am using iPyParallel (or rather, attempting to) to support parallelizing a complex task. I have successfully used iPyParallel elsewhere in another project. Unfortunately, in this project, I have this error:

    File "C:\Users\Simba\AppData\Local\Programs\Python\Python310\lib\site-packages\ipyparallel\client\view.py", line 334, in map_async
    return self.map(f, *sequences, **kwargs)
    File "C:\Users\Simba\AppData\Local\Programs\Python\Python310\lib\site-packages\decorator.py", line 232, in fun
    return caller(func, *(extras + args), **kw)
    File "C:\Users\Simba\AppData\Local\Programs\Python\Python310\lib\site-packages\ipyparallel\client\view.py", line 53, in sync_results
    ret = f(self, *args, **kwargs)
    File "C:\Users\Simba\AppData\Local\Programs\Python\Python310\lib\site-packages\decorator.py", line 232, in fun
    return caller(func, *(extras + args), **kw)
    File "C:\Users\Simba\AppData\Local\Programs\Python\Python310\lib\site-packages\ipyparallel\client\view.py", line 37, in save_ids
    ret = f(self, *args, **kwargs)
    File "C:\Users\Simba\AppData\Local\Programs\Python\Python310\lib\site-packages\ipyparallel\client\view.py", line 1338, in map
    return pf.map(*sequences)
    File "C:\Users\Simba\AppData\Local\Programs\Python\Python310\lib\site-packages\ipyparallel\client\remotefunction.py", line 332, in map
    return self(*sequences, __ipp_mapping=True)
    File "C:\Users\Simba\AppData\Local\Programs\Python\Python310\lib\site-packages\decorator.py", line 232, in fun
    return caller(func, *(extras + args), **kw)
    File "C:\Users\Simba\AppData\Local\Programs\Python\Python310\lib\site-packages\ipyparallel\client\remotefunction.py", line 81, in sync_view_results
    return f(self, *args, **kwargs)
    File "C:\Users\Simba\AppData\Local\Programs\Python\Python310\lib\site-packages\ipyparallel\client\remotefunction.py", line 272, in call
    pf = PrePickled(self.func)
    File "C:\Users\Simba\AppData\Local\Programs\Python\Python310\lib\site-packages\ipyparallel\serialize\serialize.py", line 46, in init
    self.buffers = serialize_object(obj)
    File "C:\Users\Simba\AppData\Local\Programs\Python\Python310\lib\site-packages\ipyparallel\serialize\serialize.py", line 119, in serialize_object
    buffers.insert(0, pickle.dumps(cobj, PICKLE_PROTOCOL))
    AttributeError: Can't pickle local object 'WeakSet.init.._remove'

    I have tried to identify what the object is so I can come up with a workaround (iPyParallel allows you to instantiate remotely so pickling is not required)... iPyParallel provides two workarounds to help with pickling, being Dill and CloudPickle. Dill complains:
    TypeError: no default __reduce__ due to non-trivial __cinit__
    And CloudPickle is complaining about a thread, which I will have to look into, I thought this version was a single thread version up until the call to map_async.

    Per the above request:

    If adding pickling capability is easy and clean, then it seems reasonable. On the other hand, if it is even slightly tricky, we should skip adding a feature that no one has ever asked for.

    I am formally requesting this :) Thanks for considering! Meanwhile I'll try to ID the object and see if I can remove it...

  11. hugovk commented on Apr 26, 2025

    @hugovk
    Member

    How does PR #93732 look for this?

  12. serhiy-storchaka commented on Jul 17, 2026

    @serhiy-storchaka
    MemberAuthor

    My old #93732 added support for pickling WeakSet. I wrote also extended patch to support other types of weak containers. They work fine, but with caveat -- if user pickles weak references without strong references in the same pickle, they will became dead immediately after unpickling. Weak collections became empty.

    #153818 solves this problem by making the pickler to trace weak references. If they are not backed by strong references, a PicklingError is raised at the end of pickling, so such mistake will not be left unnoticed. Only weakref.ref is supported natively in the pickle module, all other types are expressed in terms of weakref.ref. This does not need introducing a new opcode and bumping protocol. But this is too large change for backporting. We should just raise explicit errors in old Python versions.

    The current version of PR uses private functions to reconstruct. I wonder if it is worth to add public alternative constructors like WeakRef.fromrefs().

  13. added a commit that references this issue on Oct 21, 2026
  14. serhiy-storchaka commented on Oct 21, 2026

    @serhiy-storchaka
    MemberAuthor

    I did not merge #153818 myself, because I was not completely satisfied by it. It depends on new private API, and the pickled data is not optimal and cannot be read in older versions. I have other idea and am going to try it.

  15. added a commit that references this issue on Oct 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.11only security fixes3.12only security fixesstdlibStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or error

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions