diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 83745f3d0ba46e..b932c860b9f25e 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -753,23 +753,25 @@ def test_43581(self): self.assertEqual(sys.__stdout__.encoding, sys.__stderr__.encoding) def test_intern(self): - has_is_interned = (test.support.check_impl_detail(cpython=True) - or hasattr(sys, '_is_interned')) self.assertRaises(TypeError, sys.intern) self.assertRaises(TypeError, sys.intern, b'abc') + has_is_interned = (test.support.check_impl_detail(cpython=True) + or hasattr(sys, '_is_interned')) if has_is_interned: self.assertRaises(TypeError, sys._is_interned) self.assertRaises(TypeError, sys._is_interned, b'abc') + + def _is_interned(obj): + tags = sys.get_object_tags(obj) + return tags["interned"] + s = "never interned before" + str(random.randrange(0, 10**9)) self.assertTrue(sys.intern(s) is s) - if has_is_interned: - self.assertIs(sys._is_interned(s), True) + self.assertIs(_is_interned(s), True) s2 = s.swapcase().swapcase() - if has_is_interned: - self.assertIs(sys._is_interned(s2), False) + self.assertIs(_is_interned(s2), False) self.assertTrue(sys.intern(s2) is s) - if has_is_interned: - self.assertIs(sys._is_interned(s2), False) + self.assertIs(_is_interned(s2), False) # Subclasses of string can't be interned, because they # provide too much opportunity for insane things to happen. @@ -781,8 +783,28 @@ def __hash__(self): return 123 self.assertRaises(TypeError, sys.intern, S("abc")) - if has_is_interned: - self.assertIs(sys._is_interned(S("abc")), False) + self.assertIs(_is_interned(S("abc")), False) + + @support.cpython_only + def test_get_object_tags(self): + keys = ("immortal", "interned", "deferred_refcount") + s = "foobar" + tags = sys.get_object_tags(s) + self.assertEqual(len(tags), len(keys)) + for k in keys: + self.assertIn(k, tags) + + @support.cpython_only + def test_set_object_tags(self): + keys = ("immortal", "interned", "deferred_refcount") + s = "should never interned before" + str(random.randrange(0, 10**9)) + origin_tags = sys.get_object_tags(s) + for k in keys: + self.assertFalse(origin_tags[k]) + sys.set_object_tag(s, k) + sys.set_object_tag(s, "unknown") + after_tags = sys.get_object_tags(s) + self.assertEqual(len(origin_tags), len(after_tags)) @support.cpython_only @requires_subinterpreters @@ -847,7 +869,8 @@ def test_subinterp_intern_singleton(self): assert id(s) == {id(s)} t = sys.intern(s) ''')) - self.assertTrue(sys._is_interned(s)) + tags = sys.get_object_tags(s) + self.assertTrue(tags["interned"]) def test_sys_flags(self): self.assertTrue(sys.flags) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-03-21-55-03.gh-issue-134819.M9PZZc.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-03-21-55-03.gh-issue-134819.M9PZZc.rst new file mode 100644 index 00000000000000..89b2531a5110df --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-03-21-55-03.gh-issue-134819.M9PZZc.rst @@ -0,0 +1,2 @@ +Add :func:`sys.get_object_tags` and :func:`sys.set_object_tags` for handling +CPython object implementation detail. Patch By Donghee Na. diff --git a/Python/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h index a47e4d11b54441..c184e6be34caad 100644 --- a/Python/clinic/sysmodule.c.h +++ b/Python/clinic/sysmodule.c.h @@ -405,6 +405,95 @@ sys__is_immortal(PyObject *module, PyObject *op) return return_value; } +PyDoc_STRVAR(sys_get_object_tags__doc__, +"get_object_tags($module, op, /)\n" +"--\n" +"\n" +"Return the tags of the given object."); + +#define SYS_GET_OBJECT_TAGS_METHODDEF \ + {"get_object_tags", (PyCFunction)sys_get_object_tags, METH_O, sys_get_object_tags__doc__}, + +PyDoc_STRVAR(sys_set_object_tag__doc__, +"set_object_tag($module, /, object, tag, *, options=None)\n" +"--\n" +"\n" +"Set the tags of the given object."); + +#define SYS_SET_OBJECT_TAG_METHODDEF \ + {"set_object_tag", _PyCFunction_CAST(sys_set_object_tag), METH_FASTCALL|METH_KEYWORDS, sys_set_object_tag__doc__}, + +static PyObject * +sys_set_object_tag_impl(PyObject *module, PyObject *object, const char *tag, + PyObject *options); + +static PyObject * +sys_set_object_tag(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 3 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + Py_hash_t ob_hash; + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_hash = -1, + .ob_item = { &_Py_ID(object), &_Py_ID(tag), &_Py_ID(options), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"object", "tag", "options", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "set_object_tag", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[3]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 2; + PyObject *object; + const char *tag; + PyObject *options = Py_None; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + object = args[0]; + if (!PyUnicode_Check(args[1])) { + _PyArg_BadArgument("set_object_tag", "argument 'tag'", "str", args[1]); + goto exit; + } + Py_ssize_t tag_length; + tag = PyUnicode_AsUTF8AndSize(args[1], &tag_length); + if (tag == NULL) { + goto exit; + } + if (strlen(tag) != (size_t)tag_length) { + PyErr_SetString(PyExc_ValueError, "embedded null character"); + goto exit; + } + if (!noptargs) { + goto skip_optional_kwonly; + } + options = args[2]; +skip_optional_kwonly: + return_value = sys_set_object_tag_impl(module, object, tag, options); + +exit: + return return_value; +} + PyDoc_STRVAR(sys_settrace__doc__, "settrace($module, function, /)\n" "--\n" @@ -1948,4 +2037,4 @@ _jit_is_active(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef SYS_GETANDROIDAPILEVEL_METHODDEF #define SYS_GETANDROIDAPILEVEL_METHODDEF #endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */ -/*[clinic end generated code: output=449d16326e69dcf6 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=cf56a851495bb951 input=a9049054013a1b77]*/ diff --git a/Python/sysmodule.c b/Python/sysmodule.c index e5ae841d195d4f..f5933ea1bdac73 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -18,6 +18,7 @@ Data members: #include "pycore_audit.h" // _Py_AuditHookEntry #include "pycore_call.h" // _PyObject_CallNoArgs() #include "pycore_ceval.h" // _PyEval_SetAsyncGenFinalizer() +#include "pycore_object_deferred.h" // _PyObject_HasDeferredRefcount #include "pycore_frame.h" // _PyInterpreterFrame #include "pycore_import.h" // _PyImport_SetDLOpenFlags() #include "pycore_initconfig.h" // _PyStatus_EXCEPTION() @@ -1052,6 +1053,70 @@ sys__is_immortal_impl(PyObject *module, PyObject *op) return PyUnstable_IsImmortal(op); } + +/*[clinic input] +sys.get_object_tags -> object + + op: object + / +Return the tags of the given object. +[clinic start generated code]*/ + +static PyObject * +sys_get_object_tags(PyObject *module, PyObject *op) +/*[clinic end generated code: output=a68da7f1805c9216 input=75993fb67096e2ff]*/ +{ + assert(op != NULL); + PyObject *dict = PyDict_New(); + if (dict == NULL) { + return NULL; + } + + if (PyDict_SetItemString(dict, "immortal", PyBool_FromLong(PyUnstable_IsImmortal(op))) < 0) { + Py_DECREF(dict); + return NULL; + } + + if (PyDict_SetItemString(dict, "interned", PyBool_FromLong((PyUnicode_Check(op) && PyUnicode_CHECK_INTERNED(op)))) < 0) { + Py_DECREF(dict); + return NULL; + } + + if (PyDict_SetItemString(dict, "deferred_refcount", PyBool_FromLong(_PyObject_HasDeferredRefcount(op))) < 0) { + Py_DECREF(dict); + return NULL; + } + + return dict; +} + +/*[clinic input] +sys.set_object_tag -> object + + object: object + tag: str + * + options: object = None + +Set the tags of the given object. +[clinic start generated code]*/ + +static PyObject * +sys_set_object_tag_impl(PyObject *module, PyObject *object, const char *tag, + PyObject *options) +/*[clinic end generated code: output=b0fb5e9931feb4aa input=b64c9bd958c75f11]*/ +{ + assert(object != NULL); + if (strcmp(tag, "interned") == 0) { + Py_INCREF(object); + _PyUnicode_InternMortal(_PyInterpreterState_GET(), &object); + } + else if(strcmp(tag, "deferred_refcount") == 0) { + PyUnstable_Object_EnableDeferredRefcount(object); + } + Py_RETURN_NONE; +} + /* * Cached interned string objects used for calling the profile and * trace functions. @@ -2796,6 +2861,8 @@ static PyMethodDef sys_methods[] = { SYS__IS_IMMORTAL_METHODDEF SYS_INTERN_METHODDEF SYS__IS_INTERNED_METHODDEF + SYS_GET_OBJECT_TAGS_METHODDEF + SYS_SET_OBJECT_TAG_METHODDEF SYS_IS_FINALIZING_METHODDEF SYS_MDEBUG_METHODDEF SYS_SETSWITCHINTERVAL_METHODDEF pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy