Skip to content

Commit f4e1bab

Browse files
bharelserhiy-storchaka
authored andcommitted
bpo-27141: Fix collections.UserList and UserDict shallow copy. (GH-4094)
1 parent c661b30 commit f4e1bab

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
@@ -1038,6 +1038,13 @@ def __contains__(self, key):
10381038

10391039
# Now, add the methods in dicts but not in MutableMapping
10401040
def __repr__(self): return repr(self.data)
1041+
def __copy__(self):
1042+
inst = self.__class__.__new__(self.__class__)
1043+
inst.__dict__.update(self.__dict__)
1044+
# Create a copy and avoid triggering descriptors
1045+
inst.__dict__["data"] = self.__dict__["data"].copy()
1046+
return inst
1047+
10411048
def copy(self):
10421049
if self.__class__ is UserDict:
10431050
return UserDict(self.data.copy())
@@ -1050,6 +1057,7 @@ def copy(self):
10501057
self.data = data
10511058
c.update(self)
10521059
return c
1060+
10531061
@classmethod
10541062
def fromkeys(cls, iterable, value=None):
10551063
d = cls()
@@ -1118,6 +1126,12 @@ def __mul__(self, n):
11181126
def __imul__(self, n):
11191127
self.data *= n
11201128
return self
1129+
def __copy__(self):
1130+
inst = self.__class__.__new__(self.__class__)
1131+
inst.__dict__.update(self.__dict__)
1132+
# Create a copy and avoid triggering descriptors
1133+
inst.__dict__["data"] = self.__dict__["data"][:]
1134+
return inst
11211135
def append(self, item): self.data.append(item)
11221136
def insert(self, i, item): self.data.insert(i, item)
11231137
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
@@ -37,6 +37,20 @@ def _superset_test(self, a, b):
3737
b=b.__name__,
3838
),
3939
)
40+
41+
def _copy_test(self, obj):
42+
# Test internal copy
43+
obj_copy = obj.copy()
44+
self.assertIsNot(obj.data, obj_copy.data)
45+
self.assertEqual(obj.data, obj_copy.data)
46+
47+
# Test copy.copy
48+
obj.test = [1234] # Make sure instance vars are also copied.
49+
obj_copy = copy.copy(obj)
50+
self.assertIsNot(obj.data, obj_copy.data)
51+
self.assertEqual(obj.data, obj_copy.data)
52+
self.assertIs(obj.test, obj_copy.test)
53+
4054
def test_str_protocol(self):
4155
self._superset_test(UserString, str)
4256

@@ -46,6 +60,16 @@ def test_list_protocol(self):
4660
def test_dict_protocol(self):
4761
self._superset_test(UserDict, dict)
4862

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

5074
################################################################################
5175
### 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)