Issue #26167: Minimized overhead in copy.copy() and copy.deepcopy().

Optimized copying and deepcopying bytearrays, NotImplemented, slices,
short lists, tuples, dicts, sets.
This commit is contained in:
Serhiy Storchaka 2016-03-06 14:56:57 +02:00
parent de128e19e2
commit 818e18dd94
3 changed files with 100 additions and 70 deletions

View File

@ -51,7 +51,6 @@ __getstate__() and __setstate__(). See the documentation for module
import types import types
import weakref import weakref
from copyreg import dispatch_table from copyreg import dispatch_table
import builtins
class Error(Exception): class Error(Exception):
pass pass
@ -102,37 +101,33 @@ def copy(x):
else: else:
raise Error("un(shallow)copyable object of type %s" % cls) 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 = {} _copy_dispatch = d = {}
def _copy_immutable(x): def _copy_immutable(x):
return x return x
for t in (type(None), int, float, bool, str, tuple, for t in (type(None), int, float, bool, complex, str, tuple,
bytes, frozenset, type, range, bytes, frozenset, type, range, slice,
types.BuiltinFunctionType, type(Ellipsis), types.BuiltinFunctionType, type(Ellipsis), type(NotImplemented),
types.FunctionType, weakref.ref): types.FunctionType, weakref.ref):
d[t] = _copy_immutable d[t] = _copy_immutable
t = getattr(types, "CodeType", None) t = getattr(types, "CodeType", None)
if t is not None: if t is not None:
d[t] = _copy_immutable 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): d[list] = list.copy
return type(x)(x) d[dict] = dict.copy
for t in (list, dict, set): d[set] = set.copy
d[t] = _copy_with_constructor d[bytearray] = bytearray.copy
def _copy_with_copy_method(x):
return x.copy()
if PyStringMap is not None: 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=[]): def deepcopy(x, memo=None, _nil=[]):
"""Deep copy operation on arbitrary Python objects. """Deep copy operation on arbitrary Python objects.
@ -179,7 +174,10 @@ def deepcopy(x, memo=None, _nil=[]):
else: else:
raise Error( raise Error(
"un(deep)copyable object of type %s" % cls) "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 is its own copy, don't memoize.
if y is not x: if y is not x:
@ -193,13 +191,11 @@ def _deepcopy_atomic(x, memo):
return x return x
d[type(None)] = _deepcopy_atomic d[type(None)] = _deepcopy_atomic
d[type(Ellipsis)] = _deepcopy_atomic d[type(Ellipsis)] = _deepcopy_atomic
d[type(NotImplemented)] = _deepcopy_atomic
d[int] = _deepcopy_atomic d[int] = _deepcopy_atomic
d[float] = _deepcopy_atomic d[float] = _deepcopy_atomic
d[bool] = _deepcopy_atomic d[bool] = _deepcopy_atomic
try: d[complex] = _deepcopy_atomic
d[complex] = _deepcopy_atomic
except NameError:
pass
d[bytes] = _deepcopy_atomic d[bytes] = _deepcopy_atomic
d[str] = _deepcopy_atomic d[str] = _deepcopy_atomic
try: try:
@ -211,15 +207,16 @@ d[types.BuiltinFunctionType] = _deepcopy_atomic
d[types.FunctionType] = _deepcopy_atomic d[types.FunctionType] = _deepcopy_atomic
d[weakref.ref] = _deepcopy_atomic d[weakref.ref] = _deepcopy_atomic
def _deepcopy_list(x, memo): def _deepcopy_list(x, memo, deepcopy=deepcopy):
y = [] y = []
memo[id(x)] = y memo[id(x)] = y
append = y.append
for a in x: for a in x:
y.append(deepcopy(a, memo)) append(deepcopy(a, memo))
return y return y
d[list] = _deepcopy_list d[list] = _deepcopy_list
def _deepcopy_tuple(x, memo): def _deepcopy_tuple(x, memo, deepcopy=deepcopy):
y = [deepcopy(a, memo) for a in x] 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 # 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. # check for it, in case the tuple contains recursive mutable structures.
@ -236,7 +233,7 @@ def _deepcopy_tuple(x, memo):
return y return y
d[tuple] = _deepcopy_tuple d[tuple] = _deepcopy_tuple
def _deepcopy_dict(x, memo): def _deepcopy_dict(x, memo, deepcopy=deepcopy):
y = {} y = {}
memo[id(x)] = y memo[id(x)] = y
for key, value in x.items(): for key, value in x.items():
@ -248,7 +245,9 @@ if PyStringMap is not None:
def _deepcopy_method(x, memo): # Copy instance methods def _deepcopy_method(x, memo): # Copy instance methods
return type(x)(x.__func__, deepcopy(x.__self__, memo)) 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): def _keep_alive(x, memo):
"""Keeps a reference to the object x in the memo. """Keeps a reference to the object x in the memo.
@ -266,30 +265,14 @@ def _keep_alive(x, memo):
# aha, this is the first one :-) # aha, this is the first one :-)
memo[id(memo)]=[x] memo[id(memo)]=[x]
def _reconstruct(x, info, deep, memo=None): def _reconstruct(x, memo, func, args,
if isinstance(info, str): state=None, listiter=None, dictiter=None,
return x deepcopy=deepcopy):
assert isinstance(info, tuple) deep = memo is not None
if memo is None: if deep and args:
memo = {} args = (deepcopy(arg, memo) for arg in args)
n = len(info) y = func(*args)
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
if deep: if deep:
args = deepcopy(args, memo)
y = callable(*args)
memo[id(x)] = y memo[id(x)] = y
if state is not None: if state is not None:
@ -309,22 +292,22 @@ def _reconstruct(x, info, deep, memo=None):
setattr(y, key, value) setattr(y, key, value)
if listiter is not None: if listiter is not None:
for item in listiter:
if deep: if deep:
for item in listiter:
item = deepcopy(item, memo) item = deepcopy(item, memo)
y.append(item) y.append(item)
else:
for item in listiter:
y.append(item)
if dictiter is not None: if dictiter is not None:
for key, value in dictiter:
if deep: if deep:
for key, value in dictiter:
key = deepcopy(key, memo) key = deepcopy(key, memo)
value = deepcopy(value, memo) value = deepcopy(value, memo)
y[key] = value y[key] = value
else:
for key, value in dictiter:
y[key] = value
return y return y
del d del types, weakref, PyStringMap
del types
# Helper for instance creation without calling __init__
class _EmptyClass:
pass

View File

@ -95,24 +95,67 @@ class TestCopy(unittest.TestCase):
pass pass
class WithMetaclass(metaclass=abc.ABCMeta): class WithMetaclass(metaclass=abc.ABCMeta):
pass 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__, "hello", "hello\u1234", f.__code__,
b"world", bytes(range(256)), b"world", bytes(range(256)), range(10), slice(1, 10, 2),
NewStyle, range(10), Classic, max, WithMetaclass] NewStyle, Classic, max, WithMetaclass]
for x in tests: for x in tests:
self.assertIs(copy.copy(x), x) self.assertIs(copy.copy(x), x)
def test_copy_list(self): def test_copy_list(self):
x = [1, 2, 3] 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): def test_copy_tuple(self):
x = (1, 2, 3) 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): def test_copy_dict(self):
x = {"foo": 1, "bar": 2} 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): def test_copy_inst_vanilla(self):
class C: class C:

View File

@ -201,6 +201,10 @@ Core and Builtins
Library 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 - Issue #25718: Fixed pickling and copying the accumulate() iterator with
total is None. total is None.