Bug report
copy.copy() of a collections.deque subclass, and both copy.copy() and copy.deepcopy() of an array.array subclass, silently drop the instance __dict__. For array the result is also a plain array rather than the subclass. pickle preserves both, and list/dict/set subclasses behave correctly, so this is an inconsistency.
Reproducer
import copy, pickle
from collections import deque
from array import array
class D(deque): pass
d = D([1, 2, 3]); d.label = "kept"
print("deque copy :", getattr(copy.copy(d), "label", "LOST"))
print("deque deepcopy:", getattr(copy.deepcopy(d), "label", "LOST"))
print("deque pickle :", getattr(pickle.loads(pickle.dumps(d)), "label", "LOST"))
class A(array): pass
a = A("i", [1, 2, 3]); a.tag = "kept"
print("array copy :", type(copy.copy(a)).__name__, getattr(copy.copy(a), "tag", "LOST"))
print("array deepcopy:", type(copy.deepcopy(a)).__name__, getattr(copy.deepcopy(a), "tag", "LOST"))
print("array pickle :", type(pickle.loads(pickle.dumps(a))).__name__, getattr(pickle.loads(pickle.dumps(a)), "tag", "LOST"))
Output:
deque copy : LOST
deque deepcopy: kept
deque pickle : kept
array copy : array LOST
array deepcopy: array LOST
array pickle : A kept
A list subclass (and dict/set) preserves the type and attribute in all three cases, which is the expected behavior:
class L(list): pass
l = L([1, 2, 3]); l.label = "kept"
copy.copy(l).label # 'kept'
type(copy.copy(l)) # <class '__main__.L'>
Expected
copy.copy()/copy.deepcopy() should return an object of the same (sub)class with the instance attributes preserved, matching pickle and the behavior of list/dict/set subclasses.
Cause
deque.__copy__ (deque_copy in Modules/_collectionsmodule.c) and array.__copy__/__deepcopy__ (Modules/arraymodule.c) build the new object without copying the instance __dict__; the array versions also don't construct the actual subclass. Each type's __reduce_ex__ already captures the subclass and the __dict__ (that's why pickle and deepcopy-of-deque work), so the custom __copy__/__deepcopy__ shortcuts are what diverge.
Versions
Reproduces on main (3.16.0a0) and 3.12.7.
Linked PRs
Bug report
copy.copy()of acollections.dequesubclass, and bothcopy.copy()andcopy.deepcopy()of anarray.arraysubclass, silently drop the instance__dict__. Forarraythe result is also a plainarrayrather than the subclass.picklepreserves both, andlist/dict/setsubclasses behave correctly, so this is an inconsistency.Reproducer
Output:
A
listsubclass (anddict/set) preserves the type and attribute in all three cases, which is the expected behavior:Expected
copy.copy()/copy.deepcopy()should return an object of the same (sub)class with the instance attributes preserved, matchingpickleand the behavior oflist/dict/setsubclasses.Cause
deque.__copy__(deque_copyinModules/_collectionsmodule.c) andarray.__copy__/__deepcopy__(Modules/arraymodule.c) build the new object without copying the instance__dict__; thearrayversions also don't construct the actual subclass. Each type's__reduce_ex__already captures the subclass and the__dict__(that's whypickleanddeepcopy-of-deque work), so the custom__copy__/__deepcopy__shortcuts are what diverge.Versions
Reproduces on
main(3.16.0a0) and 3.12.7.Linked PRs