Skip to content

copy.copy()/copy.deepcopy() of deque and array subclasses drop instance attributes (array also changes type) #152042

Description

@iamsharduld

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions