From f44236f3d58cd4aa03857699b3014b109d4bebb9 Mon Sep 17 00:00:00 2001 From: Tal Einat Date: Thu, 6 Sep 2018 09:56:05 +0300 Subject: [PATCH 1/5] bpo-30977: make uuid.UUID use __slots__ to reduce its memory footprint Based on original patch by Wouter Bolsterlee. --- Lib/test/test_uuid.py | 30 ++++++++++++++++++++++++++++++ Lib/uuid.py | 28 ++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 7af1d7aec797c5..e02c6766f370ac 100644 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -4,6 +4,7 @@ import contextlib import io import os +import pickle import shutil import subprocess @@ -186,6 +187,9 @@ def test_UUID(self): for v in equivalents: equal(u, v) + # Test pickle-unpickle round-trip + equal(u, pickle.loads(pickle.dumps(u))) + # Bug 7380: "bytes" and "bytes_le" should give the same type. equal(type(u.bytes), builtins.bytes) equal(type(u.bytes_le), builtins.bytes) @@ -311,6 +315,32 @@ def test_getnode(self): node2 = self.uuid.getnode() self.assertEqual(node1, node2, '%012x != %012x' % (node1, node2)) + def test_unpickle_previous_python_versions(self): + u = self.uuid.UUID('12345678123456781234567812345678') + + # Python 2.7 protocol 0-2 pickles of u + py27_pickles = [ + b'ccopy_reg\n_reconstructor\np0\n(cuuid\nUUID\np1\nc__builtin__\nobject\np2\nNtp3\nRp4\n(dp5\nS\'int\'\np6\nL24197857161011715162171839636988778104L\nsb.', + b'ccopy_reg\n_reconstructor\nq\x00(cuuid\nUUID\nq\x01c__builtin__\nobject\nq\x02Ntq\x03Rq\x04}q\x05U\x03intq\x06L24197857161011715162171839636988778104L\nsb.', + b'\x80\x02cuuid\nUUID\nq\x00)\x81q\x01}q\x02U\x03intq\x03\x8a\x10xV4\x12xV4\x12xV4\x12xV4\x12sb.', + ] + # Python 3.6 protocol 0-4 pickles of u + py36_pickles = [ + b'ccopy_reg\n_reconstructor\np0\n(cuuid\nUUID\np1\nc__builtin__\nobject\np2\nNtp3\nRp4\n(dp5\nVint\np6\nL24197857161011715162171839636988778104L\nsb.', + b'ccopy_reg\n_reconstructor\nq\x00(cuuid\nUUID\nq\x01c__builtin__\nobject\nq\x02Ntq\x03Rq\x04}q\x05X\x03\x00\x00\x00intq\x06L24197857161011715162171839636988778104L\nsb.', + b'\x80\x02cuuid\nUUID\nq\x00)\x81q\x01}q\x02X\x03\x00\x00\x00intq\x03\x8a\x10xV4\x12xV4\x12xV4\x12xV4\x12sb.', + b'\x80\x03cuuid\nUUID\nq\x00)\x81q\x01}q\x02X\x03\x00\x00\x00intq\x03\x8a\x10xV4\x12xV4\x12xV4\x12xV4\x12sb.', + b'\x80\x04\x950\x00\x00\x00\x00\x00\x00\x00\x8c\x04uuid\x94\x8c\x04UUID\x94\x93\x94)\x81\x94}\x94\x8c\x03int\x94\x8a\x10xV4\x12xV4\x12xV4\x12xV4\x12sb.', + ] + + for pickled in py27_pickles + py36_pickles: + unpickled = pickle.loads(pickled) + self.assertEqual(unpickled, u) + # is_safe was added in 3.7. When unpickling values from older + # versions, is_safe will be missing, so it should be set to + # SafeUUID.unknown. + self.assertEqual(unpickled.is_safe, self.uuid.SafeUUID.unknown) + # bpo-32502: UUID1 requires a 48-bit identifier, but hardware identifiers # need not necessarily be 48 bits (e.g., EUI-64). def test_uuid1_eui64(self): diff --git a/Lib/uuid.py b/Lib/uuid.py index 66383218e70c0d..51538822151030 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -118,6 +118,8 @@ class UUID: uuid_generate_time_safe(3). """ + __slots__ = ('int', 'is_safe') + def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None, int=None, version=None, *, is_safe=SafeUUID.unknown): @@ -201,8 +203,30 @@ def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None, # Set the version number. int &= ~(0xf000 << 64) int |= version << 76 - self.__dict__['int'] = int - self.__dict__['is_safe'] = is_safe + object.__setattr__(self, 'int', int) + object.__setattr__(self, 'is_safe', is_safe) + + def __getstate__(self): + d = {attr: getattr(self, attr) for attr in self.__slots__} + # is_safe is a SafeUUID instance. Return just its value, so that + # it can be unpickled in older Python versions without SafeUUID. + d['is_safe'] = d['is_safe'].value + return d + + def __setstate__(self, state): + # is_safe was added in 3.7 + state.setdefault('is_safe', SafeUUID.unknown.value) + + for attr in self.__slots__: + value = state[attr] + + # for is_safe, restore the SafeUUID from the stored value + if attr == 'is_safe': + try: + value = SafeUUID(value) + except ValueError: + value = SafeUUID.unknown + object.__setattr__(self, attr, value) def __eq__(self, other): if isinstance(other, UUID): From 4ecc1cb67d599614d1521ef5795a0fda075d5980 Mon Sep 17 00:00:00 2001 From: Tal Einat Date: Thu, 6 Sep 2018 10:08:16 +0300 Subject: [PATCH 2/5] bpo-30977: add NEWS entry --- .../next/Library/2018-09-06-10-07-46.bpo-30977.bP661V.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2018-09-06-10-07-46.bpo-30977.bP661V.rst diff --git a/Misc/NEWS.d/next/Library/2018-09-06-10-07-46.bpo-30977.bP661V.rst b/Misc/NEWS.d/next/Library/2018-09-06-10-07-46.bpo-30977.bP661V.rst new file mode 100644 index 00000000000000..3d547c06beb5e3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-09-06-10-07-46.bpo-30977.bP661V.rst @@ -0,0 +1,2 @@ +Make uuid.UUID use ``__slots__`` to reduce its memory footprint. Based on +original patch by Wouter Bolsterlee. From 13a0dcd0d7af6384200603c6707eada4364d7334 Mon Sep 17 00:00:00 2001 From: Tal Einat Date: Thu, 6 Sep 2018 11:36:49 +0300 Subject: [PATCH 3/5] bpo-30977: reformat test fixtures to 80 character lines --- Lib/test/test_uuid.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index e02c6766f370ac..445de2225339f5 100644 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -320,17 +320,30 @@ def test_unpickle_previous_python_versions(self): # Python 2.7 protocol 0-2 pickles of u py27_pickles = [ - b'ccopy_reg\n_reconstructor\np0\n(cuuid\nUUID\np1\nc__builtin__\nobject\np2\nNtp3\nRp4\n(dp5\nS\'int\'\np6\nL24197857161011715162171839636988778104L\nsb.', - b'ccopy_reg\n_reconstructor\nq\x00(cuuid\nUUID\nq\x01c__builtin__\nobject\nq\x02Ntq\x03Rq\x04}q\x05U\x03intq\x06L24197857161011715162171839636988778104L\nsb.', - b'\x80\x02cuuid\nUUID\nq\x00)\x81q\x01}q\x02U\x03intq\x03\x8a\x10xV4\x12xV4\x12xV4\x12xV4\x12sb.', + b'ccopy_reg\n_reconstructor\np0\n(cuuid\nUUID\np1\nc__builtin__\nob' + b'ject\np2\nNtp3\nRp4\n(dp5\nS\'int\'\np6\nL24197857161011715162171' + b'839636988778104L\nsb.', + b'ccopy_reg\n_reconstructor\nq\x00(cuuid\nUUID\nq\x01c__builtin__\n' + b'object\nq\x02Ntq\x03Rq\x04}q\x05U\x03intq\x06L2419785716101171516' + b'2171839636988778104L\nsb.', + b'\x80\x02cuuid\nUUID\nq\x00)\x81q\x01}q\x02U\x03intq\x03\x8a\x10xV' + b'4\x12xV4\x12xV4\x12xV4\x12sb.', ] # Python 3.6 protocol 0-4 pickles of u py36_pickles = [ - b'ccopy_reg\n_reconstructor\np0\n(cuuid\nUUID\np1\nc__builtin__\nobject\np2\nNtp3\nRp4\n(dp5\nVint\np6\nL24197857161011715162171839636988778104L\nsb.', - b'ccopy_reg\n_reconstructor\nq\x00(cuuid\nUUID\nq\x01c__builtin__\nobject\nq\x02Ntq\x03Rq\x04}q\x05X\x03\x00\x00\x00intq\x06L24197857161011715162171839636988778104L\nsb.', - b'\x80\x02cuuid\nUUID\nq\x00)\x81q\x01}q\x02X\x03\x00\x00\x00intq\x03\x8a\x10xV4\x12xV4\x12xV4\x12xV4\x12sb.', - b'\x80\x03cuuid\nUUID\nq\x00)\x81q\x01}q\x02X\x03\x00\x00\x00intq\x03\x8a\x10xV4\x12xV4\x12xV4\x12xV4\x12sb.', - b'\x80\x04\x950\x00\x00\x00\x00\x00\x00\x00\x8c\x04uuid\x94\x8c\x04UUID\x94\x93\x94)\x81\x94}\x94\x8c\x03int\x94\x8a\x10xV4\x12xV4\x12xV4\x12xV4\x12sb.', + b'ccopy_reg\n_reconstructor\np0\n(cuuid\nUUID\np1\nc__builtin__\nob' + b'ject\np2\nNtp3\nRp4\n(dp5\nVint\np6\nL241978571610117151621718396' + b'36988778104L\nsb.', + b'ccopy_reg\n_reconstructor\nq\x00(cuuid\nUUID\nq\x01c__builtin__\n' + b'object\nq\x02Ntq\x03Rq\x04}q\x05X\x03\x00\x00\x00intq\x06L2419785' + b'7161011715162171839636988778104L\nsb.', + b'\x80\x02cuuid\nUUID\nq\x00)\x81q\x01}q\x02X\x03\x00\x00\x00intq' + b'\x03\x8a\x10xV4\x12xV4\x12xV4\x12xV4\x12sb.', + b'\x80\x03cuuid\nUUID\nq\x00)\x81q\x01}q\x02X\x03\x00\x00\x00intq' + b'\x03\x8a\x10xV4\x12xV4\x12xV4\x12xV4\x12sb.', + b'\x80\x04\x950\x00\x00\x00\x00\x00\x00\x00\x8c\x04uuid\x94\x8c\x04' + b'UUID\x94\x93\x94)\x81\x94}\x94\x8c\x03int\x94\x8a\x10xV4\x12xV4' + b'\x12xV4\x12xV4\x12sb.', ] for pickled in py27_pickles + py36_pickles: From bd997e32c8187b430b0bc31496bf37741716de29 Mon Sep 17 00:00:00 2001 From: Tal Einat Date: Thu, 6 Sep 2018 12:15:55 +0300 Subject: [PATCH 4/5] bpo-30977: attempt to fix unpickling errors in tests --- Lib/test/test_uuid.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 445de2225339f5..8781c33d980a2b 100644 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -7,6 +7,7 @@ import pickle import shutil import subprocess +import sys py_uuid = support.import_fresh_module('uuid', blocked=['_uuid']) c_uuid = support.import_fresh_module('uuid', fresh=['_uuid']) @@ -187,9 +188,6 @@ def test_UUID(self): for v in equivalents: equal(u, v) - # Test pickle-unpickle round-trip - equal(u, pickle.loads(pickle.dumps(u))) - # Bug 7380: "bytes" and "bytes_le" should give the same type. equal(type(u.bytes), builtins.bytes) equal(type(u.bytes_le), builtins.bytes) @@ -315,7 +313,24 @@ def test_getnode(self): node2 = self.uuid.getnode() self.assertEqual(node1, node2, '%012x != %012x' % (node1, node2)) + def _setup_for_pickle(self): + orig_uuid = sys.modules.get('uuid') + def restore_uuid_module(): + if orig_uuid is not None: + sys.modules['uuid'] = orig_uuid + else: + del sys.modules['uuid'] + self.addCleanup(restore_uuid_module) + + def test_pickle_roundtrip(self): + self._setup_for_pickle() + + u = self.uuid.UUID('12345678123456781234567812345678') + self.assertEqual(u, pickle.loads(pickle.dumps(u))) + def test_unpickle_previous_python_versions(self): + self._setup_for_pickle() + u = self.uuid.UUID('12345678123456781234567812345678') # Python 2.7 protocol 0-2 pickles of u From 1ffe37f89d0dd2d1019a8d390876dd7971328660 Mon Sep 17 00:00:00 2001 From: Tal Einat Date: Thu, 6 Sep 2018 14:12:28 +0300 Subject: [PATCH 5/5] bpo-30977: fix unpickling errors in tests --- Lib/test/test_uuid.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 8781c33d980a2b..9ec59d5195737f 100644 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -315,6 +315,8 @@ def test_getnode(self): def _setup_for_pickle(self): orig_uuid = sys.modules.get('uuid') + sys.modules['uuid'] = self.uuid + def restore_uuid_module(): if orig_uuid is not None: sys.modules['uuid'] = orig_uuid