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
99
Lib/copy.py
99
Lib/copy.py
|
@ -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
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue