summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Lib/copy.py115
-rw-r--r--Lib/test/test_copy.py55
-rw-r--r--Misc/NEWS4
3 files changed, 102 insertions, 72 deletions
diff --git a/Lib/copy.py b/Lib/copy.py
index 972b94ab49..f86040a33c 100644
--- a/Lib/copy.py
+++ b/Lib/copy.py
@@ -51,7 +51,6 @@ __getstate__() and __setstate__(). See the documentation for module
import types
import weakref
from copyreg import dispatch_table
-import builtins
class Error(Exception):
pass
@@ -102,37 +101,33 @@ def copy(x):
else:
raise Error("un(shallow)copyable object of type %s" % cls)
- return _reconstruct(x, rv, 0)
+ if isinstance(rv, str):
+ return x
+ return _reconstruct(x, None, *rv)
_copy_dispatch = d = {}
def _copy_immutable(x):
return x
-for t in (type(None), int, float, bool, str, tuple,
- bytes, frozenset, type, range,
- types.BuiltinFunctionType, type(Ellipsis),
+for t in (type(None), int, float, bool, complex, str, tuple,
+ bytes, frozenset, type, range, slice,
+ types.BuiltinFunctionType, type(Ellipsis), type(NotImplemented),
types.FunctionType, weakref.ref):
d[t] = _copy_immutable
t = getattr(types, "CodeType", None)
if t is not None:
d[t] = _copy_immutable
-for name in ("complex", "unicode"):
- t = getattr(builtins, name, None)
- if t is not None:
- d[t] = _copy_immutable
-
-def _copy_with_constructor(x):
- return type(x)(x)
-for t in (list, dict, set):
- d[t] = _copy_with_constructor
-
-def _copy_with_copy_method(x):
- return x.copy()
+
+d[list] = list.copy
+d[dict] = dict.copy
+d[set] = set.copy
+d[bytearray] = bytearray.copy
+
if PyStringMap is not None:
- d[PyStringMap] = _copy_with_copy_method
+ d[PyStringMap] = PyStringMap.copy
-del d
+del d, t
def deepcopy(x, memo=None, _nil=[]):
"""Deep copy operation on arbitrary Python objects.
@@ -179,7 +174,10 @@ def deepcopy(x, memo=None, _nil=[]):
else:
raise Error(
"un(deep)copyable object of type %s" % cls)
- y = _reconstruct(x, rv, 1, memo)
+ if isinstance(rv, str):
+ y = x
+ else:
+ y = _reconstruct(x, memo, *rv)
# If is its own copy, don't memoize.
if y is not x:
@@ -193,13 +191,11 @@ def _deepcopy_atomic(x, memo):
return x
d[type(None)] = _deepcopy_atomic
d[type(Ellipsis)] = _deepcopy_atomic
+d[type(NotImplemented)] = _deepcopy_atomic
d[int] = _deepcopy_atomic
d[float] = _deepcopy_atomic
d[bool] = _deepcopy_atomic
-try:
- d[complex] = _deepcopy_atomic
-except NameError:
- pass
+d[complex] = _deepcopy_atomic
d[bytes] = _deepcopy_atomic
d[str] = _deepcopy_atomic
try:
@@ -211,15 +207,16 @@ d[types.BuiltinFunctionType] = _deepcopy_atomic
d[types.FunctionType] = _deepcopy_atomic
d[weakref.ref] = _deepcopy_atomic
-def _deepcopy_list(x, memo):
+def _deepcopy_list(x, memo, deepcopy=deepcopy):
y = []
memo[id(x)] = y
+ append = y.append
for a in x:
- y.append(deepcopy(a, memo))
+ append(deepcopy(a, memo))
return y
d[list] = _deepcopy_list
-def _deepcopy_tuple(x, memo):
+def _deepcopy_tuple(x, memo, deepcopy=deepcopy):
y = [deepcopy(a, memo) for a in x]
# We're not going to put the tuple in the memo, but it's still important we
# check for it, in case the tuple contains recursive mutable structures.
@@ -236,7 +233,7 @@ def _deepcopy_tuple(x, memo):
return y
d[tuple] = _deepcopy_tuple
-def _deepcopy_dict(x, memo):
+def _deepcopy_dict(x, memo, deepcopy=deepcopy):
y = {}
memo[id(x)] = y
for key, value in x.items():
@@ -248,7 +245,9 @@ if PyStringMap is not None:
def _deepcopy_method(x, memo): # Copy instance methods
return type(x)(x.__func__, deepcopy(x.__self__, memo))
-_deepcopy_dispatch[types.MethodType] = _deepcopy_method
+d[types.MethodType] = _deepcopy_method
+
+del d
def _keep_alive(x, memo):
"""Keeps a reference to the object x in the memo.
@@ -266,31 +265,15 @@ def _keep_alive(x, memo):
# aha, this is the first one :-)
memo[id(memo)]=[x]
-def _reconstruct(x, info, deep, memo=None):
- if isinstance(info, str):
- return x
- assert isinstance(info, tuple)
- if memo is None:
- memo = {}
- n = len(info)
- assert n in (2, 3, 4, 5)
- callable, args = info[:2]
- if n > 2:
- state = info[2]
- else:
- state = None
- if n > 3:
- listiter = info[3]
- else:
- listiter = None
- if n > 4:
- dictiter = info[4]
- else:
- dictiter = None
+def _reconstruct(x, memo, func, args,
+ state=None, listiter=None, dictiter=None,
+ deepcopy=deepcopy):
+ deep = memo is not None
+ if deep and args:
+ args = (deepcopy(arg, memo) for arg in args)
+ y = func(*args)
if deep:
- args = deepcopy(args, memo)
- y = callable(*args)
- memo[id(x)] = y
+ memo[id(x)] = y
if state is not None:
if deep:
@@ -309,22 +292,22 @@ def _reconstruct(x, info, deep, memo=None):
setattr(y, key, value)
if listiter is not None:
- for item in listiter:
- if deep:
+ if deep:
+ for item in listiter:
item = deepcopy(item, memo)
- y.append(item)
+ y.append(item)
+ else:
+ for item in listiter:
+ y.append(item)
if dictiter is not None:
- for key, value in dictiter:
- if deep:
+ if deep:
+ for key, value in dictiter:
key = deepcopy(key, memo)
value = deepcopy(value, memo)
- y[key] = value
+ y[key] = value
+ else:
+ for key, value in dictiter:
+ y[key] = value
return y
-del d
-
-del types
-
-# Helper for instance creation without calling __init__
-class _EmptyClass:
- pass
+del types, weakref, PyStringMap
diff --git a/Lib/test/test_copy.py b/Lib/test/test_copy.py
index 7912c7cd69..45a692022f 100644
--- a/Lib/test/test_copy.py
+++ b/Lib/test/test_copy.py
@@ -95,24 +95,67 @@ class TestCopy(unittest.TestCase):
pass
class WithMetaclass(metaclass=abc.ABCMeta):
pass
- tests = [None, 42, 2**100, 3.14, True, False, 1j,
+ tests = [None, ..., NotImplemented,
+ 42, 2**100, 3.14, True, False, 1j,
"hello", "hello\u1234", f.__code__,
- b"world", bytes(range(256)),
- NewStyle, range(10), Classic, max, WithMetaclass]
+ b"world", bytes(range(256)), range(10), slice(1, 10, 2),
+ NewStyle, Classic, max, WithMetaclass]
for x in tests:
self.assertIs(copy.copy(x), x)
def test_copy_list(self):
x = [1, 2, 3]
- self.assertEqual(copy.copy(x), x)
+ y = copy.copy(x)
+ self.assertEqual(y, x)
+ self.assertIsNot(y, x)
+ x = []
+ y = copy.copy(x)
+ self.assertEqual(y, x)
+ self.assertIsNot(y, x)
def test_copy_tuple(self):
x = (1, 2, 3)
- self.assertEqual(copy.copy(x), x)
+ self.assertIs(copy.copy(x), x)
+ x = ()
+ self.assertIs(copy.copy(x), x)
+ x = (1, 2, 3, [])
+ self.assertIs(copy.copy(x), x)
def test_copy_dict(self):
x = {"foo": 1, "bar": 2}
- self.assertEqual(copy.copy(x), x)
+ y = copy.copy(x)
+ self.assertEqual(y, x)
+ self.assertIsNot(y, x)
+ x = {}
+ y = copy.copy(x)
+ self.assertEqual(y, x)
+ self.assertIsNot(y, x)
+
+ def test_copy_set(self):
+ x = {1, 2, 3}
+ y = copy.copy(x)
+ self.assertEqual(y, x)
+ self.assertIsNot(y, x)
+ x = set()
+ y = copy.copy(x)
+ self.assertEqual(y, x)
+ self.assertIsNot(y, x)
+
+ def test_copy_frozenset(self):
+ x = frozenset({1, 2, 3})
+ self.assertIs(copy.copy(x), x)
+ x = frozenset()
+ self.assertIs(copy.copy(x), x)
+
+ def test_copy_bytearray(self):
+ x = bytearray(b'abc')
+ y = copy.copy(x)
+ self.assertEqual(y, x)
+ self.assertIsNot(y, x)
+ x = bytearray()
+ y = copy.copy(x)
+ self.assertEqual(y, x)
+ self.assertIsNot(y, x)
def test_copy_inst_vanilla(self):
class C:
diff --git a/Misc/NEWS b/Misc/NEWS
index 3e10fe41fc..026db17c18 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -201,6 +201,10 @@ Core and Builtins
Library
-------
+- Issue #26167: Minimized overhead in copy.copy() and copy.deepcopy().
+ Optimized copying and deepcopying bytearrays, NotImplemented, slices,
+ short lists, tuples, dicts, sets.
+
- Issue #25718: Fixed pickling and copying the accumulate() iterator with
total is None.