Skip to content

Commit 58d07be

Browse files
committed
Add -X importtime=2 for logging an importtime message for already-loaded modules
1 parent c141340 commit 58d07be

File tree

12 files changed

+163
-31
lines changed

12 files changed

+163
-31
lines changed

Doc/c-api/init_config.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1477,9 +1477,10 @@ PyConfig
14771477
14781478
.. c:member:: int import_time
14791479
1480-
If non-zero, profile import time.
1480+
If non-zero, profile import time. If ``2``, include additional output that
1481+
indicates when an imported module has already been loaded.
14811482
1482-
Set the ``1`` by the :option:`-X importtime <-X>` option and the
1483+
Set by the :option:`-X importtime <-X>` option and the
14831484
:envvar:`PYTHONPROFILEIMPORTTIME` environment variable.
14841485
14851486
Default: ``0``.

Doc/using/cmdline.rst

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -542,8 +542,17 @@ Miscellaneous options
542542
application. Typical usage is ``python3 -X importtime -c 'import
543543
asyncio'``. See also :envvar:`PYTHONPROFILEIMPORTTIME`.
544544

545+
``-X importtime=2`` enables additional output that indicates when an
546+
imported module has already been loaded. In such cases, the string
547+
``cached`` will be printed in the self time and cumulative time columns.
548+
545549
.. versionadded:: 3.7
546550

551+
.. versionchanged:: next
552+
553+
Added ``-X importtime=2`` to also trace imports of loaded modules,
554+
and reserved values other than ``1`` and ``2`` for future use.
555+
547556
* ``-X dev``: enable :ref:`Python Development Mode <devmode>`, introducing
548557
additional runtime checks that are too expensive to be enabled by
549558
default. See also :envvar:`PYTHONDEVMODE`.
@@ -984,12 +993,17 @@ conflict.
984993

985994
.. envvar:: PYTHONPROFILEIMPORTTIME
986995

987-
If this environment variable is set to a non-empty string, Python will
988-
show how long each import takes.
996+
If this environment variable is set to ``1``, Python will show
997+
how long each import takes. If set to ``2``, Python will include output for
998+
imported modules that have already been loaded.
989999
This is equivalent to setting the :option:`-X` ``importtime`` option.
9901000

9911001
.. versionadded:: 3.7
9921002

1003+
.. versionchanged:: next
1004+
1005+
Added ``PYTHONPROFILEIMPORTTIME=2`` to also trace imports of loaded modules.
1006+
9931007

9941008
.. envvar:: PYTHONASYNCIODEBUG
9951009

Doc/whatsnew/3.14.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,13 @@ Other language changes
626626
of HMAC is not available.
627627
(Contributed by Bénédikt Tran in :gh:`99108`.)
628628

629+
* :option:`-X importtime <-X>` now accepts value ``2``, which indicates that
630+
an ``importtime`` entry should also be printed if an imported module has
631+
already been loaded. The ``self`` and ``cumulative`` times for such entries
632+
are replaced by the string ``cached``.
633+
Values above ``2`` are now reserved for future use.
634+
(Contributed by Noah Kim in :gh:`118655`.)
635+
629636
* When subclassing from a pure C type, the C slots for the new type are no
630637
longer replaced with a wrapped version on class creation if they are not
631638
explicitly overridden in the subclass.

Lib/_pyrepl/unix_console.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import struct
3030
import termios
3131
import time
32+
import types
3233
import platform
3334
from fcntl import ioctl
3435

@@ -39,6 +40,13 @@
3940
from .unix_eventqueue import EventQueue
4041
from .utils import wlen
4142

43+
# declare posix optional to allow None assignment on other platforms
44+
posix: types.ModuleType | None
45+
46+
try:
47+
import posix
48+
except ImportError:
49+
posix = None
4250

4351
TYPE_CHECKING = False
4452

@@ -566,11 +574,9 @@ def clear(self):
566574

567575
@property
568576
def input_hook(self):
569-
try:
570-
import posix
571-
except ImportError:
572-
return None
573-
if posix._is_inputhook_installed():
577+
# avoid inline imports here so the repl doesn't get flooded with import
578+
# logging from -Ximporttime=2
579+
if posix is not None and posix._is_inputhook_installed():
574580
return posix._inputhook
575581

576582
def __enable_bracketed_paste(self) -> None:

Lib/_pyrepl/windows_console.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import sys
2525

