Issue #26167: Minimized overhead in copy.copy() and copy.deepcopy().
authorSerhiy Storchaka <storchaka@gmail.com>
Sun, 6 Mar 2016 12:56:57 +0000 (14:56 +0200)
committerSerhiy Storchaka <storchaka@gmail.com>
Sun, 6 Mar 2016 12:56:57 +0000 (14:56 +0200)
Optimized copying and deepcopying bytearrays, NotImplemented, slices,
short lists, tuples, dicts, sets.

Lib/copy.py
Lib/test/test_copy.py
Misc/NEWS

index 972b94ab491f158703e1277254d78c17a9e3b0f6..f86040a33c55478b5fdcdf377c03616e39373b24 100644 (file)
@@ -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
index 7912c7cd692d8e827feb5c0752a7c385385a79f8..45a692022f29bc09019dcd99ab4a77ce01acdd47 100644 (file)
@@ -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:
index 3e10fe41fcd5a015915f7662888ba480f2b08747..026db17c18e9663a0af349620c7b5c7ec62a12b9 100644 (file)
--- 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.