Why do set and frozenset implement __reduce__?

As reported in this StackOverflow question, set, along with frozenset, seem to be the only built-in data types to override object.__reduce__:

>>> set.__reduce__
<method '__reduce__' of 'set' objects>
>>> frozenset.__reduce__
<method '__reduce__' of 'frozenset' objects>
>>> dict.__reduce__
<method '__reduce__' of 'object' objects>
>>> list.__reduce__
<method '__reduce__' of 'object' objects>
>>> tuple.__reduce__
<method '__reduce__' of 'object' objects>
>>> str.__reduce__
<method '__reduce__' of 'object' objects>

This causes additional attributes not to be included when re-instantiated by deepcopying an instance of a subclass of set:

import copy

class Set(set):
    def __init__(self, iterable, name):
        self.name = name

foo = copy.deepcopy(Set([1, 2, 3], 'foo'))

which produces:

Traceback (most recent call last):
  File "/ATO/code", line 7, in <module>
    foo = copy.deepcopy(Set([1, 2, 3], 'foo'))
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/copy.py", line 162, in deepcopy
    y = _reconstruct(x, memo, *rv)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/copy.py", line 253, in _reconstruct
    y = func(*args)
        ^^^^^^^^^^^
TypeError: Set.__init__() missing 1 required positional argument: 'name'

Is there a good historical reason why __reduce__ is implemented for set and frozenset?

Otherwise, a fix to the issue above appears to be simply reverting set.__reduce__ to object.__reduce__:

class Set(set):
    def __init__(self, iterable, name):
        self.name = name

    def __reduce__(self):
        return object.__reduce__(self)

foo = copy.deepcopy(Set([1, 2, 3], 'foo'))
print(foo.name) # outputs foo
1 Like