2626
import ctypes
27+
import types
2728
from ctypes.wintypes import (
2829
_COORD,
2930
WORD,
@@ -58,6 +59,12 @@ def __init__(self, err: int | None, descr: str | None = None) -> None:
5859
self.err = err
5960
self.descr = descr
6061

62+
nt: types.ModuleType | None
63+
64+
try:
65+
import nt
66+
except ImportError:
67+
nt = None
6168

6269
TYPE_CHECKING = False
6370

@@ -121,9 +128,8 @@ class _error(Exception):
121128

122129
def _supports_vt():
123130
try:
124-
import nt
125-
return nt._supports_virtual_terminal()
126-
except (ImportError, AttributeError):
131+
nt._supports_virtual_terminal()
132+
except AttributeError:
127133
return False
128134

129135
class WindowsConsole(Console):
@@ -235,11 +241,9 @@ def refresh(self, screen: list[str], c_xy: tuple[int, int]) -> None:
235241

236242
@property
237243
def input_hook(self):
238-
try:
239-
import nt
240-
except ImportError:
241-
return None
242-
if nt._is_inputhook_installed():
244+
# avoid inline imports here so the repl doesn't get flooded with import
245+
# logging from -Ximporttime=2
246+
if nt is not None and nt._is_inputhook_installed():
243247
return nt._inputhook
244248

245249
def __write_changed_line(

Lib/test/test_capi/test_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def test_config_get(self):
5757
("home", str | None, None),
5858
("thread_inherit_context", int, None),
5959
("context_aware_warnings", int, None),
60-
("import_time", bool, None),
60+
("import_time", int, None),
6161
("inspect", bool, None),
6262
("install_signal_handlers", bool, None),
6363
("int_max_str_digits", int, None),

Lib/test/test_cmd_line.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1158,6 +1158,28 @@ def test_cpu_count_default(self):
11581158
res = assert_python_ok('-c', code, PYTHON_CPU_COUNT='default')
11591159
self.assertEqual(self.res2int(res), (os.cpu_count(), os.process_cpu_count()))
11601160

1161+
def test_import_time(self):
1162+
code = "import os"
1163+
res = assert_python_ok('-X', 'importtime', '-c', code)
1164+
res_err = res.err.decode("utf-8")
1165+
self.assertRegex(res_err, r"import time: \s*\d+ \| \s*\d+ \| \s*os")
1166+
self.assertNotRegex(res_err, r"import time: cached\s* \| cached\s* \| os")
1167+
1168+
code = "import os"
1169+
res = assert_python_ok('-X', 'importtime=true', '-c', code)
1170+
res_err = res.err.decode("utf-8")
1171+
self.assertRegex(res_err, r"import time: \s*\d+ \| \s*\d+ \| \s*os")
1172+
self.assertNotRegex(res_err, r"import time: cached\s* \| cached\s* \| os")
1173+
1174+
code = "import os; import os"
1175+
res = assert_python_ok('-X', 'importtime=2', '-c', code)
1176+
res_err = res.err.decode("utf-8")
1177+
self.assertRegex(res_err, r"import time: \s*\d+ \| \s*\d+ \| \s*os")
1178+
self.assertRegex(res_err, r"import time: cached\s* \| cached\s* \| os")
1179+
1180+
assert_python_failure('-X', 'importtime=-1', '-c', code)
1181+
assert_python_failure('-X', 'importtime=3', '-c', code)
1182+
11611183
def res2int(self, res):
11621184
out = res.out.strip().decode("utf-8")
11631185
return tuple(int(i) for i in out.split())

Lib/test/test_embed.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
585585
'faulthandler': False,
586586
'tracemalloc': 0,
587587
'perf_profiling': 0,
588-
'import_time': False,
588+
'import_time': 0,
589589
'thread_inherit_context': DEFAULT_THREAD_INHERIT_CONTEXT,
590590
'context_aware_warnings': DEFAULT_CONTEXT_AWARE_WARNINGS,
591591
'code_debug_ranges': True,
@@ -998,7 +998,7 @@ def test_init_from_config(self):
998998
'hash_seed': 123,
999999
'tracemalloc': 2,
10001000
'perf_profiling': 0,
1001-
'import_time': True,
1001+
'import_time': 0,
10021002
'code_debug_ranges': False,
10031003
'show_ref_count': True,
10041004
'malloc_stats': True,
@@ -1064,7 +1064,7 @@ def test_init_compat_env(self):
10641064
'use_hash_seed': True,
10651065
'hash_seed': 42,
10661066
'tracemalloc': 2,
1067-
'import_time': True,
1067+
'import_time': 0,
10681068
'code_debug_ranges': False,
10691069
'malloc_stats': True,
10701070
'inspect': True,
@@ -1100,7 +1100,7 @@ def test_init_python_env(self):
11001100
'use_hash_seed': True,
11011101
'hash_seed': 42,
11021102
'tracemalloc': 2,
1103-
'import_time': True,
1103+
'import_time': 0,
11041104
'code_debug_ranges': False,
11051105
'malloc_stats': True,
11061106
'inspect': True,
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:option:`-X importtime <-X>` now accepts value ``2``, which indicates that
2+
an ``importtime`` entry should also be printed if an imported module has
3+
already been loaded.

Programs/_testembed.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -652,7 +652,7 @@ static int test_init_from_config(void)
652652
config.tracemalloc = 2;
653653

654654
putenv("PYTHONPROFILEIMPORTTIME=0");
655-
config.import_time = 1;
655+
config.import_time = 0;
656656

657657
putenv("PYTHONNODEBUGRANGES=0");
658658
config.code_debug_ranges = 0;

0 commit comments

Comments
 (0)