Skip to content

PEP 830: round 2 edits#4928

Open
gpshead wants to merge 12 commits intopython:mainfrom
gpshead:pep830-round2-edits
Open

PEP 830: round 2 edits#4928
gpshead wants to merge 12 commits intopython:mainfrom
gpshead:pep830-round2-edits

Conversation

@gpshead
Copy link
Copy Markdown
Member

@gpshead gpshead commented Apr 19, 2026

This is just the Change History section repeated here. Please only make editorial comments on this PR. Discussion needs to happen solely in the Discuss thread.


  • Changed the ns display format from an integer with an ns suffix to seconds with nine decimal digits, allowing direct use with datetime.fromtimestamp(). Dropped the us format; ns is now the default when enabled via 1 or with no explicit format value.
  • Replaced the traceback module no_timestamp parameter with a tri-state timestamps (default None, follow the global setting).
  • Clarified that __timestamp_ns__ is UTC, that clock-read failure yields 0, and how free-list and singleton MemoryError instances are timestamped; that str(exc) and repr(exc) are unchanged; and that the iso display format has microsecond resolution.
  • Noted that hasattr, three-argument getattr, and the dict miss paths do not instantiate exceptions in CPython's hot paths and need no special handling.
  • Added a Rationale subsection on instantiation time vs. raise time.
  • Added Rejected Ideas entries for sys.excepthook, sys.monitoring, placing the timestamp in the Traceback header line, returning None when unset, separate ms/us display formats, and using a coarse-resolution clock; reworded the Runtime API rejection.
  • Added an Open Issues section covering display location, benchmarking the sys.monitoring alternative, and recording time of first raise.
  • Reworked the Motivation example to be self-contained.

📚 Documentation preview 📚: https://pep-previews--4928.org.readthedocs.build/

gpshead added 10 commits April 19, 2026 07:08
…dback

- Clarified that __timestamp_ns__ is UTC, that clock-read failure yields 0,
  and how free-list and singleton MemoryError instances are timestamped.
- Clarified that str(exc) and repr(exc) are unchanged, and that the iso
  display format has microsecond resolution.
- Changed the ns display format from an integer with an ns suffix to seconds
  with nine decimal digits, allowing direct use with datetime.fromtimestamp().
  Dropped the us format; ns is now the default format when the feature is
  enabled via 1 or with no explicit format value.
- Added Rejected Ideas entries for returning None when unset and for using a
  coarse-resolution clock.
- Made the Motivation example self-contained.
Frame it as an operator-level setting and note that runtime configurability
can be added later if there is demand.
…tion

Explain why the timestamp is appended to the message line rather than the
Traceback header (the header is absent for exceptions without a traceback,
as with ProcessPoolExecutor's _RemoteTraceback), and add an Open Issues
section noting display location remains open to revision.
Rename the traceback formatting parameter from no_timestamp (a
double-negative boolean) to timestamps, and make it tri-state: None follows
the global configuration (default), False suppresses, True displays any
non-zero __timestamp_ns__ regardless of the global setting.
CPython's PyObject_GetOptionalAttr and the suppress paths in generic
attribute lookup already return "not found" without instantiating an
exception object, and the dict miss paths never create a KeyError
internally, so these idioms do not need to be excluded from timestamp
collection.
The hook fires at display time, once for the whole ExceptionGroup, so it
cannot record per-sub-exception creation times; caught exceptions never
reach it.
Explain why the timestamp is recorded at instantiation rather than raise:
the two coincide in the common case, instantiation time is the more useful
value for re-raise and collect-then-group patterns, and instantiation has a
clean two-point funnel in CPython while raising does not. Defer "time of
first raise" to Open Issues.
Describe a viable third-party design using a RAISE callback and its costs:
per-frame (not per-exception) dispatch through vectorcall, instance dict and
PyLongObject (or list and string for add_note) allocation on every raise,
consuming a tool ID, and needing excepthook or a traceback monkeypatch to
display the attribute variant. Also describe a hybrid that keeps the struct
field and traceback display but uses monitoring for collection. Leave a
short Open Issues entry offering to prototype and benchmark if there is
demand.
Break up two adjacent "rather than" pairs (Rationale and sys.monitoring
paragraphs) and reorder/consolidate the Change History bullets so spec
changes lead, followed by Rationale, a single Rejected Ideas bullet, a
single Open Issues bullet, and housekeeping.
The original TaskGroup example only ever produced one sub-exception because
TaskGroup cancels siblings on first failure. Switch to a three-backend
asyncio.gather(..., return_exceptions=True) pattern that collects every
failure, and replace the illustrative output with real output captured from
the reference implementation. Update the sys.excepthook rejection to drop
the now-stale TaskGroup reference.
gpshead added a commit to gpshead/cpython that referenced this pull request Apr 19, 2026
Updates the implementation to match the PEP revisions in
python/peps#4928

* Drop the ``us`` display format; ``-X traceback_timestamps`` with no
  value or ``=1`` now selects ``ns``.
* ``ns`` format now renders as seconds with nine fractional digits
  (``<@1776017178.687320256>``) instead of a raw integer with an ``ns``
  suffix.  Integer divmod is used to preserve full precision.
* The ``no_timestamp`` boolean on the traceback formatting APIs is
  replaced by a tri-state ``timestamps`` keyword: ``None`` follows the
  global config, ``False`` suppresses, ``True`` forces display of any
  non-zero ``__timestamp_ns__``.  The setting now propagates through
  ``__cause__``, ``__context__`` and exception-group members.
* MemoryError instances handed out from the free list receive a fresh
  timestamp at hand-out time; only the last-resort static singleton
  remains at 0.
Comment thread peps/pep-0830.rst
preserves the original timestamp on re-raise.

This is expected to cost more than the struct-field approach. ``RAISE``
fires once per Python frame during unwind rather than once per exception, so
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some sample code to demonstrate this once per frame RAISE event in this gist produces:

One `raise`, RAISE callback fired 5 times:
  in frame 'level_d'    offset=22   exc id=0x7f4c881e8700
  in frame 'level_c'    offset=10   exc id=0x7f4c881e8700
  in frame 'level_b'    offset=10   exc id=0x7f4c881e8700
  in frame 'level_a'    offset=10   exc id=0x7f4c881e8700
  in frame '<module>'   offset=320  exc id=0x7f4c881e8700

Distinct exception objects seen: 1 (same object passed to every call)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shorten the None-when-unset and coarse-clock rejections, and replace the
"can be added later" closer in the time-of-first-raise open issue with a
note that early-construction patterns are not common in practice.
@gpshead gpshead marked this pull request as ready for review April 19, 2026 08:32
@gpshead gpshead self-assigned this Apr 19, 2026
@gpshead gpshead requested a review from hugovk April 19, 2026 23:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant