From 82f3279ee9028bcd32d091f5a57acce358ee2abf Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Mon, 27 Oct 2025 13:32:13 +0200 Subject: [PATCH] Add C implementation of UUID stringification --- Lib/test/test_uuid.py | 4 ++ Lib/uuid.py | 6 +++ ...-10-27-13-17-17.gh-issue-131196.tYkQbd.rst | 2 + Modules/_uuidmodule.c | 45 ++++++++++++++++--- 4 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-10-27-13-17-17.gh-issue-131196.tYkQbd.rst diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 33045a78721aac..75a90f7776be02 100755 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -224,6 +224,10 @@ def test_UUID(self): self.uuid.UUID(urn)]: # Test all conversions and properties of the UUID object. equal(str(u), string) + if hasattr(self.uuid, "_c_uuid_str_method"): + # If we have the C accelerator, test both implementations. + equal(self.uuid._py_uuid_str_method(u), string) + equal(self.uuid._c_uuid_str_method(u), string) equal(int(u), integer) equal(u.bytes, bytes) equal(u.bytes_le, bytes_le) diff --git a/Lib/uuid.py b/Lib/uuid.py index c0150a59d7cb9a..887e6e1fd046d8 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -635,6 +635,12 @@ def _netstat_getnode(): _generate_time_safe = getattr(_uuid, "generate_time_safe", None) _has_stable_extractable_node = _uuid.has_stable_extractable_node _UuidCreate = getattr(_uuid, "UuidCreate", None) + _uuid_int_to_str = getattr(_uuid, "uuid_int_to_str", None) + _py_uuid_str_method = UUID.__str__ + if _uuid_int_to_str is not None: + def _c_uuid_str_method(self): + return _uuid_int_to_str(self.int) + UUID.__str__ = _c_uuid_str_method except ImportError: _uuid = None _generate_time_safe = None diff --git a/Misc/NEWS.d/next/Library/2025-10-27-13-17-17.gh-issue-131196.tYkQbd.rst b/Misc/NEWS.d/next/Library/2025-10-27-13-17-17.gh-issue-131196.tYkQbd.rst new file mode 100644 index 00000000000000..63d02b2a6b0a03 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-10-27-13-17-17.gh-issue-131196.tYkQbd.rst @@ -0,0 +1,2 @@ +Improve performance of :meth:`uuid.UUID.__str__ ` by adding +a C implementation in _uuidmodule. diff --git a/Modules/_uuidmodule.c b/Modules/_uuidmodule.c index c31a7e8fea5608..685d9e96b88fe9 100644 --- a/Modules/_uuidmodule.c +++ b/Modules/_uuidmodule.c @@ -3,13 +3,9 @@ * DCE compatible Universally Unique Identifier library. */ -// Need limited C API version 3.13 for Py_mod_gil -#include "pyconfig.h" // Py_GIL_DISABLED -#ifndef Py_GIL_DISABLED -# define Py_LIMITED_API 0x030d0000 -#endif - +#define Py_BUILD_CORE_MODULE #include "Python.h" +#include "pycore_long.h" // _PyLong_AsByteArray #if defined(HAVE_UUID_H) // AIX, FreeBSD, libuuid with pkgconf #include @@ -90,6 +86,42 @@ py_windows_has_stable_node(void) } #endif /* MS_WINDOWS */ +static PyObject * +py_uuid_int_to_str(PyObject *Py_UNUSED(context), PyObject *uuid_int) +{ + if (!PyLong_Check(uuid_int)) { + PyErr_SetString(PyExc_TypeError, "uuid_int must be an integer"); + return NULL; + } + + unsigned char bytes[16]; + if (_PyLong_AsByteArray((PyLongObject *)uuid_int, bytes, 16, 0, 0, 1) < 0) { + return NULL; + } + + PyObject *result = PyUnicode_New(36, 127); + if (result == NULL) { + return NULL; + } + assert(PyUnicode_KIND(result) == PyUnicode_1BYTE_KIND); + Py_UCS1 *str = PyUnicode_1BYTE_DATA(result); + static const Py_UCS1 hex_digits[] = "0123456789abcdef"; + + int pos = 0; + for (int i = 0; i < 16; i++) { + if (i == 4 || i == 6 || i == 8 || i == 10) { + str[pos++] = '-'; + } + unsigned char byte = bytes[i]; + str[pos++] = hex_digits[byte >> 4]; + str[pos++] = hex_digits[byte & 0x0f]; + } +#ifdef Py_DEBUG + assert(pos == 36); + assert(_PyUnicode_CheckConsistency(result, 1)); +#endif + return result; +} static int uuid_exec(PyObject *module) @@ -129,6 +161,7 @@ static PyMethodDef uuid_methods[] = { #if defined(MS_WINDOWS) {"UuidCreate", py_UuidCreate, METH_NOARGS, NULL}, #endif + {"uuid_int_to_str", py_uuid_int_to_str, METH_O, NULL}, {NULL, NULL, 0, NULL} /* sentinel */ };