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 @@ class instances).
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
d[list] = list.copy
d[dict] = dict.copy
d[set] = set.copy
d[bytearray] = bytearray.copy
def _copy_with_copy_method(x):
return x.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 @@ def _deepcopy_atomic(x, memo):
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 @@ def _deepcopy_dict(x, memo):
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

View file

@ -95,24 +95,67 @@ def f():
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:

View file

@ -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.