8000 gh-86682: Adds sys._getframemodulename as an alternative to using _ge… · python/cpython@b5d4347 · GitHub
[go: up one dir, main page]

Skip to content

Commit b5d4347

Browse files
authored
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.
1 parent 94fc770 commit b5d4347

File tree

13 files changed

+200
-14
lines changed

13 files changed

+200
-14
lines changed

Doc/library/sys.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,22 @@ always available.
808808
It is not guaranteed to exist in all implementations of Python.
809809

810810

811+
.. function:: _getframemodulename([depth])
812+
813+
Return the name of a module from the call stack. If optional integer *depth*
814+
is given, return the module that many calls below the top of the stack. If
815+
that is deeper than the call stack, or if the module is unidentifiable,
816+
``None`` is returned. The default for *depth* is zero, returning the
817+
module at the top of the call stack.
818+
819+
.. audit-event:: sys._getframemodulename depth sys._getframemodulename
820+
821+
.. impl-detail::
822+
823+
This function should be used for internal and specialized purposes only.
824+
It is not guaranteed to exist in all implementations of Python.
825+
826+
811827
.. function:: getprofile()
812828

813829
.. index::

Lib/_pydecimal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@
159159

160160
try:
161161
from collections import namedtuple as _namedtuple
162-
DecimalTuple = _namedtuple('DecimalTuple', 'sign digits exponent')
162+
DecimalTuple = _namedtuple('DecimalTuple', 'sign digits exponent', module='decimal')
163163
except ImportError:
164164
DecimalTuple = lambda *args: args
165165

Lib/collections/__init__.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -507,9 +507,12 @@ def __getnewargs__(self):
507507
# specified a particular module.
508508
if module is None:
509509
try:
510-
module = _sys._getframe(1).f_globals.get('__name__', '__main__')
511-
except (AttributeError, ValueError):
512-
pass
510+
module = _sys._getframemodulename(1) or '__main__'
511+
except AttributeError:
512+
try:
513+
module = _sys._getframe(1).f_globals.get('__name__', '__main__')
514+
except (AttributeError, ValueError):
515+
pass
513516
if module is not None:
514517
result.__module__ = module
515518

Lib/doctest.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,13 @@ def _normalize_module(module, depth=2):
207207
elif isinstance(module, str):
208208
return __import__(module, globals(), locals(), ["*"])
209209
elif module is None:
210-
return sys.modules[sys._getframe(depth).f_globals['__name__']]
210+
try:
211+
try:
212+
return sys.modules[sys._getframemodulename(depth)]
213+
except AttributeError:
214+
return sys.modules[sys._getframe(depth).f_globals['__name__']]
215+
except KeyError:
216+
pass
211217
else:
212218
raise TypeError("Expected a module, string, or None")
213219

Lib/enum.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -862,13 +862,15 @@ def _create_(cls, class_name, names, *, module=None, qualname=None, type=None, s
862862
member_name, member_value = item
863863
classdict[member_name] = member_value
864864

865-
# TODO: replace the frame hack if a blessed way to know the calling
866-
# module is ever developed
867865
if module is None:
868866
try:
869-
module = sys._getframe(2).f_globals['__name__']
870-
except (AttributeError, ValueError, KeyError):
871-
pass
867+
module = sys._getframemodulename(2)
868+
except AttributeError:
869+
# Fall back on _getframe if _getframemodulename is missing
870+
try:
871+
module = sys._getframe(2).f_globals['__name__']
872+
except (AttributeError, ValueError, KeyError):
873+
pass
872874
if module is None:
873875
_make_class_unpicklable(classdict)
874876
else:

Lib/test/audit-tests.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,17 @@ def hook(event, args):
419419
sys._getframe()
420420

421421

422+
def test_sys_getframemodulename():
423+
import sys
424+
425+
def hook(event, args):
426+
if event.startswith("sys."):
427+
print(event, *args)
428+
429+
sys.addaudithook(hook)
430+
sys._getframemodulename()
431+
432+
422433
def test_threading():
423434 F438
import _thread
424435

Lib/test/test_audit.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,18 @@ def test_sys_getframe(self):
186186

187187
self.assertEqual(actual, expected)
188188

189+
def test_sys_getframemodulename(self):
190+
returncode, events, stderr = self.run_python("test_sys_getframemodulename")
191+
if returncode:
192+
self.fail(stderr)
193+
194+
if support.verbose:
195+
print(*events, sep='\n')
196+
actual = [(ev[0], ev[2]) for ev in events]
197+
expected = [("sys._getframemodulename", "0")]
198+
199+
self.assertEqual(actual, expected)
200+
189201

190202
def test_threading(self):
191203
returncode, events, stderr = self.run_python("test_threading")

Lib/test/test_sys.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,26 @@ def test_getframe(self):
399399
is sys._getframe().f_code
400400
)
401401

402+
def test_getframemodulename(self):
403+
# Default depth gets ourselves
404+
self.assertEqual(__name__, sys._getframemodulename())
405+
self.assertEqual("unittest.case", sys._getframemodulename(1))
406+
i = 0
407+
f = sys._getframe(i)
408+
while f:
409+
self.assertEqual(
410+
f.f_globa 10000 ls['__name__'],
411+
sys._getframemodulename(i) or '__main__'
412+
)
413+
i += 1
414+
f2 = f.f_back
415+
try:
416+
f = sys._getframe(i)
417+
except ValueError:
418+
break
419+
self.assertIs(f, f2)
420+
self.assertIsNone(sys._getframemodulename(i))
421+
402422
# sys._current_frames() is a CPython-only gimmick.
403423
@threading_helper.reap_threads
404424
@threading_helper.requires_working_threading()

Lib/typing.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1939,11 +1939,15 @@ def _no_init_or_replace_init(self, *args, **kwargs):
19391939

19401940

19411941
def _caller(depth=1, default='__main__'):
1942+
try:
1943+
return sys._getframemodulename(depth + 1) or default
1944+
except AttributeError: # For platforms without _getframemodulename()
1945+
pass
19421946
try:
19431947
return sys._getframe(depth + 1).f_globals.get('__name__', default)
19441948
except (AttributeError, ValueError): # For platforms without _getframe()
1945-
return None
1946-
1949+
pass
1950+
return None
19471951

19481952
def _allow_reckless_class_checks(depth=3):
19491953
"""Allow instance and class checks for special stdlib modules.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Ensure runtime-created collections have the correct module name using
2+
the newly added (internal) :func:`sys._getframemodulename`.

0 commit comments

Comments
 (0)
0