mirror of https://github.com/python/cpython
gh-86682: Adds sys._getframemodulename as an alternative to using _getframe (GH-99520)
Also updates calls in collections, doctest, enum, and typing modules to use _getframemodulename first when available.
This commit is contained in:
parent
94fc7706b7
commit
b5d4347950
|
@ -808,6 +808,22 @@ always available.
|
|||
It is not guaranteed to exist in all implementations of Python.
|
||||
|
||||
|
||||
.. function:: _getframemodulename([depth])
|
||||
|
||||
Return the name of a module from the call stack. If optional integer *depth*
|
||||
is given, return the module that many calls below the top of the stack. If
|
||||
that is deeper than the call stack, or if the module is unidentifiable,
|
||||
``None`` is returned. The default for *depth* is zero, returning the
|
||||
module at the top of the call stack.
|
||||
|
||||
.. audit-event:: sys._getframemodulename depth sys._getframemodulename
|
||||
|
||||
.. impl-detail::
|
||||
|
||||
This function should be used for internal and specialized purposes only.
|
||||
It is not guaranteed to exist in all implementations of Python.
|
||||
|
||||
|
||||
.. function:: getprofile()
|
||||
|
||||
.. index::
|
||||
|
|
|
@ -159,7 +159,7 @@ import sys
|
|||
|
||||
try:
|
||||
from collections import namedtuple as _namedtuple
|
||||
DecimalTuple = _namedtuple('DecimalTuple', 'sign digits exponent')
|
||||
DecimalTuple = _namedtuple('DecimalTuple', 'sign digits exponent', module='decimal')
|
||||
except ImportError:
|
||||
DecimalTuple = lambda *args: args
|
||||
|
||||
|
|
|
@ -507,9 +507,12 @@ def namedtuple(typename, field_names, *, rename=False, defaults=None, module=Non
|
|||
# specified a particular module.
|
||||
if module is None:
|
||||
try:
|
||||
module = _sys._getframe(1).f_globals.get('__name__', '__main__')
|
||||
except (AttributeError, ValueError):
|
||||
pass
|
||||
module = _sys._getframemodulename(1) or '__main__'
|
||||
except AttributeError:
|
||||
try:
|
||||
module = _sys._getframe(1).f_globals.get('__name__', '__main__')
|
||||
except (AttributeError, ValueError):
|
||||
pass
|
||||
if module is not None:
|
||||
result.__module__ = module
|
||||
|
||||
|
|
|
@ -207,7 +207,13 @@ def _normalize_module(module, depth=2):
|
|||
elif isinstance(module, str):
|
||||
return __import__(module, globals(), locals(), ["*"])
|
||||
elif module is None:
|
||||
return sys.modules[sys._getframe(depth).f_globals['__name__']]
|
||||
try:
|
||||
try:
|
||||
return sys.modules[sys._getframemodulename(depth)]
|
||||
except AttributeError:
|
||||
return sys.modules[sys._getframe(depth).f_globals['__name__']]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
raise TypeError("Expected a module, string, or None")
|
||||
|
||||
|
|
12
Lib/enum.py
12
Lib/enum.py
|
@ -862,13 +862,15 @@ class EnumType(type):
|
|||
member_name, member_value = item
|
||||
classdict[member_name] = member_value
|
||||
|
||||
# TODO: replace the frame hack if a blessed way to know the calling
|
||||
# module is ever developed
|
||||
if module is None:
|
||||
try:
|
||||
module = sys._getframe(2).f_globals['__name__']
|
||||
except (AttributeError, ValueError, KeyError):
|
||||
pass
|
||||
module = sys._getframemodulename(2)
|
||||
except AttributeError:
|
||||
# Fall back on _getframe if _getframemodulename is missing
|
||||
try:
|
||||
module = sys._getframe(2).f_globals['__name__']
|
||||
except (AttributeError, ValueError, KeyError):
|
||||
pass
|
||||
if module is None:
|
||||
_make_class_unpicklable(classdict)
|
||||
else:
|
||||
|
|
|
@ -419,6 +419,17 @@ def test_sys_getframe():
|
|||
sys._getframe()
|
||||
|
||||
|
||||
def test_sys_getframemodulename():
|
||||
import sys
|
||||
|
||||
def hook(event, args):
|
||||
if event.startswith("sys."):
|
||||
print(event, *args)
|
||||
|
||||
sys.addaudithook(hook)
|
||||
sys._getframemodulename()
|
||||
|
||||
|
||||
def test_threading():
|
||||
import _thread
|
||||
|
||||
|
|
|
@ -186,6 +186,18 @@ class AuditTest(unittest.TestCase):
|
|||
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_sys_getframemodulename(self):
|
||||
returncode, events, stderr = self.run_python("test_sys_getframemodulename")
|
||||
if returncode:
|
||||
self.fail(stderr)
|
||||
|
||||
if support.verbose:
|
||||
print(*events, sep='\n')
|
||||
actual = [(ev[0], ev[2]) for ev in events]
|
||||
expected = [("sys._getframemodulename", "0")]
|
||||
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
|
||||
def test_threading(self):
|
||||
returncode, events, stderr = self.run_python("test_threading")
|
||||
|
|
|
@ -399,6 +399,26 @@ class SysModuleTest(unittest.TestCase):
|
|||
is sys._getframe().f_code
|
||||
)
|
||||
|
||||
def test_getframemodulename(self):
|
||||
# Default depth gets ourselves
|
||||
self.assertEqual(__name__, sys._getframemodulename())
|
||||
self.assertEqual("unittest.case", sys._getframemodulename(1))
|
||||
i = 0
|
||||
f = sys._getframe(i)
|
||||
while f:
|
||||
self.assertEqual(
|
||||
f.f_globals['__name__'],
|
||||
sys._getframemodulename(i) or '__main__'
|
||||
)
|
||||
i += 1
|
||||
f2 = f.f_back
|
||||
try:
|
||||
f = sys._getframe(i)
|
||||
except ValueError:
|
||||
break
|
||||
self.assertIs(f, f2)
|
||||
self.assertIsNone(sys._getframemodulename(i))
|
||||
|
||||
# sys._current_frames() is a CPython-only gimmick.
|
||||
@threading_helper.reap_threads
|
||||
@threading_helper.requires_working_threading()
|
||||
|
|
|
@ -1939,11 +1939,15 @@ def _no_init_or_replace_init(self, *args, **kwargs):
|
|||
|
||||
|
||||
def _caller(depth=1, default='__main__'):
|
||||
try:
|
||||
return sys._getframemodulename(depth + 1) or default
|
||||
except AttributeError: # For platforms without _getframemodulename()
|
||||
pass
|
||||
try:
|
||||
return sys._getframe(depth + 1).f_globals.get('__name__', default)
|
||||
except (AttributeError, ValueError): # For platforms without _getframe()
|
||||
return None
|
||||
|
||||
pass
|
||||
return None
|
||||
|
||||
def _allow_reckless_class_checks(depth=3):
|
||||
"""Allow instance and class checks for special stdlib modules.
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Ensure runtime-created collections have the correct module name using
|
||||
the newly added (internal) :func:`sys._getframemodulename`.
|
|
@ -93,7 +93,10 @@ _PyFunction_FromConstructor(PyFrameConstructor *constr)
|
|||
op->func_doc = Py_NewRef(Py_None);
|
||||
op->func_dict = NULL;
|
||||
op->func_weakreflist = NULL;
|
||||
op->func_module = NULL;
|
||||
op->func_module = Py_XNewRef(PyDict_GetItem(op->func_globals, &_Py_ID(__name__)));
|
||||
if (!op->func_module) {
|
||||
PyErr_Clear();
|
||||
}
|
||||
op->func_annotations = NULL;
|
||||
op->vectorcall = _PyFunction_Vectorcall;
|
||||
op->func_version = 0;
|
||||
|
|
|
@ -1275,6 +1275,75 @@ sys_is_stack_trampoline_active(PyObject *module, PyObject *Py_UNUSED(ignored))
|
|||
return sys_is_stack_trampoline_active_impl(module);
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(sys__getframemodulename__doc__,
|
||||
"_getframemodulename($module, /, depth=0)\n"
|
||||
"--\n"
|
||||
"\n"
|
||||
"Return the name of the module for a calling frame.\n"
|
||||
"\n"
|
||||
"The default depth returns the module containing the call to this API.\n"
|
||||
"A more typical use in a library will pass a depth of 1 to get the user\'s\n"
|
||||
"module rather than the library module.\n"
|
||||
"\n"
|
||||
"If no frame, module, or name can be found, returns None.");
|
||||
|
||||
#define SYS__GETFRAMEMODULENAME_METHODDEF \
|
||||
{"_getframemodulename", _PyCFunction_CAST(sys__getframemodulename), METH_FASTCALL|METH_KEYWORDS, sys__getframemodulename__doc__},
|
||||
|
||||
static PyObject *
|
||||
sys__getframemodulename_impl(PyObject *module, int depth);
|
||||
|
||||
static PyObject *
|
||||
sys__getframemodulename(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 1
|
||||
static struct {
|
||||
PyGC_Head _this_is_not_used;
|
||||
PyObject_VAR_HEAD
|
||||
PyObject *ob_item[NUM_KEYWORDS];
|
||||
} _kwtuple = {
|
||||
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
|
||||
.ob_item = { &_Py_ID(depth), },
|
||||
};
|
||||
#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[] = {"depth", NULL};
|
||||
static _PyArg_Parser _parser = {
|
||||
.keywords = _keywords,
|
||||
.fname = "_getframemodulename",
|
||||
.kwtuple = KWTUPLE,
|
||||
};
|
||||
#undef KWTUPLE
|
||||
PyObject *argsbuf[1];
|
||||
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
|
||||
int depth = 0;
|
||||
|
||||
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 1, 0, argsbuf);
|
||||
if (!args) {
|
||||
goto exit;
|
||||
}
|
||||
if (!noptargs) {
|
||||
goto skip_optional_pos;
|
||||
}
|
||||
depth = _PyLong_AsInt(args[0]);
|
||||
if (depth == -1 && PyErr_Occurred()) {
|
||||
goto exit;
|
||||
}
|
||||
skip_optional_pos:
|
||||
return_value = sys__getframemodulename_impl(module, depth);
|
||||
|
||||
exit:
|
||||
return return_value;
|
||||
}
|
||||
|
||||
#ifndef SYS_GETWINDOWSVERSION_METHODDEF
|
||||
#define SYS_GETWINDOWSVERSION_METHODDEF
|
||||
#endif /* !defined(SYS_GETWINDOWSVERSION_METHODDEF) */
|
||||
|
@ -1318,4 +1387,4 @@ sys_is_stack_trampoline_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=b32b444538dfd354 input=a9049054013a1b77]*/
|
||||
/*[clinic end generated code: output=5c761f14326ced54 input=a9049054013a1b77]*/
|
||||
|
|
|
@ -2172,6 +2172,43 @@ sys_is_stack_trampoline_active_impl(PyObject *module)
|
|||
}
|
||||
|
||||
|
||||
/*[clinic input]
|
||||
sys._getframemodulename
|
||||
|
||||
depth: int = 0
|
||||
|
||||
Return the name of the module for a calling frame.
|
||||
|
||||
The default depth returns the module containing the call to this API.
|
||||
A more typical use in a library will pass a depth of 1 to get the user's
|
||||
module rather than the library module.
|
||||
|
||||
If no frame, module, or name can be found, returns None.
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
sys__getframemodulename_impl(PyObject *module, int depth)
|
||||
/*[clinic end generated code: output=1d70ef691f09d2db input=d4f1a8ed43b8fb46]*/
|
||||
{
|
||||
if (PySys_Audit("sys._getframemodulename", "i", depth) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
_PyInterpreterFrame *f = _PyThreadState_GET()->cframe->current_frame;
|
||||
while (f && (_PyFrame_IsIncomplete(f) || depth-- > 0)) {
|
||||
f = f->previous;
|
||||
}
|
||||
if (f == NULL || f->f_funcobj == NULL) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
PyObject *r = PyFunction_GetModule(f->f_funcobj);
|
||||
if (!r) {
|
||||
PyErr_Clear();
|
||||
r = Py_None;
|
||||
}
|
||||
return Py_NewRef(r);
|
||||
}
|
||||
|
||||
|
||||
static PyMethodDef sys_methods[] = {
|
||||
/* Might as well keep this in alphabetic order */
|
||||
SYS_ADDAUDITHOOK_METHODDEF
|
||||
|
@ -2200,6 +2237,7 @@ static PyMethodDef sys_methods[] = {
|
|||
{"getsizeof", _PyCFunction_CAST(sys_getsizeof),
|
||||
METH_VARARGS | METH_KEYWORDS, getsizeof_doc},
|
||||
SYS__GETFRAME_METHODDEF
|
||||
SYS__GETFRAMEMODULENAME_METHODDEF
|
||||
SYS_GETWINDOWSVERSION_METHODDEF
|
||||
SYS__ENABLELEGACYWINDOWSFSENCODING_METHODDEF
|
||||
SYS_INTERN_METHODDEF
|
||||
|
|
Loading…
Reference in New Issue