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:
parent
de128e19e2
commit
818e18dd94
111
Lib/copy.py
111
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
|
||||
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 @@ 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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
Loading…
Reference in New Issue