Skip to content

Commit 3645d29

Browse files
bpo-27141: Fix collections.UserList and UserDict shallow copy. (GH-4094)
(cherry picked from commit f4e1bab) Co-authored-by: Bar Harel <[email protected]>
1 parent f81b33b commit 3645d29

File tree

3 files changed

+41
-0
lines changed

3 files changed

+41
-0
lines changed

Lib/collections/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,6 +1036,13 @@ def __contains__(self, key):
10361036

10371037
# Now, add the methods in dicts but not in MutableMapping
10381038
def __repr__(self): return repr(self.data)
1039+
def __copy__(self):
1040+
inst = self.__class__.__new__(self.__class__)
1041+
inst.__dict__.update(self.__dict__)
1042+
# Create a copy and avoid triggering descriptors
1043+
inst.__dict__["data"] = self.__dict__["data"].copy()
1044+
return inst
1045+
10391046
def copy(self):
10401047
if self.__class__ is UserDict:
10411048
return UserDict(self.data.copy())
@@ -1048,6 +1055,7 @@ def copy(self):
10481055
self.data = data
10491056
c.update(self)
10501057
return c
1058+
10511059
@classmethod
10521060
def fromkeys(cls, iterable, value=None):
10531061
d = cls()
@@ -1112,6 +1120,12 @@ def __mul__(self, n):
11121120
def __imul__(self, n):
11131121
self.data *= n
11141122
return self
1123+
def __copy__(self):
1124+
inst = self.__class__.__new__(self.__class__)
1125+
inst.__dict__.update(self.__dict__)
1126+
# Create a copy and avoid triggering descriptors
1127+
inst.__dict__["data"] = self.__dict__["data"][:]
1128+
return inst
11151129
def append(self, item): self.data.append(item)
11161130
def insert(self, i, item): self.data.insert(i, item)
11171131
def pop(self, i=-1): return self.data.pop(i)

Lib/test/test_collections.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,20 @@ def _superset_test(self, a, b):
3838
b=b.__name__,
3939
),
4040
)
41+
42+
def _copy_test(self, obj):
43+
# Test internal copy
44+
obj_copy = obj.copy()
45+
self.assertIsNot(obj.data, obj_copy.data)
46+
self.assertEqual(obj.data, obj_copy.data)
47+
48+
# Test copy.copy
49+
obj.test = [1234] # Make sure instance vars are also copied.
50+
obj_copy = copy.copy(obj)
51+
self.assertIsNot(obj.data, obj_copy.data)
52+
self.assertEqual(obj.data, obj_copy.data)
53+
self.assertIs(obj.test, obj_copy.test)
54+
4155
def test_str_protocol(self):
4256
self._superset_test(UserString, str)
4357

@@ -47,6 +61,16 @@ def test_list_protocol(self):
4761
def test_dict_protocol(self):
4862
self._superset_test(UserDict, dict)
4963

64+
def test_list_copy(self):
65+
obj = UserList()
66+
obj.append(123)
67+
self._copy_test(obj)
68+
69+
def test_dict_copy(self):
70+
obj = UserDict()
71+
obj[123] = "abc"
72+
self._copy_test(obj)
73+
5074

5175
################################################################################
5276
### ChainMap (helper class for configparser and the string module)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Added a ``__copy__()`` to ``collections.UserList`` and
2+
``collections.UserDict`` in order to correctly implement shallow copying of
3+
the objects. Patch by Bar Harel.

0 commit comments

Comments
 (0)