diff --git a/Doc/c-api/import.rst b/Doc/c-api/import.rst index 1786ac6b503895..a28c0713dd3b2f 100644 --- a/Doc/c-api/import.rst +++ b/Doc/c-api/import.rst @@ -129,8 +129,7 @@ Importing Modules of :class:`~importlib.machinery.SourceFileLoader` otherwise. The module's :attr:`~module.__file__` attribute will be set to the code - object's :attr:`~codeobject.co_filename`. If applicable, - :attr:`~module.__cached__` will also be set. + object's :attr:`~codeobject.co_filename`. This function will reload the module if it was already imported. See :c:func:`PyImport_ReloadModule` for the intended way to reload a module. @@ -142,10 +141,13 @@ Importing Modules :c:func:`PyImport_ExecCodeModuleWithPathnames`. .. versionchanged:: 3.12 - The setting of :attr:`~module.__cached__` and :attr:`~module.__loader__` + The setting of ``__cached__`` and :attr:`~module.__loader__` is deprecated. See :class:`~importlib.machinery.ModuleSpec` for alternatives. + .. versionchanged:: 3.15 + ``__cached__`` is no longer set. + .. c:function:: PyObject* PyImport_ExecCodeModuleEx(const char *name, PyObject *co, const char *pathname) @@ -157,16 +159,19 @@ Importing Modules .. c:function:: PyObject* PyImport_ExecCodeModuleObject(PyObject *name, PyObject *co, PyObject *pathname, PyObject *cpathname) - Like :c:func:`PyImport_ExecCodeModuleEx`, but the :attr:`~module.__cached__` - attribute of the module object is set to *cpathname* if it is - non-``NULL``. Of the three functions, this is the preferred one to use. + Like :c:func:`PyImport_ExecCodeModuleEx`, but the path to any compiled file + via *cpathname* is used appropriately when non-``NULL``. Of the three + functions, this is the preferred one to use. .. versionadded:: 3.3 .. versionchanged:: 3.12 - Setting :attr:`~module.__cached__` is deprecated. See + Setting ``__cached__`` is deprecated. See :class:`~importlib.machinery.ModuleSpec` for alternatives. + .. versionchanged:: 3.15 + ``__cached__`` no longer set. + .. c:function:: PyObject* PyImport_ExecCodeModuleWithPathnames(const char *name, PyObject *co, const char *pathname, const char *cpathname) diff --git a/Doc/deprecations/pending-removal-in-3.15.rst b/Doc/deprecations/pending-removal-in-3.15.rst index 09cbd6f01a0580..3b9cf892fe913d 100644 --- a/Doc/deprecations/pending-removal-in-3.15.rst +++ b/Doc/deprecations/pending-removal-in-3.15.rst @@ -3,9 +3,9 @@ Pending removal in Python 3.15 * The import system: - * Setting :attr:`~module.__cached__` on a module while + * Setting ``__cached__`` on a module while failing to set :attr:`__spec__.cached ` - is deprecated. In Python 3.15, :attr:`!__cached__` will cease to be set or + is deprecated. In Python 3.15, ``__cached__`` will cease to be set or take into consideration by the import system or standard library. (:gh:`97879`) * Setting :attr:`~module.__package__` on a module while diff --git a/Doc/howto/gdb_helpers.rst b/Doc/howto/gdb_helpers.rst index 98ce813ca4ab02..33d1fbf8cd9e9e 100644 --- a/Doc/howto/gdb_helpers.rst +++ b/Doc/howto/gdb_helpers.rst @@ -136,7 +136,7 @@ enabled:: at Objects/unicodeobject.c:551 #7 0x0000000000440d94 in PyUnicodeUCS2_FromString (u=0x5c2b8d "__lltrace__") at Objects/unicodeobject.c:569 #8 0x0000000000584abd in PyDict_GetItemString (v= - {'Yuck': , '__builtins__': , '__file__': 'Lib/test/crashers/nasty_eq_vs_dict.py', '__package__': None, 'y': , 'dict': {0: 0, 1: 1, 2: 2, 3: 3}, '__cached__': None, '__name__': '__main__', 'z': , '__doc__': None}, key= + {'Yuck': , '__builtins__': , '__file__': 'Lib/test/crashers/nasty_eq_vs_dict.py', '__package__': None, 'y': , 'dict': {0: 0, 1: 1, 2: 2, 3: 3}, '__name__': '__main__', 'z': , '__doc__': None}, key= 0x5c2b8d "__lltrace__") at Objects/dictobject.c:2171 Notice how the dictionary argument to ``PyDict_GetItemString`` is displayed diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 71c4f094886546..d50ec34e54d710 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -1679,7 +1679,7 @@ The Namespace object Other utilities --------------- -Sub-commands +Subcommands ^^^^^^^^^^^^ .. method:: ArgumentParser.add_subparsers(*, [title], [description], [prog], \ @@ -1708,7 +1708,7 @@ Sub-commands * *description* - description for the sub-parser group in help output, by default ``None`` - * *prog* - usage information that will be displayed with sub-command help, + * *prog* - usage information that will be displayed with subcommand help, by default the name of the program and any positional arguments before the subparser argument @@ -1718,7 +1718,7 @@ Sub-commands * action_ - the basic type of action to be taken when this argument is encountered at the command line - * dest_ - name of the attribute under which sub-command name will be + * dest_ - name of the attribute under which subcommand name will be stored; by default ``None`` and no value is stored * required_ - Whether or not a subcommand must be provided, by default diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index 8314fed80fa512..601745a75780fc 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -526,7 +526,7 @@ are always available. They are listed here in alphabetical order. >>> dir() # show the names in the module namespace # doctest: +SKIP ['__builtins__', '__name__', 'struct'] >>> dir(struct) # show the names in the struct module # doctest: +SKIP - ['Struct', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', + ['Struct', '__all__', '__builtins__', '__doc__', '__file__', '__initializing__', '__loader__', '__name__', '__package__', '_clearcache', 'calcsize', 'error', 'pack', 'pack_into', 'unpack', 'unpack_from'] diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst index 34130f9be67e7e..c5ea78c1683761 100644 --- a/Doc/library/importlib.rst +++ b/Doc/library/importlib.rst @@ -1197,8 +1197,7 @@ find and load modules. .. attribute:: cached - The filename of a compiled version of the module's code - (see :attr:`module.__cached__`). + The filename of a compiled version of the module's code. The :term:`finder` should always set this attribute but it may be ``None`` for modules that do not need compiled code stored. @@ -1300,7 +1299,7 @@ an :term:`importer`. .. versionadded:: 3.4 -.. function:: cache_from_source(path, debug_override=None, *, optimization=None) +.. function:: cache_from_source(path, *, optimization=None) Return the :pep:`3147`/:pep:`488` path to the byte-compiled file associated with the source *path*. For example, if *path* is ``/foo/bar/baz.py`` the return @@ -1319,12 +1318,6 @@ an :term:`importer`. ``/foo/bar/__pycache__/baz.cpython-32.opt-2.pyc``. The string representation of *optimization* can only be alphanumeric, else :exc:`ValueError` is raised. - The *debug_override* parameter is deprecated and can be used to override - the system's value for ``__debug__``. A ``True`` value is the equivalent of - setting *optimization* to the empty string. A ``False`` value is the same as - setting *optimization* to ``1``. If both *debug_override* an *optimization* - are not ``None`` then :exc:`TypeError` is raised. - .. versionadded:: 3.4 .. versionchanged:: 3.5 @@ -1334,6 +1327,9 @@ an :term:`importer`. .. versionchanged:: 3.6 Accepts a :term:`path-like object`. + .. versionchanged:: 3.15 + The *debug_override* parameter was removed. + .. function:: source_from_cache(path) diff --git a/Doc/library/profiling.sampling.rst b/Doc/library/profiling.sampling.rst index 23e9173a815d22..2bad2a8d1ab353 100644 --- a/Doc/library/profiling.sampling.rst +++ b/Doc/library/profiling.sampling.rst @@ -470,9 +470,10 @@ which you can use to judge whether the data is sufficient for your analysis. Profiling modes =============== -The sampling profiler supports three modes that control which samples are +The sampling profiler supports four modes that control which samples are recorded. The mode determines what the profile measures: total elapsed time, -CPU execution time, or time spent holding the global interpreter lock. +CPU execution time, time spent holding the global interpreter lock, or +exception handling. Wall-clock mode @@ -553,6 +554,67 @@ single-threaded programs to distinguish Python execution time from time spent in C extensions or I/O. +Exception mode +-------------- + +Exception mode (``--mode=exception``) records samples only when a thread has +an active exception:: + + python -m profiling.sampling run --mode=exception script.py + +Samples are recorded in two situations: when an exception is being propagated +up the call stack (after ``raise`` but before being caught), or when code is +executing inside an ``except`` block where exception information is still +present in the thread state. + +The following example illustrates which code regions are captured: + +.. code-block:: python + + def example(): + try: + raise ValueError("error") # Captured: exception being raised + except ValueError: + process_error() # Captured: inside except block + finally: + cleanup() # NOT captured: exception already handled + + def example_propagating(): + try: + try: + raise ValueError("error") + finally: + cleanup() # Captured: exception propagating through + except ValueError: + pass + + def example_no_exception(): + try: + do_work() + finally: + cleanup() # NOT captured: no exception involved + +Note that ``finally`` blocks are only captured when an exception is actively +propagating through them. Once an ``except`` block finishes executing, Python +clears the exception information before running any subsequent ``finally`` +block. Similarly, ``finally`` blocks that run during normal execution (when no +exception was raised) are not captured because no exception state is present. + +This mode is useful for understanding where your program spends time handling +errors. Exception handling can be a significant source of overhead in code +that uses exceptions for flow control (such as ``StopIteration`` in iterators) +or in applications that process many error conditions (such as network servers +handling connection failures). + +Exception mode helps answer questions like "how much time is spent handling +exceptions?" and "which exception handlers are the most expensive?" It can +reveal hidden performance costs in code that catches and processes many +exceptions, even when those exceptions are handled gracefully. For example, +if a parsing library uses exceptions internally to signal format errors, this +mode will capture time spent in those handlers even if the calling code never +sees the exceptions. + + Output formats ============== @@ -1006,8 +1068,9 @@ Mode options .. option:: --mode - Sampling mode: ``wall`` (default), ``cpu``, or ``gil``. - The ``cpu`` and ``gil`` modes are incompatible with ``--async-aware``. + Sampling mode: ``wall`` (default), ``cpu``, ``gil``, or ``exception``. + The ``cpu``, ``gil``, and ``exception`` modes are incompatible with + ``--async-aware``. .. option:: --async-mode diff --git a/Doc/library/runpy.rst b/Doc/library/runpy.rst index b07ec6e93f80ab..64735b5a109e66 100644 --- a/Doc/library/runpy.rst +++ b/Doc/library/runpy.rst @@ -50,10 +50,10 @@ The :mod:`runpy` module provides two functions: overridden by :func:`run_module`. The special global variables ``__name__``, ``__spec__``, ``__file__``, - ``__cached__``, ``__loader__`` and ``__package__`` are set in the globals - dictionary before the module code is executed. (Note that this is a - minimal set of variables - other variables may be set implicitly as an - interpreter implementation detail.) + ``__loader__`` and ``__package__`` are set in the globals dictionary before + the module code is executed. (Note that this is a minimal set of variables - + other variables may be set implicitly as an interpreter implementation + detail.) ``__name__`` is set to *run_name* if this optional argument is not :const:`None`, to ``mod_name + '.__main__'`` if the named module is a @@ -63,7 +63,7 @@ The :mod:`runpy` module provides two functions: module (that is, ``__spec__.name`` will always be *mod_name* or ``mod_name + '.__main__'``, never *run_name*). - ``__file__``, ``__cached__``, ``__loader__`` and ``__package__`` are + ``__file__``, ``__loader__`` and ``__package__`` are :ref:`set as normal ` based on the module spec. If the argument *alter_sys* is supplied and evaluates to :const:`True`, @@ -98,6 +98,9 @@ The :mod:`runpy` module provides two functions: ``__package__`` are deprecated. See :class:`~importlib.machinery.ModuleSpec` for alternatives. + .. versionchanged:: 3.15 + ``__cached__`` is no longer set. + .. function:: run_path(path_name, init_globals=None, run_name=None) .. index:: @@ -125,23 +128,23 @@ The :mod:`runpy` module provides two functions: overridden by :func:`run_path`. The special global variables ``__name__``, ``__spec__``, ``__file__``, - ``__cached__``, ``__loader__`` and ``__package__`` are set in the globals - dictionary before the module code is executed. (Note that this is a - minimal set of variables - other variables may be set implicitly as an - interpreter implementation detail.) + ``__loader__`` and ``__package__`` are set in the globals dictionary before + the module code is executed. (Note that this is a minimal set of variables - + other variables may be set implicitly as an interpreter implementation + detail.) ``__name__`` is set to *run_name* if this optional argument is not :const:`None` and to ``''`` otherwise. If *file_path* directly references a script file (whether as source or as precompiled byte code), then ``__file__`` will be set to - *file_path*, and ``__spec__``, ``__cached__``, ``__loader__`` and + *file_path*, and ``__spec__``, ``__loader__`` and ``__package__`` will all be set to :const:`None`. If *file_path* is a reference to a valid :data:`sys.path` entry, then ``__spec__`` will be set appropriately for the imported :mod:`__main__` module (that is, ``__spec__.name`` will always be ``__main__``). - ``__file__``, ``__cached__``, ``__loader__`` and ``__package__`` will be + ``__file__``, ``__loader__`` and ``__package__`` will be :ref:`set as normal ` based on the module spec. A number of alterations are also made to the :mod:`sys` module. Firstly, @@ -173,6 +176,9 @@ The :mod:`runpy` module provides two functions: The setting of ``__cached__``, ``__loader__``, and ``__package__`` are deprecated. + .. versionchanged:: 3.15 + ``__cached__`` is no longer set. + .. seealso:: :pep:`338` -- Executing modules as scripts diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 5f79c6fe8f50ff..97f55acf00d521 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -895,7 +895,6 @@ Attribute assignment updates the module's namespace dictionary, e.g., single: __loader__ (module attribute) single: __path__ (module attribute) single: __file__ (module attribute) - single: __cached__ (module attribute) single: __doc__ (module attribute) single: __annotations__ (module attribute) single: __annotate__ (module attribute) @@ -1044,43 +1043,28 @@ this approach. instead of :attr:`!module.__path__`. .. attribute:: module.__file__ -.. attribute:: module.__cached__ - :attr:`!__file__` and :attr:`!__cached__` are both optional attributes that + :attr:`!__file__` is an optional attribute that may or may not be set. Both attributes should be a :class:`str` when they are available. - :attr:`!__file__` indicates the pathname of the file from which the module - was loaded (if loaded from a file), or the pathname of the shared library - file for extension modules loaded dynamically from a shared library. - It might be missing for certain types of modules, such as C modules that are - statically linked into the interpreter, and the + An optional attribute, :attr:`!__file__` indicates the pathname of the file + from which the module was loaded (if loaded from a file), or the pathname of + the shared library file for extension modules loaded dynamically from a + shared library. It might be missing for certain types of modules, such as C + modules that are statically linked into the interpreter, and the :ref:`import system ` may opt to leave it unset if it has no semantic meaning (for example, a module loaded from a database). - If :attr:`!__file__` is set then the :attr:`!__cached__` attribute might - also be set, which is the path to any compiled version of - the code (for example, a byte-compiled file). The file does not need to exist - to set this attribute; the path can simply point to where the - compiled file *would* exist (see :pep:`3147`). - - Note that :attr:`!__cached__` may be set even if :attr:`!__file__` is not - set. However, that scenario is quite atypical. Ultimately, the - :term:`loader` is what makes use of the module spec provided by the - :term:`finder` (from which :attr:`!__file__` and :attr:`!__cached__` are - derived). So if a loader can load from a cached module but otherwise does - not load from a file, that atypical scenario may be appropriate. - - It is **strongly** recommended that you use - :attr:`module.__spec__.cached ` - instead of :attr:`!module.__cached__`. - .. deprecated-removed:: 3.13 3.15 - Setting :attr:`!__cached__` on a module while failing to set + Setting ``__cached__`` on a module while failing to set :attr:`!__spec__.cached` is deprecated. In Python 3.15, - :attr:`!__cached__` will cease to be set or taken into consideration by + ``__cached__`` will cease to be set or taken into consideration by the import system or standard library. + .. versionchanged:: 3.15 + ``__cached__`` is no longer set. + Other writable attributes on module objects ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 8badfe9a6b49b9..221956f3dd3819 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -1337,7 +1337,7 @@ Deprecated it was :exc:`ImportWarning`). (Contributed by Brett Cannon in :gh:`65961`.) -* Setting :attr:`~module.__package__` or :attr:`~module.__cached__` on a +* Setting :attr:`~module.__package__` or ``__cached__`` on a module is deprecated, and will cease to be set or taken into consideration by the import system in Python 3.14. (Contributed by Brett Cannon in :gh:`65961`.) diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 853c47d4402f20..9d4686f982a99a 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -146,6 +146,8 @@ Key features include: and blocking. Use this to identify CPU-bound bottlenecks and optimize computational work. * **GIL-holding time** (``--mode gil``): Measures time spent holding Python's Global Interpreter Lock. Use this to identify which threads dominate GIL usage in multi-threaded applications. + * **Exception handling time** (``--mode exception``): Captures samples only from threads with + an active exception. Use this to analyze exception handling overhead. * **Thread-aware profiling**: Option to profile all threads (``-a``) or just the main thread, essential for understanding multi-threaded application behavior. @@ -175,6 +177,10 @@ Key features include: (``--async-aware``). See which coroutines are consuming time, with options to show only running tasks or all tasks including those waiting. +* **Opcode-level profiling**: Gather bytecode opcode information for instruction-level + profiling (``--opcodes``). Shows which bytecode instructions are executing, including + specializations from the adaptive interpreter. + See :mod:`profiling.sampling` for the complete documentation, including all available output formats, profiling modes, and configuration options. diff --git a/Doc/whatsnew/3.2.rst b/Doc/whatsnew/3.2.rst index 47c4d9acbc870e..3b13d90f7692cd 100644 --- a/Doc/whatsnew/3.2.rst +++ b/Doc/whatsnew/3.2.rst @@ -312,7 +312,7 @@ cluttering source directories, the *pyc* files are now collected in a Aside from the filenames and target directories, the new scheme has a few aspects that are visible to the programmer: -* Imported modules now have a :attr:`~module.__cached__` attribute which stores +* Imported modules now have a ``__cached__`` attribute which stores the name of the actual file that was imported: >>> import collections diff --git a/Include/cpython/pyatomic.h b/Include/cpython/pyatomic.h index 2a0c11e7b3ad66..790640309f1e03 100644 --- a/Include/cpython/pyatomic.h +++ b/Include/cpython/pyatomic.h @@ -591,6 +591,17 @@ static inline void _Py_atomic_fence_release(void); // --- aliases --------------------------------------------------------------- +// Compilers don't really support "consume" semantics, so we fake it. Use +// "acquire" with TSan to support false positives. Use "relaxed" otherwise, +// because CPUs on all platforms we support respect address dependencies without +// extra barriers. +// See 2.6.7 in https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2055r0.pdf +#if defined(_Py_THREAD_SANITIZER) +# define _Py_atomic_load_ptr_consume _Py_atomic_load_ptr_acquire +#else +# define _Py_atomic_load_ptr_consume _Py_atomic_load_ptr_relaxed +#endif + #if SIZEOF_LONG == 8 # define _Py_atomic_load_ulong(p) \ _Py_atomic_load_uint64((uint64_t *)p) diff --git a/Include/internal/pycore_debug_offsets.h b/Include/internal/pycore_debug_offsets.h index 1cdc4449b173e8..66f14e69f33f44 100644 --- a/Include/internal/pycore_debug_offsets.h +++ b/Include/internal/pycore_debug_offsets.h @@ -110,8 +110,15 @@ typedef struct _Py_DebugOffsets { uint64_t status; uint64_t holds_gil; uint64_t gil_requested; + uint64_t current_exception; + uint64_t exc_state; } thread_state; + // Exception stack item offset + struct { + uint64_t exc_value; + } err_stackitem; + // InterpreterFrame offset; struct _interpreter_frame { uint64_t size; @@ -282,6 +289,11 @@ typedef struct _Py_DebugOffsets { .status = offsetof(PyThreadState, _status), \ .holds_gil = offsetof(PyThreadState, holds_gil), \ .gil_requested = offsetof(PyThreadState, gil_requested), \ + .current_exception = offsetof(PyThreadState, current_exception), \ + .exc_state = offsetof(PyThreadState, exc_state), \ + }, \ + .err_stackitem = { \ + .exc_value = offsetof(_PyErr_StackItem, exc_value), \ }, \ .interpreter_frame = { \ .size = sizeof(_PyInterpreterFrame), \ diff --git a/Include/internal/pycore_pyatomic_ft_wrappers.h b/Include/internal/pycore_pyatomic_ft_wrappers.h index 2ae0185226f847..817c0763bf899b 100644 --- a/Include/internal/pycore_pyatomic_ft_wrappers.h +++ b/Include/internal/pycore_pyatomic_ft_wrappers.h @@ -31,6 +31,8 @@ extern "C" { _Py_atomic_store_ptr(&value, new_value) #define FT_ATOMIC_LOAD_PTR_ACQUIRE(value) \ _Py_atomic_load_ptr_acquire(&value) +#define FT_ATOMIC_LOAD_PTR_CONSUME(value) \ + _Py_atomic_load_ptr_consume(&value) #define FT_ATOMIC_LOAD_UINTPTR_ACQUIRE(value) \ _Py_atomic_load_uintptr_acquire(&value) #define FT_ATOMIC_LOAD_PTR_RELAXED(value) \ @@ -125,6 +127,7 @@ extern "C" { #define FT_ATOMIC_LOAD_SSIZE_ACQUIRE(value) value #define FT_ATOMIC_LOAD_SSIZE_RELAXED(value) value #define FT_ATOMIC_LOAD_PTR_ACQUIRE(value) value +#define FT_ATOMIC_LOAD_PTR_CONSUME(value) value #define FT_ATOMIC_LOAD_UINTPTR_ACQUIRE(value) value #define FT_ATOMIC_LOAD_PTR_RELAXED(value) value #define FT_ATOMIC_LOAD_UINT8(value) value diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py index 8cee9fda935050..07d938b18fe727 100644 --- a/Lib/importlib/_bootstrap.py +++ b/Lib/importlib/_bootstrap.py @@ -565,8 +565,7 @@ class ModuleSpec: `has_location` indicates that a spec's "origin" reflects a location. When this is True, `__file__` attribute of the module is set. - `cached` is the location of the cached bytecode file, if any. It - corresponds to the `__cached__` attribute. + `cached` is the location of the cached bytecode file, if any. `submodule_search_locations` is the sequence of path entries to search when importing submodules. If set, is_package should be @@ -699,10 +698,6 @@ def _spec_from_module(module, loader=None, origin=None): origin = getattr(loader, '_ORIGIN', None) if not origin and location is not None: origin = location - try: - cached = module.__cached__ - except AttributeError: - cached = None try: submodule_search_locations = list(module.__path__) except AttributeError: @@ -710,7 +705,7 @@ def _spec_from_module(module, loader=None, origin=None): spec = ModuleSpec(name, loader, origin=origin) spec._set_fileattr = False if location is None else (origin == location) - spec.cached = cached + spec.cached = None spec.submodule_search_locations = submodule_search_locations return spec @@ -770,7 +765,7 @@ def _init_module_attrs(spec, module, *, override=False): module.__path__ = spec.submodule_search_locations except AttributeError: pass - # __file__/__cached__ + # __file__ if spec.has_location: if override or getattr(module, '__file__', None) is None: try: @@ -778,12 +773,6 @@ def _init_module_attrs(spec, module, *, override=False): except AttributeError: pass - if override or getattr(module, '__cached__', None) is None: - if spec.cached is not None: - try: - module.__cached__ = spec.cached - except AttributeError: - pass return module diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py index 332dc1c5a4fc8f..b576ceb1ce9f6e 100644 --- a/Lib/importlib/_bootstrap_external.py +++ b/Lib/importlib/_bootstrap_external.py @@ -236,7 +236,7 @@ def _write_atomic(path, data, mode=0o666): # Deprecated. DEBUG_BYTECODE_SUFFIXES = OPTIMIZED_BYTECODE_SUFFIXES = BYTECODE_SUFFIXES -def cache_from_source(path, debug_override=None, *, optimization=None): +def cache_from_source(path, *, optimization=None): """Given the path to a .py file, return the path to its .pyc file. The .py file does not need to exist; this simply returns the path to the @@ -247,20 +247,9 @@ def cache_from_source(path, debug_override=None, *, optimization=None): of the argument is taken and verified to be alphanumeric (else ValueError is raised). - The debug_override parameter is deprecated. If debug_override is not None, - a True value is the same as setting 'optimization' to the empty string - while a False value is equivalent to setting 'optimization' to '1'. - If sys.implementation.cache_tag is None then NotImplementedError is raised. """ - if debug_override is not None: - _warnings.warn('the debug_override parameter is deprecated; use ' - "'optimization' instead", DeprecationWarning) - if optimization is not None: - message = 'debug_override or optimization must be set to None' - raise TypeError(message) - optimization = '' if debug_override else 1 path = _os.fspath(path) head, tail = _path_split(path) base, sep, rest = tail.rpartition('.') @@ -1514,7 +1503,6 @@ def _fix_up_module(ns, name, pathname, cpathname=None): ns['__spec__'] = spec ns['__loader__'] = loader ns['__file__'] = pathname - ns['__cached__'] = cpathname except Exception: # Not important enough to report. pass diff --git a/Lib/inspect.py b/Lib/inspect.py index ff462750888c88..07c4e28f0d9952 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -3407,20 +3407,20 @@ def _main(): sys.exit(1) if args.details: - print('Target: {}'.format(target)) - print('Origin: {}'.format(getsourcefile(module))) - print('Cached: {}'.format(module.__cached__)) + print(f'Target: {target}') + print(f'Origin: {getsourcefile(module)}') + print(f'Cached: {module.__spec__.cached}') if obj is module: - print('Loader: {}'.format(repr(module.__loader__))) + print(f'Loader: {module.__loader__!r}') if hasattr(module, '__path__'): - print('Submodule search path: {}'.format(module.__path__)) + print(f'Submodule search path: {module.__path__}') else: try: __, lineno = findsource(obj) except Exception: pass else: - print('Line: {}'.format(lineno)) + print(f'Line: {lineno}') print() else: diff --git a/Lib/profile.py b/Lib/profile.py index 20c500d28bc5b9..304284da421163 100644 --- a/Lib/profile.py +++ b/Lib/profile.py @@ -607,7 +607,6 @@ def main(): '__file__': spec.origin, '__name__': spec.name, '__package__': None, - '__cached__': None, } try: runctx(code, globs, None, options.outfile, options.sort) diff --git a/Lib/profiling/sampling/_flamegraph_assets/flamegraph.css b/Lib/profiling/sampling/_flamegraph_assets/flamegraph.css index ee699f2982616a..c3b1d955f7f526 100644 --- a/Lib/profiling/sampling/_flamegraph_assets/flamegraph.css +++ b/Lib/profiling/sampling/_flamegraph_assets/flamegraph.css @@ -329,34 +329,44 @@ body.resizing-sidebar { gap: 8px; padding: 8px 10px; background: var(--bg-primary); - border: 1px solid var(--border); + border: 2px solid var(--border); border-radius: 8px; transition: all var(--transition-fast); animation: slideUp 0.4s ease-out backwards; - animation-delay: calc(var(--i, 0) * 0.05s); + animation-delay: calc(var(--i, 0) * 0.08s); overflow: hidden; + position: relative; } -.summary-card:nth-child(1) { --i: 0; } -.summary-card:nth-child(2) { --i: 1; } -.summary-card:nth-child(3) { --i: 2; } -.summary-card:nth-child(4) { --i: 3; } +.summary-card:nth-child(1) { --i: 0; --card-color: 55, 118, 171; } +.summary-card:nth-child(2) { --i: 1; --card-color: 40, 167, 69; } +.summary-card:nth-child(3) { --i: 2; --card-color: 255, 193, 7; } +.summary-card:nth-child(4) { --i: 3; --card-color: 111, 66, 193; } .summary-card:hover { - border-color: var(--accent); - background: var(--accent-glow); + border-color: rgba(var(--card-color), 0.6); + background: linear-gradient(135deg, rgba(var(--card-color), 0.08) 0%, var(--bg-primary) 100%); + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(var(--card-color), 0.15); } .summary-icon { - font-size: 16px; + font-size: 14px; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; - background: var(--bg-tertiary); + background: linear-gradient(135deg, rgba(var(--card-color), 0.15) 0%, rgba(var(--card-color), 0.05) 100%); + border: 1px solid rgba(var(--card-color), 0.2); border-radius: 6px; flex-shrink: 0; + transition: all var(--transition-fast); +} + +.summary-card:hover .summary-icon { + transform: scale(1.05); + background: linear-gradient(135deg, rgba(var(--card-color), 0.25) 0%, rgba(var(--card-color), 0.1) 100%); } .summary-data { @@ -368,8 +378,8 @@ body.resizing-sidebar { .summary-value { font-family: var(--font-mono); font-size: 13px; - font-weight: 700; - color: var(--accent); + font-weight: 800; + color: rgb(var(--card-color)); line-height: 1.2; white-space: nowrap; overflow: hidden; diff --git a/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js b/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js index 3076edd1d68cba..dc7bfed602f32a 100644 --- a/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js +++ b/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js @@ -187,6 +187,27 @@ function restoreUIState() { } } +// ============================================================================ +// Logo/Favicon Setup +// ============================================================================ + +function setupLogos() { + const logo = document.querySelector('.sidebar-logo-img img'); + if (!logo) return; + + const navbarLogoContainer = document.getElementById('navbar-logo'); + if (navbarLogoContainer) { + const navbarLogo = logo.cloneNode(true); + navbarLogoContainer.appendChild(navbarLogo); + } + + const favicon = document.createElement('link'); + favicon.rel = 'icon'; + favicon.type = 'image/png'; + favicon.href = logo.src; + document.head.appendChild(favicon); +} + // ============================================================================ // Status Bar // ============================================================================ @@ -198,6 +219,11 @@ function updateStatusBar(nodeData, rootValue) { const timeMs = (nodeData.value / 1000).toFixed(2); const percent = rootValue > 0 ? ((nodeData.value / rootValue) * 100).toFixed(1) : "0.0"; + const brandEl = document.getElementById('status-brand'); + const taglineEl = document.getElementById('status-tagline'); + if (brandEl) brandEl.style.display = 'none'; + if (taglineEl) taglineEl.style.display = 'none'; + const locationEl = document.getElementById('status-location'); const funcItem = document.getElementById('status-func-item'); const timeItem = document.getElementById('status-time-item'); @@ -230,6 +256,11 @@ function clearStatusBar() { const el = document.getElementById(id); if (el) el.style.display = 'none'; }); + + const brandEl = document.getElementById('status-brand'); + const taglineEl = document.getElementById('status-tagline'); + if (brandEl) brandEl.style.display = 'flex'; + if (taglineEl) taglineEl.style.display = 'flex'; } // ============================================================================ @@ -717,6 +748,10 @@ function populateThreadStats(data, selectedThreadId = null) { const gcPctElem = document.getElementById('gc-pct'); if (gcPctElem) gcPctElem.textContent = `${(threadStats.gc_pct || 0).toFixed(1)}%`; + + // Exception stats + const excPctElem = document.getElementById('exc-pct'); + if (excPctElem) excPctElem.textContent = `${(threadStats.has_exception_pct || 0).toFixed(1)}%`; } // ============================================================================ @@ -1061,6 +1096,7 @@ function exportSVG() { function initFlamegraph() { ensureLibraryLoaded(); restoreUIState(); + setupLogos(); let processedData = EMBEDDED_DATA; if (EMBEDDED_DATA.strings) { diff --git a/Lib/profiling/sampling/_flamegraph_assets/flamegraph_template.html b/Lib/profiling/sampling/_flamegraph_assets/flamegraph_template.html index 82102c229e7af9..05277fb225c86f 100644 --- a/Lib/profiling/sampling/_flamegraph_assets/flamegraph_template.html +++ b/Lib/profiling/sampling/_flamegraph_assets/flamegraph_template.html @@ -3,7 +3,7 @@ - Tachyon Profiler - Flamegraph + Tachyon Profiler - Flamegraph Report @@ -15,9 +15,10 @@
+ Tachyon - Profiler + Flamegraph Report
Runtime Stats
--
GC
+
+
--
+
Exception
+
@@ -286,6 +291,12 @@

Heat Map