Skip to content

Commit cca58ee

Browse files
authored
Fix copying generic instances in Python 3 (#502)
Fixes #498 (on both Python 2 and 3)
1 parent 98db8a4 commit cca58ee

File tree

4 files changed

+65
-11
lines changed

4 files changed

+65
-11
lines changed

python2/test_typing.py

+24
Original file line numberDiff line numberDiff line change
@@ -1024,6 +1024,30 @@ class Node(Generic[T]): pass
10241024
self.assertEqual(t, deepcopy(t))
10251025
self.assertEqual(t, copy(t))
10261026

1027+
def test_copy_generic_instances(self):
1028+
T = TypeVar('T')
1029+
class C(Generic[T]):
1030+
def __init__(self, attr):
1031+
self.attr = attr
1032+
1033+
c = C(42)
1034+
self.assertEqual(copy(c).attr, 42)
1035+
self.assertEqual(deepcopy(c).attr, 42)
1036+
self.assertIsNot(copy(c), c)
1037+
self.assertIsNot(deepcopy(c), c)
1038+
c.attr = 1
1039+
self.assertEqual(copy(c).attr, 1)
1040+
self.assertEqual(deepcopy(c).attr, 1)
1041+
ci = C[int](42)
1042+
self.assertEqual(copy(ci).attr, 42)
1043+
self.assertEqual(deepcopy(ci).attr, 42)
1044+
self.assertIsNot(copy(ci), ci)
1045+
self.assertIsNot(deepcopy(ci), ci)
1046+
ci.attr = 1
1047+
self.assertEqual(copy(ci).attr, 1)
1048+
self.assertEqual(deepcopy(ci).attr, 1)
1049+
self.assertEqual(ci.__orig_class__, C[int])
1050+
10271051
def test_weakref_all(self):
10281052
T = TypeVar('T')
10291053
things = [Any, Union[T, int], Callable[..., T], Tuple[Any, Any],

python2/typing.py

+17-5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import re as stdlib_re # Avoid confusion with the re we export.
88
import sys
99
import types
10+
import copy
1011
try:
1112
import collections.abc as collections_abc
1213
except ImportError:
@@ -1253,11 +1254,6 @@ def __instancecheck__(self, instance):
12531254
return issubclass(instance.__class__, self)
12541255
return False
12551256

1256-
def __copy__(self):
1257-
return self.__class__(self.__name__, self.__bases__, dict(self.__dict__),
1258-
self.__parameters__, self.__args__, self.__origin__,
1259-
self.__extra__, self.__orig_bases__)
1260-
12611257
def __setattr__(self, attr, value):
12621258
# We consider all the subscripted genrics as proxies for original class
12631259
if (
@@ -1269,6 +1265,16 @@ def __setattr__(self, attr, value):
12691265
super(GenericMeta, self._gorg).__setattr__(attr, value)
12701266

12711267

1268+
def _copy_generic(self):
1269+
"""Hack to work around https://bugs.python.org/issue11480 on Python 2"""
1270+
return self.__class__(self.__name__, self.__bases__, dict(self.__dict__),
1271+
self.__parameters__, self.__args__, self.__origin__,
1272+
self.__extra__, self.__orig_bases__)
1273+
1274+
1275+
copy._copy_dispatch[GenericMeta] = _copy_generic
1276+
1277+
12721278
# Prevent checks for Generic to crash when defining Generic.
12731279
Generic = None
12741280

@@ -1365,6 +1371,9 @@ def __subclasscheck__(self, cls):
13651371
"with issubclass().")
13661372

13671373

1374+
copy._copy_dispatch[TupleMeta] = _copy_generic
1375+
1376+
13681377
class Tuple(tuple):
13691378
"""Tuple type; Tuple[X, Y] is the cross-product type of X and Y.
13701379
@@ -1443,6 +1452,9 @@ def __getitem_inner__(self, parameters):
14431452
return super(CallableMeta, self).__getitem__(parameters)
14441453

14451454

1455+
copy._copy_dispatch[CallableMeta] = _copy_generic
1456+
1457+
14461458
class Callable(object):
14471459
"""Callable type; Callable[[int], str] is a function of (int) -> str.
14481460

src/test_typing.py

+24
Original file line numberDiff line numberDiff line change
@@ -1080,6 +1080,30 @@ class Node(Generic[T]): ...
10801080
self.assertTrue(t is copy(t))
10811081
self.assertTrue(t is deepcopy(t))
10821082

1083+
def test_copy_generic_instances(self):
1084+
T = TypeVar('T')
1085+
class C(Generic[T]):
1086+
def __init__(self, attr: T) -> None:
1087+
self.attr = attr
1088+
1089+
c = C(42)
1090+
self.assertEqual(copy(c).attr, 42)
1091+
self.assertEqual(deepcopy(c).attr, 42)
1092+
self.assertIsNot(copy(c), c)
1093+
self.assertIsNot(deepcopy(c), c)
1094+
c.attr = 1
1095+
self.assertEqual(copy(c).attr, 1)
1096+
self.assertEqual(deepcopy(c).attr, 1)
1097+
ci = C[int](42)
1098+
self.assertEqual(copy(ci).attr, 42)
1099+
self.assertEqual(deepcopy(ci).attr, 42)
1100+
self.assertIsNot(copy(ci), ci)
1101+
self.assertIsNot(deepcopy(ci), ci)
1102+
ci.attr = 1
1103+
self.assertEqual(copy(ci).attr, 1)
1104+
self.assertEqual(deepcopy(ci).attr, 1)
1105+
self.assertEqual(ci.__orig_class__, C[int])
1106+
10831107
def test_weakref_all(self):
10841108
T = TypeVar('T')
10851109
things = [Any, Union[T, int], Callable[..., T], Tuple[Any, Any],

src/typing.py

-6
Original file line numberDiff line numberDiff line change
@@ -1160,12 +1160,6 @@ def __instancecheck__(self, instance):
11601160
# classes are supposed to be rare anyways.
11611161
return issubclass(instance.__class__, self)
11621162

1163-
def __copy__(self):
1164-
return self.__class__(self.__name__, self.__bases__,
1165-
_no_slots_copy(self.__dict__),
1166-
self.__parameters__, self.__args__, self.__origin__,
1167-
self.__extra__, self.__orig_bases__)
1168-
11691163
def __setattr__(self, attr, value):
11701164
# We consider all the subscripted generics as proxies for original class
11711165
if (

0 commit comments

Comments
 (0)