Compare commits

...

80 commits

Author SHA1 Message Date
Stan Ulbrych
f9704f1d84
gh-84232: Fix pydoc docs.python.org link generation (#139995)
Co-authored-by: Éric <merwok@netwok.org>
2025-12-23 10:29:58 +02:00
Zheng Yu
a273bc99d2
gh-122431: Correct the non-negative error message in readline.append_history_file (GH-143075)
"positive" -> "non-negative", since zero is included.
2025-12-22 23:35:23 -05:00
Hai Zhu
5b5ee3c4bf
gh-134584: Eliminate redundant refcounting from _LOAD_ATTR_WITH_HINT (GH-143062)
Some checks failed
Tests / Check if generated files are up to date (push) Has been cancelled
Tests / Ubuntu SSL tests with OpenSSL (push) Has been cancelled
Tests / Ubuntu SSL tests with AWS-LC (push) Has been cancelled
Tests / Android (aarch64) (push) Has been cancelled
Tests / Android (x86_64) (push) Has been cancelled
Tests / WASI (push) Has been cancelled
Tests / Hypothesis tests on Ubuntu (push) Has been cancelled
Tests / Sanitizers (push) Has been cancelled
Tests / Cross build Linux (push) Has been cancelled
Tests / CIFuzz (push) Has been cancelled
Tests / All required checks pass (push) Has been cancelled
JIT / aarch64-pc-windows-msvc/msvc (Release) (push) Has been cancelled
JIT / aarch64-pc-windows-msvc/msvc (Debug) (push) Has been cancelled
JIT / i686-pc-windows-msvc/msvc (Release) (push) Has been cancelled
JIT / i686-pc-windows-msvc/msvc (Debug) (push) Has been cancelled
JIT / aarch64-apple-darwin/clang (Release) (push) Has been cancelled
JIT / aarch64-unknown-linux-gnu/gcc (Debug) (push) Has been cancelled
JIT / x86_64-pc-windows-msvc/msvc (Release) (push) Has been cancelled
JIT / x86_64-pc-windows-msvc/msvc (Debug) (push) Has been cancelled
Tests / iOS (push) Has been cancelled
Tests / Address sanitizer (push) Has been cancelled
JIT / aarch64-unknown-linux-gnu/gcc (Release) (push) Has been cancelled
JIT / aarch64-apple-darwin/clang (Debug) (push) Has been cancelled
JIT / x86_64-apple-darwin/clang (Release) (push) Has been cancelled
JIT / x86_64-unknown-linux-gnu/gcc (Release) (push) Has been cancelled
JIT / x86_64-apple-darwin/clang (Debug) (push) Has been cancelled
JIT / x86_64-unknown-linux-gnu/gcc (Debug) (push) Has been cancelled
JIT / Free-Threaded (Debug) (push) Has been cancelled
JIT / JIT without optimizations (Debug) (push) Has been cancelled
JIT / JIT with tail calling interpreter (push) Has been cancelled
Eliminate redundant refcounting from _LOAD_ATTR_WITH_HINT
2025-12-23 00:28:08 +00:00
Pablo Galindo Salgado
9e51301234
gh-138122: Allow tachyon to write and read binary output (#142730) 2025-12-22 23:57:20 +00:00
Ken Jin
714037ba84
gh-139922: Add tail call for MSVC for whats new in 3.15 (GH-143087) 2025-12-22 23:56:26 +00:00
Chris Eibl
be3c131640
GH-139922: Tail calling for MSVC (VS 2026) (GH-143068)
Co-authored-by: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com>
Co-authored-by: Brandt Bucher <brandt@python.org>
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
2025-12-22 23:01:34 +00:00
Ken Jin
665d2807a0
gh-139109: Add terminator to JIT code when halting due to invalid dependencies (#143033)
* Add terminator to JIT code when  halting due to invalid dependencies

* 📜🤖 Added by blurb_it.

---------

Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
2025-12-22 20:54:47 +00:00
Stan Ulbrych
3c0888b25b
gh-89152: Note truth testing exception in stdtypes.rst (#137640)
Co-authored-by: Peter Bierma <zintensitydev@gmail.com>
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
2025-12-22 11:36:44 -08:00
AZero13
a88d1b8dab
gh-143010: Prevent a TOCTOU issue by only calling open once (#143011)
* gh-143010: Prevent a TOCTOU issue by gh-143010: Prevent a TOCTOU issue by only calling open once

RDM: per  AZero13's research the 'x' option did not exist when this code was written,  This
modernization can thus drop the fd trick in _create_carefully and just use open with 'x' to achieve the same goal more securely.

Co-authored-by: sobolevn <mail@sobolevn.me>
2025-12-22 12:48:11 -05:00
Chris Eibl
700e9fad70
GH-142513: fix missing return in executor_clear (GH-143073)
fix missing return in executor_clear
2025-12-22 17:10:52 +00:00
Kumar Aditya
487e91c120
gh-129069: fix more thread safety issues in list (#143019)
Some checks are pending
Tests / CIFuzz (push) Blocked by required conditions
Tests / All required checks pass (push) Blocked by required conditions
JIT / Interpreter (Debug) (push) Waiting to run
JIT / aarch64-pc-windows-msvc/msvc (Release) (push) Blocked by required conditions
JIT / aarch64-pc-windows-msvc/msvc (Debug) (push) Blocked by required conditions
JIT / i686-pc-windows-msvc/msvc (Release) (push) Blocked by required conditions
JIT / i686-pc-windows-msvc/msvc (Debug) (push) Blocked by required conditions
JIT / aarch64-apple-darwin/clang (Release) (push) Blocked by required conditions
JIT / aarch64-unknown-linux-gnu/gcc (Release) (push) Blocked by required conditions
JIT / aarch64-apple-darwin/clang (Debug) (push) Blocked by required conditions
JIT / aarch64-unknown-linux-gnu/gcc (Debug) (push) Blocked by required conditions
JIT / x86_64-pc-windows-msvc/msvc (Release) (push) Blocked by required conditions
JIT / x86_64-pc-windows-msvc/msvc (Debug) (push) Blocked by required conditions
JIT / x86_64-apple-darwin/clang (Release) (push) Blocked by required conditions
JIT / x86_64-unknown-linux-gnu/gcc (Release) (push) Blocked by required conditions
JIT / x86_64-apple-darwin/clang (Debug) (push) Blocked by required conditions
JIT / x86_64-unknown-linux-gnu/gcc (Debug) (push) Blocked by required conditions
JIT / Free-Threaded (Debug) (push) Blocked by required conditions
JIT / JIT without optimizations (Debug) (push) Blocked by required conditions
JIT / JIT with tail calling interpreter (push) Blocked by required conditions
Lint / lint (push) Waiting to run
mypy / Run mypy on Lib/_pyrepl (push) Waiting to run
mypy / Run mypy on Lib/test/libregrtest (push) Waiting to run
mypy / Run mypy on Lib/tomllib (push) Waiting to run
mypy / Run mypy on Tools/build (push) Waiting to run
mypy / Run mypy on Tools/cases_generator (push) Waiting to run
mypy / Run mypy on Tools/check-c-api-docs (push) Waiting to run
mypy / Run mypy on Tools/clinic (push) Waiting to run
mypy / Run mypy on Tools/jit (push) Waiting to run
mypy / Run mypy on Tools/peg_generator (push) Waiting to run
2025-12-22 21:45:28 +05:30
Kumar Aditya
e728b006de
gh-143057: avoid locking in tracemalloc C-APIs when it is not enabled (#143065) 2025-12-22 21:08:07 +05:30
Ken Jin
9ded3dd4e9
gh-142476: Fix Windows crashing with JIT (GH-143021)
Fix Windows crashing with JIT
2025-12-22 14:57:13 +00:00
Hugo van Kemenade
ff7f62eb23
gh-142927: Tachyon: Comma separate thousands and fix singular/plurals (#142934) 2025-12-22 14:15:57 +00:00
Yongtao Huang
3960878143
Remove unreachable code in mmapmodule error path on Windows (GH-143063)
mmapmodule: remove unreachable code in Windows error path

Remove an unreachable `return NULL` after `PyErr_SetFromWindowsErr()` in
the Windows mmap resize error path.

Signed-off-by: Yongtao Huang <yongtaoh2022@gmail.com>
2025-12-22 11:16:50 +01:00
Bartosz Sławecki
6213a512bf
gh-143046: Make asyncio REPL respect the -q flag (quiet mode) (#143047)
Some checks are pending
Tests / (push) Blocked by required conditions
Tests / Change detection (push) Waiting to run
Tests / Docs (push) Blocked by required conditions
Tests / Windows MSI (push) Blocked by required conditions
Tests / Check if Autoconf files are up to date (push) Blocked by required conditions
Tests / Check if generated files are up to date (push) Blocked by required conditions
Tests / Ubuntu SSL tests with OpenSSL (push) Blocked by required conditions
Tests / Ubuntu SSL tests with AWS-LC (push) Blocked by required conditions
Tests / Android (aarch64) (push) Blocked by required conditions
Tests / Android (x86_64) (push) Blocked by required conditions
Tests / iOS (push) Blocked by required conditions
Tests / WASI (push) Blocked by required conditions
Tests / Hypothesis tests on Ubuntu (push) Blocked by required conditions
Tests / Address sanitizer (push) Blocked by required conditions
Tests / Sanitizers (push) Blocked by required conditions
Tests / Cross build Linux (push) Blocked by required conditions
Tests / CIFuzz (push) Blocked by required conditions
Tests / All required checks pass (push) Blocked by required conditions
Lint / lint (push) Waiting to run
mypy / Run mypy on Lib/_pyrepl (push) Waiting to run
mypy / Run mypy on Lib/test/libregrtest (push) Waiting to run
mypy / Run mypy on Lib/tomllib (push) Waiting to run
mypy / Run mypy on Tools/build (push) Waiting to run
mypy / Run mypy on Tools/cases_generator (push) Waiting to run
mypy / Run mypy on Tools/check-c-api-docs (push) Waiting to run
mypy / Run mypy on Tools/clinic (push) Waiting to run
mypy / Run mypy on Tools/jit (push) Waiting to run
mypy / Run mypy on Tools/peg_generator (push) Waiting to run
2025-12-22 07:05:15 +00:00
saucoide
09044dd42b
gh-80744: do not read .pdbrc twice when cwd == $home (#136816)
Some checks are pending
Tests / (push) Blocked by required conditions
Tests / Windows MSI (push) Blocked by required conditions
Tests / Change detection (push) Waiting to run
Tests / Docs (push) Blocked by required conditions
Tests / Check if Autoconf files are up to date (push) Blocked by required conditions
Tests / Check if generated files are up to date (push) Blocked by required conditions
Tests / Ubuntu SSL tests with OpenSSL (push) Blocked by required conditions
Tests / Ubuntu SSL tests with AWS-LC (push) Blocked by required conditions
Tests / Android (aarch64) (push) Blocked by required conditions
Tests / Android (x86_64) (push) Blocked by required conditions
Tests / iOS (push) Blocked by required conditions
Tests / WASI (push) Blocked by required conditions
Tests / Hypothesis tests on Ubuntu (push) Blocked by required conditions
Tests / Address sanitizer (push) Blocked by required conditions
Tests / Sanitizers (push) Blocked by required conditions
Tests / Cross build Linux (push) Blocked by required conditions
Tests / CIFuzz (push) Blocked by required conditions
Tests / All required checks pass (push) Blocked by required conditions
Lint / lint (push) Waiting to run
mypy / Run mypy on Lib/_pyrepl (push) Waiting to run
mypy / Run mypy on Lib/test/libregrtest (push) Waiting to run
mypy / Run mypy on Lib/tomllib (push) Waiting to run
mypy / Run mypy on Tools/build (push) Waiting to run
mypy / Run mypy on Tools/cases_generator (push) Waiting to run
mypy / Run mypy on Tools/check-c-api-docs (push) Waiting to run
mypy / Run mypy on Tools/clinic (push) Waiting to run
mypy / Run mypy on Tools/jit (push) Waiting to run
mypy / Run mypy on Tools/peg_generator (push) Waiting to run
2025-12-21 08:58:07 -08:00
Gregory P. Smith
b8d3fddba6
gh-70647: Better promote how to safely parse yearless dates in datetime. (GH-116179)
Some checks are pending
Tests / Check if Autoconf files are up to date (push) Blocked by required conditions
Tests / (push) Blocked by required conditions
Tests / Change detection (push) Waiting to run
Tests / Docs (push) Blocked by required conditions
Tests / Windows MSI (push) Blocked by required conditions
Tests / Check if generated files are up to date (push) Blocked by required conditions
Tests / Ubuntu SSL tests with OpenSSL (push) Blocked by required conditions
Tests / Ubuntu SSL tests with AWS-LC (push) Blocked by required conditions
Tests / Android (aarch64) (push) Blocked by required conditions
Tests / Android (x86_64) (push) Blocked by required conditions
Tests / iOS (push) Blocked by required conditions
Tests / WASI (push) Blocked by required conditions
Tests / Hypothesis tests on Ubuntu (push) Blocked by required conditions
Tests / Address sanitizer (push) Blocked by required conditions
Tests / Sanitizers (push) Blocked by required conditions
Tests / Cross build Linux (push) Blocked by required conditions
Tests / CIFuzz (push) Blocked by required conditions
Tests / All required checks pass (push) Blocked by required conditions
Lint / lint (push) Waiting to run
mypy / Run mypy on Lib/_pyrepl (push) Waiting to run
mypy / Run mypy on Lib/test/libregrtest (push) Waiting to run
mypy / Run mypy on Lib/tomllib (push) Waiting to run
mypy / Run mypy on Tools/build (push) Waiting to run
mypy / Run mypy on Tools/cases_generator (push) Waiting to run
mypy / Run mypy on Tools/check-c-api-docs (push) Waiting to run
mypy / Run mypy on Tools/clinic (push) Waiting to run
mypy / Run mypy on Tools/jit (push) Waiting to run
mypy / Run mypy on Tools/peg_generator (push) Waiting to run
* gh-70647: Better promote how to safely parse yearless dates in datetime.

Every four years people encounter this because it just isn't obvious.
This moves the footnote up to a note with a code example.

We'd love to change the default year value for datetime but doing
that could have other consequences for existing code.  This documented
workaround *always* works.

* doctest code within note is bad, dedent.

* Update to match the error message.

* remove no longer referenced footnote

* ignore the warning in the doctest

* use Petr's suggestion for the docs to hide the warning processing

* cover date.strptime (3.14) as well
2025-12-20 22:47:40 -08:00
Gregory P. Smith
8d2d7bb2e7
gh-142145: relax the no-longer-quadratic test timing (#143030)
Some checks are pending
Tests / Windows MSI (push) Blocked by required conditions
Tests / (push) Blocked by required conditions
Tests / Change detection (push) Waiting to run
Tests / Docs (push) Blocked by required conditions
Tests / Check if Autoconf files are up to date (push) Blocked by required conditions
Tests / Check if generated files are up to date (push) Blocked by required conditions
Tests / Ubuntu SSL tests with OpenSSL (push) Blocked by required conditions
Tests / Ubuntu SSL tests with AWS-LC (push) Blocked by required conditions
Tests / Android (aarch64) (push) Blocked by required conditions
Tests / Android (x86_64) (push) Blocked by required conditions
Tests / iOS (push) Blocked by required conditions
Tests / WASI (push) Blocked by required conditions
Tests / Hypothesis tests on Ubuntu (push) Blocked by required conditions
Tests / Address sanitizer (push) Blocked by required conditions
Tests / Sanitizers (push) Blocked by required conditions
Tests / Cross build Linux (push) Blocked by required conditions
Tests / CIFuzz (push) Blocked by required conditions
Tests / All required checks pass (push) Blocked by required conditions
Lint / lint (push) Waiting to run
mypy / Run mypy on Lib/_pyrepl (push) Waiting to run
mypy / Run mypy on Lib/test/libregrtest (push) Waiting to run
mypy / Run mypy on Lib/tomllib (push) Waiting to run
mypy / Run mypy on Tools/build (push) Waiting to run
mypy / Run mypy on Tools/cases_generator (push) Waiting to run
mypy / Run mypy on Tools/check-c-api-docs (push) Waiting to run
mypy / Run mypy on Tools/clinic (push) Waiting to run
mypy / Run mypy on Tools/jit (push) Waiting to run
mypy / Run mypy on Tools/peg_generator (push) Waiting to run
* gh-142145: relax the no-longer-quadratic test timing

* require cpu resource
2025-12-20 23:42:06 +00:00
Sam Gross
2b4feee648
gh-122581: Use parser mutex in default build for subinterpreters (gh-142959) 2025-12-20 15:37:31 -05:00
Sam Gross
7607712b61
gh-120321: Avoid -Wunreachable-code warning on Clang (gh-143022) 2025-12-20 14:42:12 -05:00
Hai Zhu
3cc57505e5
gh-142834: pdb commands command should use last available breakpoint (#142835) 2025-12-20 09:27:34 -08:00
AZero13
5989095dfd
gh-143012: use Py_ssize_t cast for PyBytes_FromStringAndSize (#143013)
Some checks are pending
Tests / Windows MSI (push) Blocked by required conditions
Tests / (push) Blocked by required conditions
Tests / Change detection (push) Waiting to run
Tests / Docs (push) Blocked by required conditions
Tests / Check if Autoconf files are up to date (push) Blocked by required conditions
Tests / Check if generated files are up to date (push) Blocked by required conditions
Tests / Ubuntu SSL tests with OpenSSL (push) Blocked by required conditions
Tests / Ubuntu SSL tests with AWS-LC (push) Blocked by required conditions
Tests / Android (aarch64) (push) Blocked by required conditions
Tests / Android (x86_64) (push) Blocked by required conditions
Tests / iOS (push) Blocked by required conditions
Tests / WASI (push) Blocked by required conditions
Tests / Hypothesis tests on Ubuntu (push) Blocked by required conditions
Tests / Address sanitizer (push) Blocked by required conditions
Tests / Sanitizers (push) Blocked by required conditions
Tests / Cross build Linux (push) Blocked by required conditions
Tests / CIFuzz (push) Blocked by required conditions
Tests / All required checks pass (push) Blocked by required conditions
Lint / lint (push) Waiting to run
mypy / Run mypy on Lib/_pyrepl (push) Waiting to run
mypy / Run mypy on Lib/test/libregrtest (push) Waiting to run
mypy / Run mypy on Lib/tomllib (push) Waiting to run
mypy / Run mypy on Tools/build (push) Waiting to run
mypy / Run mypy on Tools/cases_generator (push) Waiting to run
mypy / Run mypy on Tools/check-c-api-docs (push) Waiting to run
mypy / Run mypy on Tools/clinic (push) Waiting to run
mypy / Run mypy on Tools/jit (push) Waiting to run
mypy / Run mypy on Tools/peg_generator (push) Waiting to run
2025-12-20 10:37:10 +03:00
Hugo van Kemenade
5b5263648f
gh-142927: Tachyon: Start with user's default light/dark theme (#142987)
Some checks are pending
Tests / Check if Autoconf files are up to date (push) Blocked by required conditions
Tests / Windows MSI (push) Blocked by required conditions
Tests / (push) Blocked by required conditions
Tests / Change detection (push) Waiting to run
Tests / Docs (push) Blocked by required conditions
Tests / Check if generated files are up to date (push) Blocked by required conditions
Tests / Ubuntu SSL tests with OpenSSL (push) Blocked by required conditions
Tests / Ubuntu SSL tests with AWS-LC (push) Blocked by required conditions
Tests / Android (aarch64) (push) Blocked by required conditions
Tests / Android (x86_64) (push) Blocked by required conditions
Tests / iOS (push) Blocked by required conditions
Tests / WASI (push) Blocked by required conditions
Tests / Hypothesis tests on Ubuntu (push) Blocked by required conditions
Tests / Address sanitizer (push) Blocked by required conditions
Tests / Sanitizers (push) Blocked by required conditions
Tests / Cross build Linux (push) Blocked by required conditions
Tests / CIFuzz (push) Blocked by required conditions
Tests / All required checks pass (push) Blocked by required conditions
Lint / lint (push) Waiting to run
mypy / Run mypy on Lib/_pyrepl (push) Waiting to run
mypy / Run mypy on Lib/test/libregrtest (push) Waiting to run
mypy / Run mypy on Lib/tomllib (push) Waiting to run
mypy / Run mypy on Tools/build (push) Waiting to run
mypy / Run mypy on Tools/cases_generator (push) Waiting to run
mypy / Run mypy on Tools/check-c-api-docs (push) Waiting to run
mypy / Run mypy on Tools/clinic (push) Waiting to run
mypy / Run mypy on Tools/jit (push) Waiting to run
mypy / Run mypy on Tools/peg_generator (push) Waiting to run
2025-12-20 02:36:09 +01:00
Sam Gross
e46f28c6af
gh-129069: Fix listobject.c data races due to memmove (gh-142957)
Some checks failed
mypy / Run mypy on Tools/cases_generator (push) Waiting to run
mypy / Run mypy on Tools/build (push) Waiting to run
mypy / Run mypy on Tools/check-c-api-docs (push) Waiting to run
mypy / Run mypy on Tools/clinic (push) Waiting to run
mypy / Run mypy on Tools/jit (push) Waiting to run
mypy / Run mypy on Tools/peg_generator (push) Waiting to run
JIT / Interpreter (Debug) (push) Has been cancelled
Tail calling interpreter / aarch64-apple-darwin/clang (push) Has been cancelled
Tail calling interpreter / aarch64-unknown-linux-gnu/gcc (push) Has been cancelled
Tail calling interpreter / x86_64-pc-windows-msvc/msvc (push) Has been cancelled
Tail calling interpreter / x86_64-apple-darwin/clang (push) Has been cancelled
Tail calling interpreter / free-threading (push) Has been cancelled
Tail calling interpreter / x86_64-unknown-linux-gnu/gcc (push) Has been cancelled
JIT / aarch64-pc-windows-msvc/msvc (Release) (push) Has been cancelled
JIT / aarch64-pc-windows-msvc/msvc (Debug) (push) Has been cancelled
JIT / i686-pc-windows-msvc/msvc (Release) (push) Has been cancelled
JIT / i686-pc-windows-msvc/msvc (Debug) (push) Has been cancelled
JIT / aarch64-apple-darwin/clang (Release) (push) Has been cancelled
JIT / aarch64-unknown-linux-gnu/gcc (Release) (push) Has been cancelled
JIT / aarch64-apple-darwin/clang (Debug) (push) Has been cancelled
JIT / aarch64-unknown-linux-gnu/gcc (Debug) (push) Has been cancelled
JIT / x86_64-pc-windows-msvc/msvc (Release) (push) Has been cancelled
JIT / x86_64-pc-windows-msvc/msvc (Debug) (push) Has been cancelled
JIT / x86_64-apple-darwin/clang (Release) (push) Has been cancelled
JIT / x86_64-unknown-linux-gnu/gcc (Release) (push) Has been cancelled
JIT / x86_64-apple-darwin/clang (Debug) (push) Has been cancelled
JIT / x86_64-unknown-linux-gnu/gcc (Debug) (push) Has been cancelled
JIT / Free-Threaded (Debug) (push) Has been cancelled
JIT / JIT without optimizations (Debug) (push) Has been cancelled
JIT / JIT with tail calling interpreter (push) Has been cancelled
The use of memmove and _Py_memory_repeat were not thread-safe in the
free threading build in some cases. In theory, memmove and
_Py_memory_repeat can copy byte-by-byte instead of pointer-by-pointer,
so concurrent readers could see uninitialized data or tearing.

Additionally, we should be using "release" (or stronger) ordering to be
compliant with the C11 memory model when copying objects within a list.
2025-12-19 18:06:47 -05:00
Sam Gross
4ea3c1a047
gh-120321: Fix TSan reported race in gen_clear_frame (gh-142995)
TSan treats compare-exchanges that fail as if they are writes
so there is a false positive with the read of gi_frame_state in
gen_close.
2025-12-19 17:33:49 -05:00
Sam Gross
08bc03ff2a
gh-120321: Make gi_frame_state transitions atomic in FT build (gh-142599)
This makes generator frame state transitions atomic in the free
threading build, which avoids segfaults when trying to execute
a generator from multiple threads concurrently.

There are still a few operations that aren't thread-safe and may crash
if performed concurrently on the same generator/coroutine:

 * Accessing gi_yieldfrom/cr_await/ag_await
 * Accessing gi_frame/cr_frame/ag_frame
 * Async generator operations
2025-12-19 19:10:37 +00:00
Shamil
e2a7db7175
gh-142476: fix memory leak when creating JIT executors (GH-142492) 2025-12-19 19:07:11 +00:00
Ken Jin
6b4bc6e6a2
gh-134584: JIT: Borrow references for immortal promoted globals (GH-142921)
JIT: Borrow references for immortal promoted globals
2025-12-19 19:06:34 +00:00
stratakis
6a4f10325d
gh-142776: Ensure fp file descriptor is closed on all code paths in import.c (GH-142777) 2025-12-19 10:14:52 -08:00
Ken Jin
786f464c74
gh-142961: Fix constant folding len(tuple) in JIT (GH-142963) 2025-12-19 17:43:36 +00:00
Petr Viktorin
049c2526bf
gh-134160: Start "Extending and embedding" with a Diataxis-style tutorial (GH-142314)
Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com>
Co-authored-by: Éric <merwok@netwok.org>
Co-authored-by: Daniele Nicolodi <daniele@grinta.net>
Co-authored-by: Peter Bierma <zintensitydev@gmail.com>
2025-12-19 17:48:34 +01:00
Diego Russo
685272eb8a
JIT: Rename trampoline.c to shim.c (#142974)
Some checks are pending
JIT / i686-pc-windows-msvc/msvc (Debug) (push) Blocked by required conditions
JIT / aarch64-apple-darwin/clang (Release) (push) Blocked by required conditions
JIT / aarch64-unknown-linux-gnu/gcc (Release) (push) Blocked by required conditions
JIT / aarch64-apple-darwin/clang (Debug) (push) Blocked by required conditions
JIT / aarch64-unknown-linux-gnu/gcc (Debug) (push) Blocked by required conditions
JIT / x86_64-pc-windows-msvc/msvc (Release) (push) Blocked by required conditions
JIT / x86_64-pc-windows-msvc/msvc (Debug) (push) Blocked by required conditions
JIT / x86_64-apple-darwin/clang (Release) (push) Blocked by required conditions
JIT / x86_64-unknown-linux-gnu/gcc (Release) (push) Blocked by required conditions
JIT / x86_64-apple-darwin/clang (Debug) (push) Blocked by required conditions
JIT / x86_64-unknown-linux-gnu/gcc (Debug) (push) Blocked by required conditions
JIT / Free-Threaded (Debug) (push) Blocked by required conditions
JIT / JIT without optimizations (Debug) (push) Blocked by required conditions
JIT / JIT with tail calling interpreter (push) Blocked by required conditions
Lint / lint (push) Waiting to run
mypy / Run mypy on Lib/_pyrepl (push) Waiting to run
mypy / Run mypy on Lib/test/libregrtest (push) Waiting to run
mypy / Run mypy on Lib/tomllib (push) Waiting to run
mypy / Run mypy on Tools/build (push) Waiting to run
mypy / Run mypy on Tools/cases_generator (push) Waiting to run
mypy / Run mypy on Tools/check-c-api-docs (push) Waiting to run
mypy / Run mypy on Tools/clinic (push) Waiting to run
mypy / Run mypy on Tools/jit (push) Waiting to run
mypy / Run mypy on Tools/peg_generator (push) Waiting to run
Tail calling interpreter / aarch64-apple-darwin/clang (push) Waiting to run
Tail calling interpreter / aarch64-unknown-linux-gnu/gcc (push) Waiting to run
Tail calling interpreter / x86_64-pc-windows-msvc/msvc (push) Waiting to run
Tail calling interpreter / x86_64-apple-darwin/clang (push) Waiting to run
Tail calling interpreter / free-threading (push) Waiting to run
Tail calling interpreter / x86_64-unknown-linux-gnu/gcc (push) Waiting to run
2025-12-19 14:39:41 +00:00
Rogdham
4aef138325
gh-136282: Configparser: create unnamed sections via mapping protocol access (GH-136313) 2025-12-19 13:44:03 +01:00
Andrej
610aabfef2
gh-142527: Docs: Clarify that random.seed() discards the sign of an integer input (#142483)
If *a* is an integer, the sign of *a* is discarded in the C source code. Clarify this behavior to prevent foot guns, where a common use case might naively assume that flipping the sign will produce different sequences (e.g. for a train/test split of a synthetic data generator in machine learning).

Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
2025-12-19 00:29:35 -08:00
wangxiaolei
220f0b1077
gh-142560: prevent use-after-free in search-like methods by exporting buffer in bytearray (#142938) 2025-12-19 08:02:23 +00:00
Savannah Ostrowski
1391ee664c
GH-134584: Remove redundant refcount for BINARY_OP_SUBSCR_STR_INT (#142844)
Some checks are pending
JIT / i686-pc-windows-msvc/msvc (Debug) (push) Blocked by required conditions
JIT / aarch64-apple-darwin/clang (Release) (push) Blocked by required conditions
JIT / aarch64-unknown-linux-gnu/gcc (Release) (push) Blocked by required conditions
JIT / aarch64-apple-darwin/clang (Debug) (push) Blocked by required conditions
JIT / aarch64-unknown-linux-gnu/gcc (Debug) (push) Blocked by required conditions
JIT / x86_64-pc-windows-msvc/msvc (Release) (push) Blocked by required conditions
JIT / x86_64-apple-darwin/clang (Debug) (push) Blocked by required conditions
JIT / x86_64-unknown-linux-gnu/gcc (Debug) (push) Blocked by required conditions
JIT / Free-Threaded (Debug) (push) Blocked by required conditions
JIT / JIT without optimizations (Debug) (push) Blocked by required conditions
Lint / lint (push) Waiting to run
mypy / Run mypy on Lib/_pyrepl (push) Waiting to run
mypy / Run mypy on Lib/test/libregrtest (push) Waiting to run
mypy / Run mypy on Lib/tomllib (push) Waiting to run
mypy / Run mypy on Tools/build (push) Waiting to run
mypy / Run mypy on Tools/cases_generator (push) Waiting to run
mypy / Run mypy on Tools/check-c-api-docs (push) Waiting to run
mypy / Run mypy on Tools/clinic (push) Waiting to run
mypy / Run mypy on Tools/jit (push) Waiting to run
mypy / Run mypy on Tools/peg_generator (push) Waiting to run
Tail calling interpreter / aarch64-apple-darwin/clang (push) Waiting to run
JIT / x86_64-pc-windows-msvc/msvc (Debug) (push) Blocked by required conditions
JIT / x86_64-apple-darwin/clang (Release) (push) Blocked by required conditions
JIT / x86_64-unknown-linux-gnu/gcc (Release) (push) Blocked by required conditions
JIT / JIT with tail calling interpreter (push) Blocked by required conditions
Tail calling interpreter / aarch64-unknown-linux-gnu/gcc (push) Waiting to run
Tail calling interpreter / x86_64-pc-windows-msvc/msvc (push) Waiting to run
Tail calling interpreter / x86_64-apple-darwin/clang (push) Waiting to run
Tail calling interpreter / free-threading (push) Waiting to run
Tail calling interpreter / x86_64-unknown-linux-gnu/gcc (push) Waiting to run
2025-12-18 21:29:54 +00:00
Ethan Furman
e79c39101a
gh-118342: [Enum] update docs (GH-137290)
Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com>
2025-12-18 18:31:37 +00:00
LloydZ
33d94abafd
gh-134584: Eliminate redundant refcounting from _BINARY_OP_SUBSCR_LIST_INT (GH-142926) 2025-12-18 18:25:36 +00:00
Sam Gross
f54d44d333
gh-129068: Make range iterators thread-safe (gh-142886)
Now that we specialize range iteration in the interpreter for the common
case where the iterator has only one reference, there's not a
significant performance cost to making the iteration thread-safe.
2025-12-18 13:11:51 -05:00
Kumar Aditya
e22c49522b
gh-142890: remove unnecessary interp parameter from dict functions and _PyDict_NotifyEvent (#142923) 2025-12-18 22:48:56 +05:30
Serhiy Storchaka
4a8ecbad80
gh-142681: Move NormalizationTest-3.2.0.txt to more safe place. (GH-142935) 2025-12-18 18:04:28 +01:00
Mark Shannon
e4058d7cb1
GH-142513: Reimplement executor management (GH-142931)
* Invalidating an executor does not cause arbitrary code to run
* Executors are only freed at safe points
2025-12-18 16:43:44 +00:00
Donghee Na
14f0b5191a
gh-142419: Add mmap.set_name method for user custom annotation (gh-142480)
Some checks are pending
JIT / i686-pc-windows-msvc/msvc (Release) (push) Blocked by required conditions
JIT / i686-pc-windows-msvc/msvc (Debug) (push) Blocked by required conditions
JIT / Interpreter (Debug) (push) Waiting to run
JIT / aarch64-unknown-linux-gnu/gcc (Release) (push) Blocked by required conditions
JIT / aarch64-apple-darwin/clang (Debug) (push) Blocked by required conditions
JIT / aarch64-unknown-linux-gnu/gcc (Debug) (push) Blocked by required conditions
JIT / x86_64-pc-windows-msvc/msvc (Release) (push) Blocked by required conditions
JIT / x86_64-pc-windows-msvc/msvc (Debug) (push) Blocked by required conditions
JIT / x86_64-unknown-linux-gnu/gcc (Release) (push) Blocked by required conditions
JIT / x86_64-apple-darwin/clang (Debug) (push) Blocked by required conditions
JIT / x86_64-unknown-linux-gnu/gcc (Debug) (push) Blocked by required conditions
JIT / Free-Threaded (Debug) (push) Blocked by required conditions
JIT / JIT without optimizations (Debug) (push) Blocked by required conditions
JIT / JIT with tail calling interpreter (push) Blocked by required conditions
Lint / lint (push) Waiting to run
mypy / Run mypy on Lib/_pyrepl (push) Waiting to run
mypy / Run mypy on Lib/test/libregrtest (push) Waiting to run
mypy / Run mypy on Lib/tomllib (push) Waiting to run
mypy / Run mypy on Tools/build (push) Waiting to run
mypy / Run mypy on Tools/cases_generator (push) Waiting to run
mypy / Run mypy on Tools/check-c-api-docs (push) Waiting to run
mypy / Run mypy on Tools/clinic (push) Waiting to run
mypy / Run mypy on Tools/jit (push) Waiting to run
mypy / Run mypy on Tools/peg_generator (push) Waiting to run
Tail calling interpreter / aarch64-unknown-linux-gnu/gcc (push) Waiting to run
Tail calling interpreter / aarch64-apple-darwin/clang (push) Waiting to run
Tail calling interpreter / x86_64-unknown-linux-gnu/gcc (push) Waiting to run
Tail calling interpreter / x86_64-pc-windows-msvc/msvc (push) Waiting to run
Tail calling interpreter / x86_64-apple-darwin/clang (push) Waiting to run
Tail calling interpreter / free-threading (push) Waiting to run
2025-12-18 23:33:49 +09:00
Hugo van Kemenade
d2abd5733b
gh-76007: Deprecate VERSION in xml.etree.ElementTree & version in xml.sax.expatreader & xml.sax.handler (#142898) 2025-12-18 14:22:23 +00:00
Max R
0f01530bd5
Fix typo in format_string docstring (GH-136742) 2025-12-18 14:43:19 +01:00
Bartosz Sławecki
ddfc155d3a
gh-142784: make the asyncio REPL call loop.close() at exit (#142785) 2025-12-18 13:00:12 +00:00
MonadChains
1c544acaa5
gh-124098: Fix incorrect inclusion of handler methods without protocol prefix in OpenerDirector (GH-136873) 2025-12-18 13:50:05 +01:00
Donghee Na
71a7cb8887
gh-134584: Remove redundant refcount from _BINARY_OP_ADD_UNICODE (gh-142825) 2025-12-18 21:33:18 +09:00
James
fc80096a07
gh-137063: Document that ast node types replaced by Constant are no longer available (#137064) 2025-12-18 13:17:42 +01:00
ivonastojanovic
cbc0851ada
gh-138122: Improve bytecode panel (#142910)
The bytecode panel appears when a user generates a heatmap with
--opcodes and clicks the button to unfold a line and display the
bytecode instructions. Currently, an empty space appears on the
left where the line number, self, and total columns are displayed.
This area should instead extend those columns, rather than leaving
a gap.
2025-12-18 11:43:39 +00:00
Ken Jin
8b64dd853d
gh-139757: Treat call specially in JIT assembly backend optimizer on x86-64 and AArch64 (GH-142907)
Some checks are pending
Tests / Cross build Linux (push) Blocked by required conditions
Tests / CIFuzz (push) Blocked by required conditions
JIT / Interpreter (Debug) (push) Waiting to run
JIT / aarch64-pc-windows-msvc/msvc (Release) (push) Blocked by required conditions
JIT / aarch64-pc-windows-msvc/msvc (Debug) (push) Blocked by required conditions
JIT / i686-pc-windows-msvc/msvc (Release) (push) Blocked by required conditions
JIT / i686-pc-windows-msvc/msvc (Debug) (push) Blocked by required conditions
JIT / aarch64-apple-darwin/clang (Release) (push) Blocked by required conditions
JIT / x86_64-apple-darwin/clang (Release) (push) Blocked by required conditions
JIT / aarch64-unknown-linux-gnu/gcc (Release) (push) Blocked by required conditions
JIT / aarch64-apple-darwin/clang (Debug) (push) Blocked by required conditions
JIT / aarch64-unknown-linux-gnu/gcc (Debug) (push) Blocked by required conditions
JIT / x86_64-pc-windows-msvc/msvc (Release) (push) Blocked by required conditions
JIT / x86_64-pc-windows-msvc/msvc (Debug) (push) Blocked by required conditions
JIT / x86_64-unknown-linux-gnu/gcc (Release) (push) Blocked by required conditions
JIT / x86_64-apple-darwin/clang (Debug) (push) Blocked by required conditions
JIT / x86_64-unknown-linux-gnu/gcc (Debug) (push) Blocked by required conditions
JIT / Free-Threaded (Debug) (push) Blocked by required conditions
JIT / JIT without optimizations (Debug) (push) Blocked by required conditions
JIT / JIT with tail calling interpreter (push) Blocked by required conditions
Lint / lint (push) Waiting to run
mypy / Run mypy on Lib/_pyrepl (push) Waiting to run
mypy / Run mypy on Lib/test/libregrtest (push) Waiting to run
mypy / Run mypy on Lib/tomllib (push) Waiting to run
mypy / Run mypy on Tools/build (push) Waiting to run
mypy / Run mypy on Tools/cases_generator (push) Waiting to run
mypy / Run mypy on Tools/check-c-api-docs (push) Waiting to run
mypy / Run mypy on Tools/clinic (push) Waiting to run
mypy / Run mypy on Tools/jit (push) Waiting to run
mypy / Run mypy on Tools/peg_generator (push) Waiting to run
Co-authored-by: Savannah Ostrowski <savannah@python.org>
2025-12-17 22:47:47 +00:00
Sam Gross
6e625f87d2
gh-129748: Remove TSan suppression for mi_block_set_nextx (gh-142887)
Some checks are pending
JIT / x86_64-unknown-linux-gnu/gcc (Debug) (push) Blocked by required conditions
JIT / i686-pc-windows-msvc/msvc (Debug) (push) Blocked by required conditions
JIT / aarch64-apple-darwin/clang (Release) (push) Blocked by required conditions
JIT / aarch64-unknown-linux-gnu/gcc (Release) (push) Blocked by required conditions
JIT / aarch64-apple-darwin/clang (Debug) (push) Blocked by required conditions
JIT / aarch64-unknown-linux-gnu/gcc (Debug) (push) Blocked by required conditions
JIT / x86_64-pc-windows-msvc/msvc (Release) (push) Blocked by required conditions
JIT / Interpreter (Debug) (push) Waiting to run
JIT / aarch64-pc-windows-msvc/msvc (Release) (push) Blocked by required conditions
JIT / aarch64-pc-windows-msvc/msvc (Debug) (push) Blocked by required conditions
JIT / i686-pc-windows-msvc/msvc (Release) (push) Blocked by required conditions
JIT / Free-Threaded (Debug) (push) Blocked by required conditions
JIT / JIT with tail calling interpreter (push) Blocked by required conditions
Lint / lint (push) Waiting to run
mypy / Run mypy on Tools/peg_generator (push) Waiting to run
JIT / JIT without optimizations (Debug) (push) Blocked by required conditions
mypy / Run mypy on Lib/_pyrepl (push) Waiting to run
mypy / Run mypy on Lib/test/libregrtest (push) Waiting to run
mypy / Run mypy on Lib/tomllib (push) Waiting to run
mypy / Run mypy on Tools/build (push) Waiting to run
mypy / Run mypy on Tools/cases_generator (push) Waiting to run
mypy / Run mypy on Tools/check-c-api-docs (push) Waiting to run
mypy / Run mypy on Tools/clinic (push) Waiting to run
mypy / Run mypy on Tools/jit (push) Waiting to run
Tail calling interpreter / x86_64-apple-darwin/clang (push) Waiting to run
Tail calling interpreter / free-threading (push) Waiting to run
Tail calling interpreter / x86_64-unknown-linux-gnu/gcc (push) Waiting to run
Tail calling interpreter / aarch64-apple-darwin/clang (push) Waiting to run
Tail calling interpreter / aarch64-unknown-linux-gnu/gcc (push) Waiting to run
Tail calling interpreter / x86_64-pc-windows-msvc/msvc (push) Waiting to run
The function was already changed to use a relaxed atomic store in gh-134238.
2025-12-17 15:27:04 -05:00
Savannah Ostrowski
92243dc62c
GH-100964: Fix reference cycle in exhausted generator frames (#141112) 2025-12-17 19:21:45 +00:00
Sam Gross
25397f9541
gh-142766: Clear frame when generator.close() is called (gh-142838) 2025-12-17 13:06:32 -05:00
Pablo Galindo Salgado
77b56eafde
gh-91048: Refactor common data into context object in Modules/_remote_debugging (#142879) 2025-12-17 17:43:52 +00:00
Ken Jin
fba4584ffc
gh-142849: Fix segfault in executor_to_gv (GH-142885)
Fix segfault in `executor_to_gv`
2025-12-17 17:05:21 +00:00
sobolevn
e61a447d0e
gh-142873: Do not check for PyContextVar_CheckExact twice in PyContextVar_Set (#142874) 2025-12-17 19:41:36 +03:00
Damian Birchler
77c8e6a2b8
gh-142876: remove reference to thread in documentation of asyncio.Queue.shutdown (#142888) 2025-12-17 16:40:03 +00:00
Petr Viktorin
7d81eab923
gh-142225: Add PyABIInfo_VAR to to _testcapimodule & _testinternalcapi (GH-142833) 2025-12-17 16:33:09 +01:00
Pablo Galindo Salgado
568a819f67
gh-138122: Validate base frame before caching in remote debugging frame cache (#142852) 2025-12-17 15:12:28 +00:00
Benjamin Johnson
2b466c47c3
gh-112127: Fix possible use-after-free in atexit.unregister() (GH-114092)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
2025-12-17 17:09:57 +02:00
Ken Jin
49627dc991
Use other name for JIT contributor (#142877)
Some checks are pending
Tests / Cross build Linux (push) Blocked by required conditions
Tests / CIFuzz (push) Blocked by required conditions
JIT / aarch64-unknown-linux-gnu/gcc (Release) (push) Blocked by required conditions
JIT / aarch64-apple-darwin/clang (Debug) (push) Blocked by required conditions
JIT / aarch64-unknown-linux-gnu/gcc (Debug) (push) Blocked by required conditions
JIT / x86_64-pc-windows-msvc/msvc (Release) (push) Blocked by required conditions
JIT / x86_64-pc-windows-msvc/msvc (Debug) (push) Blocked by required conditions
JIT / x86_64-apple-darwin/clang (Release) (push) Blocked by required conditions
JIT / x86_64-unknown-linux-gnu/gcc (Release) (push) Blocked by required conditions
JIT / x86_64-apple-darwin/clang (Debug) (push) Blocked by required conditions
JIT / x86_64-unknown-linux-gnu/gcc (Debug) (push) Blocked by required conditions
JIT / i686-pc-windows-msvc/msvc (Debug) (push) Blocked by required conditions
JIT / aarch64-apple-darwin/clang (Release) (push) Blocked by required conditions
JIT / Interpreter (Debug) (push) Waiting to run
JIT / aarch64-pc-windows-msvc/msvc (Release) (push) Blocked by required conditions
JIT / aarch64-pc-windows-msvc/msvc (Debug) (push) Blocked by required conditions
JIT / i686-pc-windows-msvc/msvc (Release) (push) Blocked by required conditions
JIT / Free-Threaded (Debug) (push) Blocked by required conditions
JIT / JIT without optimizations (Debug) (push) Blocked by required conditions
JIT / JIT with tail calling interpreter (push) Blocked by required conditions
Lint / lint (push) Waiting to run
mypy / Run mypy on Lib/_pyrepl (push) Waiting to run
mypy / Run mypy on Lib/test/libregrtest (push) Waiting to run
mypy / Run mypy on Lib/tomllib (push) Waiting to run
mypy / Run mypy on Tools/build (push) Waiting to run
mypy / Run mypy on Tools/cases_generator (push) Waiting to run
mypy / Run mypy on Tools/check-c-api-docs (push) Waiting to run
mypy / Run mypy on Tools/clinic (push) Waiting to run
mypy / Run mypy on Tools/jit (push) Waiting to run
mypy / Run mypy on Tools/peg_generator (push) Waiting to run
2025-12-17 14:21:02 +00:00
Keming
d4095f25e8
gh-142654: show the clear error message when sampling on an unknown PID in tachyon (#142655)
Co-authored-by: Pablo Galindo Salgado <pablogsal@gmail.com>
2025-12-17 14:15:22 +00:00
Savannah Ostrowski
1fc3039d71
gh-139038: Add JIT What's New for 3.15 (#142845)
Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
Co-authored-by: Ken Jin <kenjin4096@gmail.com>
2025-12-17 12:14:44 +00:00
Mark Shannon
c7dcb26520
GH-142621: JIT: Avoid memory load for symbols within 4GB on AArch64 (GH-142820) 2025-12-17 12:07:07 +00:00
sobolevn
248eb3efb3
gh-142859: Add Tools/check-c-api-docs to mypy check (#142860) 2025-12-17 13:23:30 +02:00
Lysandros Nikolaou
1e9a0ee682
gh-140374: Add glossary entries related to multithreading (#140375)
---------

Co-authored-by: Daniele Parmeggiani <8658291+dpdani@users.noreply.github.com>
2025-12-17 12:09:51 +01:00
yihong
454485e564
gh-139743: Avoid print twice verbose version for sqlite tests (GH-142850)
Signed-off-by: yihong0618 <zouzou0208@gmail.com>
2025-12-17 09:44:47 +00:00
wangxiaolei
8307a14d0e
gh-142783: Fix possible use after free in zoneinfo module (GH-142790) 2025-12-17 08:35:08 +00:00
Kumar Aditya
4fd006e712
gh-142752: add more thread safety tests for mock (#142791) 2025-12-17 13:17:12 +05:30
Jason R. Coombs
c35b812e77
gh-142836: Avoid /proc fd pipes on Solaris (#142853) 2025-12-17 07:38:00 +00:00
Ken Jin
89729f2ef7
gh-142543: Mark tracer functions as Py_NO_INLINE (GH-142846)
Some checks are pending
JIT / x86_64-pc-windows-msvc/msvc (Release) (push) Blocked by required conditions
JIT / x86_64-pc-windows-msvc/msvc (Debug) (push) Blocked by required conditions
JIT / x86_64-apple-darwin/clang (Release) (push) Blocked by required conditions
JIT / x86_64-unknown-linux-gnu/gcc (Release) (push) Blocked by required conditions
JIT / x86_64-apple-darwin/clang (Debug) (push) Blocked by required conditions
JIT / x86_64-unknown-linux-gnu/gcc (Debug) (push) Blocked by required conditions
JIT / Interpreter (Debug) (push) Waiting to run
JIT / aarch64-pc-windows-msvc/msvc (Release) (push) Blocked by required conditions
JIT / aarch64-pc-windows-msvc/msvc (Debug) (push) Blocked by required conditions
JIT / i686-pc-windows-msvc/msvc (Release) (push) Blocked by required conditions
JIT / i686-pc-windows-msvc/msvc (Debug) (push) Blocked by required conditions
JIT / Free-Threaded (Debug) (push) Blocked by required conditions
JIT / JIT without optimizations (Debug) (push) Blocked by required conditions
JIT / JIT with tail calling interpreter (push) Blocked by required conditions
mypy / Run mypy on Tools/build (push) Waiting to run
mypy / Run mypy on Tools/cases_generator (push) Waiting to run
mypy / Run mypy on Tools/clinic (push) Waiting to run
mypy / Run mypy on Tools/jit (push) Waiting to run
mypy / Run mypy on Tools/peg_generator (push) Waiting to run
JIT / aarch64-apple-darwin/clang (Release) (push) Blocked by required conditions
Lint / lint (push) Waiting to run
mypy / Run mypy on Lib/_pyrepl (push) Waiting to run
mypy / Run mypy on Lib/test/libregrtest (push) Waiting to run
mypy / Run mypy on Lib/tomllib (push) Waiting to run
Tail calling interpreter / aarch64-apple-darwin/clang (push) Waiting to run
Tail calling interpreter / aarch64-unknown-linux-gnu/gcc (push) Waiting to run
Tail calling interpreter / x86_64-pc-windows-msvc/msvc (push) Waiting to run
Tail calling interpreter / x86_64-apple-darwin/clang (push) Waiting to run
Tail calling interpreter / free-threading (push) Waiting to run
Tail calling interpreter / x86_64-unknown-linux-gnu/gcc (push) Waiting to run
2025-12-17 00:12:32 +00:00
Nadeshiko Manju
4345253981
gh-134584: Eliminate redundant refcounting from _STORE_ATTR_WITH_HINT (GH-142767)
Signed-off-by: Manjusaka <me@manjusaka.me>
2025-12-16 22:21:04 +00:00
ivonastojanovic
8c87bcd7f2
gh-138122: Update Tachyon dark theme colors (#142841)
Some checks are pending
JIT / i686-pc-windows-msvc/msvc (Release) (push) Blocked by required conditions
JIT / i686-pc-windows-msvc/msvc (Debug) (push) Blocked by required conditions
JIT / aarch64-apple-darwin/clang (Release) (push) Blocked by required conditions
JIT / aarch64-unknown-linux-gnu/gcc (Release) (push) Blocked by required conditions
JIT / aarch64-apple-darwin/clang (Debug) (push) Blocked by required conditions
JIT / aarch64-unknown-linux-gnu/gcc (Debug) (push) Blocked by required conditions
JIT / x86_64-pc-windows-msvc/msvc (Release) (push) Blocked by required conditions
JIT / x86_64-pc-windows-msvc/msvc (Debug) (push) Blocked by required conditions
JIT / x86_64-apple-darwin/clang (Release) (push) Blocked by required conditions
JIT / x86_64-unknown-linux-gnu/gcc (Release) (push) Blocked by required conditions
JIT / x86_64-apple-darwin/clang (Debug) (push) Blocked by required conditions
JIT / x86_64-unknown-linux-gnu/gcc (Debug) (push) Blocked by required conditions
JIT / Free-Threaded (Debug) (push) Blocked by required conditions
JIT / JIT without optimizations (Debug) (push) Blocked by required conditions
JIT / JIT with tail calling interpreter (push) Blocked by required conditions
Lint / lint (push) Waiting to run
mypy / Run mypy on Lib/_pyrepl (push) Waiting to run
mypy / Run mypy on Tools/build (push) Waiting to run
mypy / Run mypy on Lib/test/libregrtest (push) Waiting to run
mypy / Run mypy on Lib/tomllib (push) Waiting to run
mypy / Run mypy on Tools/jit (push) Waiting to run
mypy / Run mypy on Tools/peg_generator (push) Waiting to run
Tail calling interpreter / aarch64-unknown-linux-gnu/gcc (push) Waiting to run
mypy / Run mypy on Tools/cases_generator (push) Waiting to run
mypy / Run mypy on Tools/clinic (push) Waiting to run
Tail calling interpreter / aarch64-apple-darwin/clang (push) Waiting to run
Tail calling interpreter / x86_64-pc-windows-msvc/msvc (push) Waiting to run
Tail calling interpreter / x86_64-apple-darwin/clang (push) Waiting to run
Tail calling interpreter / free-threading (push) Waiting to run
Tail calling interpreter / x86_64-unknown-linux-gnu/gcc (push) Waiting to run
2025-12-16 20:58:56 +00:00
Nadeshiko Manju
6ee51a36b3
gh-134584: Eliminate redundant refcounting from _LOAD_ATTR_INSTANCE_VALUE (GH-142769)
Signed-off-by: Manjusaka <me@manjusaka.me>
2025-12-16 20:39:20 +00:00
Mark Shannon
92d4aeafd5
GH-142629: JIT: Fix out of bounds memory read in lltrace (GH-142821)
JIT: Fix out of bounds memory read in lltrace
2025-12-16 19:57:15 +00:00
Gabriele N. Tornetta
16a305f152
Make RESUME monitoring more readable and robust (GH-142136) 2025-12-16 16:23:27 +00:00
Edward Xu
a043407510
gh-142495: Make defaultdict keep existed value when racing with __missing__ (GH-142668) 2025-12-16 17:04:20 +02:00
Hugo van Kemenade
47ec96f133 Post 3.15.0a3 2025-12-16 16:31:52 +02:00
196 changed files with 12563 additions and 3175 deletions

4
.gitattributes vendored
View file

@ -83,8 +83,10 @@ Include/opcode.h generated
Include/opcode_ids.h generated
Include/token.h generated
Lib/_opcode_metadata.py generated
Lib/keyword.py generated
Lib/idlelib/help.html generated
Lib/keyword.py generated
Lib/pydoc_data/topics.py generated
Lib/pydoc_data/module_docs.py generated
Lib/test/certdata/*.pem generated
Lib/test/certdata/*.0 generated
Lib/test/levenshtein_examples.json generated

View file

@ -26,6 +26,7 @@ on:
- "Tools/build/update_file.py"
- "Tools/build/verify_ensurepip_wheels.py"
- "Tools/cases_generator/**"
- "Tools/check-c-api-docs/**"
- "Tools/clinic/**"
- "Tools/jit/**"
- "Tools/peg_generator/**"
@ -58,6 +59,7 @@ jobs:
"Lib/tomllib",
"Tools/build",
"Tools/cases_generator",
"Tools/check-c-api-docs",
"Tools/clinic",
"Tools/jit",
"Tools/peg_generator",

View file

@ -79,19 +79,17 @@ jobs:
with:
python-version: '3.11'
- name: Native Windows (debug)
- name: Native Windows MSVC (release)
if: runner.os == 'Windows' && matrix.architecture != 'ARM64'
shell: cmd
run: |
choco install llvm --allow-downgrade --no-progress --version ${{ matrix.llvm }}.1.0
set PlatformToolset=clangcl
set LLVMToolsVersion=${{ matrix.llvm }}.1.0
set LLVMInstallDir=C:\Program Files\LLVM
call ./PCbuild/build.bat --tail-call-interp -d -p ${{ matrix.architecture }}
call ./PCbuild/rt.bat -d -p ${{ matrix.architecture }} -q --multiprocess 0 --timeout 4500 --verbose2 --verbose3
choco install visualstudio2026buildtools --no-progress -y --force --params "--add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 --locale en-US --passive"
$env:PATH = "C:\Program Files (x86)\Microsoft Visual Studio\18\BuildTools\MSBuild\Current\bin;$env:PATH"
./PCbuild/build.bat --tail-call-interp -c Release -p ${{ matrix.architecture }} "/p:PlatformToolset=v145"
./PCbuild/rt.bat -p ${{ matrix.architecture }} -q --multiprocess 0 --timeout 4500 --verbose2 --verbose3
# No tests (yet):
- name: Emulated Windows (release)
- name: Emulated Windows Clang (release)
if: runner.os == 'Windows' && matrix.architecture == 'ARM64'
shell: cmd
run: |

View file

@ -40,15 +40,15 @@ repos:
files: ^Apple
- id: ruff-format
name: Run Ruff (format) on Doc/
args: [--check]
args: [--exit-non-zero-on-fix]
files: ^Doc/
- id: ruff-format
name: Run Ruff (format) on Tools/build/check_warnings.py
args: [--check, --config=Tools/build/.ruff.toml]
args: [--exit-non-zero-on-fix, --config=Tools/build/.ruff.toml]
files: ^Tools/build/check_warnings.py
- id: ruff-format
name: Run Ruff (format) on Tools/wasm/
args: [--check, --config=Tools/wasm/.ruff.toml]
args: [--exit-non-zero-on-fix, --config=Tools/wasm/.ruff.toml]
files: ^Tools/wasm/
- repo: https://github.com/psf/black-pre-commit-mirror

View file

@ -140,7 +140,8 @@ doctest:
pydoc-topics: BUILDER = pydoc-topics
pydoc-topics: build
@echo "Building finished; now run this:" \
"cp build/pydoc-topics/topics.py ../Lib/pydoc_data/topics.py"
"cp build/pydoc-topics/topics.py ../Lib/pydoc_data/topics.py" \
"&& cp build/pydoc-topics/module_docs.py ../Lib/pydoc_data/module_docs.py"
.PHONY: gettext
gettext: BUILDER = gettext

View file

@ -107,6 +107,46 @@ header files properly declare the entry points to be ``extern "C"``. As a result
there is no need to do anything special to use the API from C++.
.. _capi-system-includes:
System includes
---------------
:file:`Python.h` includes several standard header files.
C extensions should include the standard headers that they use,
and should not rely on these implicit includes.
The implicit includes are:
* ``<assert.h>``
* ``<intrin.h>`` (on Windows)
* ``<inttypes.h>``
* ``<limits.h>``
* ``<math.h>``
* ``<stdarg.h>``
* ``<wchar.h>``
* ``<sys/types.h>`` (if present)
The following are included for backwards compatibility, unless using
:ref:`Limited API <limited-c-api>` 3.13 or newer:
* ``<ctype.h>``
* ``<unistd.h>`` (on POSIX)
The following are included for backwards compatibility, unless using
:ref:`Limited API <limited-c-api>` 3.11 or newer:
* ``<errno.h>``
* ``<stdio.h>``
* ``<stdlib.h>``
* ``<string.h>``
.. note::
Since Python may define some pre-processor definitions which affect the standard
headers on some systems, you *must* include :file:`Python.h` before any standard
headers are included.
Useful macros
=============

View file

@ -1,9 +1,9 @@
Pending removal in Python 3.20
------------------------------
* The ``__version__`` attribute has been deprecated in these standard library
modules and will be removed in Python 3.20.
Use :py:data:`sys.version_info` instead.
* The ``__version__``, ``version`` and ``VERSION`` attributes have been
deprecated in these standard library modules and will be removed in
Python 3.20. Use :py:data:`sys.version_info` instead.
- :mod:`argparse`
- :mod:`csv`
@ -24,6 +24,9 @@ Pending removal in Python 3.20
- :mod:`tkinter.font`
- :mod:`tkinter.ttk`
- :mod:`wsgiref.simple_server`
- :mod:`xml.etree.ElementTree`
- :mod:`!xml.sax.expatreader`
- :mod:`xml.sax.handler`
- :mod:`zlib`
(Contributed by Hugo van Kemenade and Stan Ulbrych in :gh:`76007`.)

View file

@ -3,154 +3,20 @@
.. _extending-intro:
******************************
Extending Python with C or C++
******************************
********************************
Using the C API: Assorted topics
********************************
It is quite easy to add new built-in modules to Python, if you know how to
program in C. Such :dfn:`extension modules` can do two things that can't be
done directly in Python: they can implement new built-in object types, and they
can call C library functions and system calls.
To support extensions, the Python API (Application Programmers Interface)
defines a set of functions, macros and variables that provide access to most
aspects of the Python run-time system. The Python API is incorporated in a C
source file by including the header ``"Python.h"``.
The compilation of an extension module depends on its intended use as well as on
your system setup; details are given in later chapters.
.. note::
The C extension interface is specific to CPython, and extension modules do
not work on other Python implementations. In many cases, it is possible to
avoid writing C extensions and preserve portability to other implementations.
For example, if your use case is calling C library functions or system calls,
you should consider using the :mod:`ctypes` module or the `cffi
<https://cffi.readthedocs.io/>`_ library rather than writing
custom C code.
These modules let you write Python code to interface with C code and are more
portable between implementations of Python than writing and compiling a C
extension module.
.. _extending-simpleexample:
A Simple Example
================
Let's create an extension module called ``spam`` (the favorite food of Monty
Python fans...) and let's say we want to create a Python interface to the C
library function :c:func:`system` [#]_. This function takes a null-terminated
character string as argument and returns an integer. We want this function to
be callable from Python as follows:
.. code-block:: pycon
>>> import spam
>>> status = spam.system("ls -l")
Begin by creating a file :file:`spammodule.c`. (Historically, if a module is
called ``spam``, the C file containing its implementation is called
:file:`spammodule.c`; if the module name is very long, like ``spammify``, the
module name can be just :file:`spammify.c`.)
The first two lines of our file can be::
#define PY_SSIZE_T_CLEAN
#include <Python.h>
which pulls in the Python API (you can add a comment describing the purpose of
the module and a copyright notice if you like).
.. note::
Since Python may define some pre-processor definitions which affect the standard
headers on some systems, you *must* include :file:`Python.h` before any standard
headers are included.
``#define PY_SSIZE_T_CLEAN`` was used to indicate that ``Py_ssize_t`` should be
used in some APIs instead of ``int``.
It is not necessary since Python 3.13, but we keep it here for backward compatibility.
See :ref:`arg-parsing-string-and-buffers` for a description of this macro.
All user-visible symbols defined by :file:`Python.h` have a prefix of ``Py`` or
``PY``, except those defined in standard header files.
.. tip::
For backward compatibility, :file:`Python.h` includes several standard header files.
C extensions should include the standard headers that they use,
and should not rely on these implicit includes.
If using the limited C API version 3.13 or newer, the implicit includes are:
* ``<assert.h>``
* ``<intrin.h>`` (on Windows)
* ``<inttypes.h>``
* ``<limits.h>``
* ``<math.h>``
* ``<stdarg.h>``
* ``<wchar.h>``
* ``<sys/types.h>`` (if present)
If :c:macro:`Py_LIMITED_API` is not defined, or is set to version 3.12 or older,
the headers below are also included:
* ``<ctype.h>``
* ``<unistd.h>`` (on POSIX)
If :c:macro:`Py_LIMITED_API` is not defined, or is set to version 3.10 or older,
the headers below are also included:
* ``<errno.h>``
* ``<stdio.h>``
* ``<stdlib.h>``
* ``<string.h>``
The next thing we add to our module file is the C function that will be called
when the Python expression ``spam.system(string)`` is evaluated (we'll see
shortly how it ends up being called)::
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
const char *command;
int sts;
if (!PyArg_ParseTuple(args, "s", &command))
return NULL;
sts = system(command);
return PyLong_FromLong(sts);
}
There is a straightforward translation from the argument list in Python (for
example, the single expression ``"ls -l"``) to the arguments passed to the C
function. The C function always has two arguments, conventionally named *self*
and *args*.
The *self* argument points to the module object for module-level functions;
for a method it would point to the object instance.
The *args* argument will be a pointer to a Python tuple object containing the
arguments. Each item of the tuple corresponds to an argument in the call's
argument list. The arguments are Python objects --- in order to do anything
with them in our C function we have to convert them to C values. The function
:c:func:`PyArg_ParseTuple` in the Python API checks the argument types and
converts them to C values. It uses a template string to determine the required
types of the arguments as well as the types of the C variables into which to
store the converted values. More about this later.
:c:func:`PyArg_ParseTuple` returns true (nonzero) if all arguments have the right
type and its components have been stored in the variables whose addresses are
passed. It returns false (zero) if an invalid argument list was passed. In the
latter case it also raises an appropriate exception so the calling function can
return ``NULL`` immediately (as we saw in the example).
The :ref:`tutorial <first-extension-module>` walked you through
creating a C API extension module, but left many areas unexplained.
This document looks at several concepts that you'll need to learn
in order to write more complex extensions.
.. _extending-errors:
Intermezzo: Errors and Exceptions
=================================
Errors and Exceptions
=====================
An important convention throughout the Python interpreter is the following: when
a function fails, it should set an exception condition and return an error value
@ -321,194 +187,14 @@ call to :c:func:`PyErr_SetString` as shown below::
}
.. _backtoexample:
Back to the Example
===================
Going back to our example function, you should now be able to understand this
statement::
if (!PyArg_ParseTuple(args, "s", &command))
return NULL;
It returns ``NULL`` (the error indicator for functions returning object pointers)
if an error is detected in the argument list, relying on the exception set by
:c:func:`PyArg_ParseTuple`. Otherwise the string value of the argument has been
copied to the local variable :c:data:`!command`. This is a pointer assignment and
you are not supposed to modify the string to which it points (so in Standard C,
the variable :c:data:`!command` should properly be declared as ``const char
*command``).
The next statement is a call to the Unix function :c:func:`system`, passing it
the string we just got from :c:func:`PyArg_ParseTuple`::
sts = system(command);
Our :func:`!spam.system` function must return the value of :c:data:`!sts` as a
Python object. This is done using the function :c:func:`PyLong_FromLong`. ::
return PyLong_FromLong(sts);
In this case, it will return an integer object. (Yes, even integers are objects
on the heap in Python!)
If you have a C function that returns no useful argument (a function returning
:c:expr:`void`), the corresponding Python function must return ``None``. You
need this idiom to do so (which is implemented by the :c:macro:`Py_RETURN_NONE`
macro)::
Py_INCREF(Py_None);
return Py_None;
:c:data:`Py_None` is the C name for the special Python object ``None``. It is a
genuine Python object rather than a ``NULL`` pointer, which means "error" in most
contexts, as we have seen.
.. _methodtable:
The Module's Method Table and Initialization Function
=====================================================
I promised to show how :c:func:`!spam_system` is called from Python programs.
First, we need to list its name and address in a "method table"::
static PyMethodDef spam_methods[] = {
...
{"system", spam_system, METH_VARARGS,
"Execute a shell command."},
...
{NULL, NULL, 0, NULL} /* Sentinel */
};
Note the third entry (``METH_VARARGS``). This is a flag telling the interpreter
the calling convention to be used for the C function. It should normally always
be ``METH_VARARGS`` or ``METH_VARARGS | METH_KEYWORDS``; a value of ``0`` means
that an obsolete variant of :c:func:`PyArg_ParseTuple` is used.
When using only ``METH_VARARGS``, the function should expect the Python-level
parameters to be passed in as a tuple acceptable for parsing via
:c:func:`PyArg_ParseTuple`; more information on this function is provided below.
The :c:macro:`METH_KEYWORDS` bit may be set in the third field if keyword
arguments should be passed to the function. In this case, the C function should
accept a third ``PyObject *`` parameter which will be a dictionary of keywords.
Use :c:func:`PyArg_ParseTupleAndKeywords` to parse the arguments to such a
function.
The method table must be referenced in the module definition structure::
static struct PyModuleDef spam_module = {
...
.m_methods = spam_methods,
...
};
This structure, in turn, must be passed to the interpreter in the module's
initialization function. The initialization function must be named
:c:func:`!PyInit_name`, where *name* is the name of the module, and should be the
only non-\ ``static`` item defined in the module file::
PyMODINIT_FUNC
PyInit_spam(void)
{
return PyModuleDef_Init(&spam_module);
}
Note that :c:macro:`PyMODINIT_FUNC` declares the function as ``PyObject *`` return type,
declares any special linkage declarations required by the platform, and for C++
declares the function as ``extern "C"``.
:c:func:`!PyInit_spam` is called when each interpreter imports its module
:mod:`!spam` for the first time. (See below for comments about embedding Python.)
A pointer to the module definition must be returned via :c:func:`PyModuleDef_Init`,
so that the import machinery can create the module and store it in ``sys.modules``.
When embedding Python, the :c:func:`!PyInit_spam` function is not called
automatically unless there's an entry in the :c:data:`PyImport_Inittab` table.
To add the module to the initialization table, use :c:func:`PyImport_AppendInittab`,
optionally followed by an import of the module::
#define PY_SSIZE_T_CLEAN
#include <Python.h>
int
main(int argc, char *argv[])
{
PyStatus status;
PyConfig config;
PyConfig_InitPythonConfig(&config);
/* Add a built-in module, before Py_Initialize */
if (PyImport_AppendInittab("spam", PyInit_spam) == -1) {
fprintf(stderr, "Error: could not extend in-built modules table\n");
exit(1);
}
/* Pass argv[0] to the Python interpreter */
status = PyConfig_SetBytesString(&config, &config.program_name, argv[0]);
if (PyStatus_Exception(status)) {
goto exception;
}
/* Initialize the Python interpreter. Required.
If this step fails, it will be a fatal error. */
status = Py_InitializeFromConfig(&config);
if (PyStatus_Exception(status)) {
goto exception;
}
PyConfig_Clear(&config);
/* Optionally import the module; alternatively,
import can be deferred until the embedded script
imports it. */
PyObject *pmodule = PyImport_ImportModule("spam");
if (!pmodule) {
PyErr_Print();
fprintf(stderr, "Error: could not import module 'spam'\n");
}
// ... use Python C API here ...
return 0;
exception:
PyConfig_Clear(&config);
Py_ExitStatusException(status);
}
.. note::
If you declare a global variable or a local static one, the module may
experience unintended side-effects on re-initialisation, for example when
removing entries from ``sys.modules`` or importing compiled modules into
multiple interpreters within a process
(or following a :c:func:`fork` without an intervening :c:func:`exec`).
If module state is not yet fully :ref:`isolated <isolating-extensions-howto>`,
authors should consider marking the module as having no support for subinterpreters
(via :c:macro:`Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED`).
A more substantial example module is included in the Python source distribution
as :file:`Modules/xxlimited.c`. This file may be used as a template or simply
read as an example.
.. _compilation:
Compilation and Linkage
=======================
Embedding an extension
======================
There are two more things to do before you can use your new extension: compiling
and linking it with the Python system. If you use dynamic loading, the details
may depend on the style of dynamic loading your system uses; see the chapters
about building extension modules (chapter :ref:`building`) and additional
information that pertains only to building on Windows (chapter
:ref:`building-on-windows`) for more information about this.
If you can't use dynamic loading, or if you want to make your module a permanent
If you want to make your module a permanent
part of the Python interpreter, you will have to change the configuration setup
and rebuild the interpreter. Luckily, this is very simple on Unix: just place
and rebuild the interpreter. On Unix, place
your file (:file:`spammodule.c` for example) in the :file:`Modules/` directory
of an unpacked source distribution, add a line to the file
:file:`Modules/Setup.local` describing your file:
@ -536,7 +222,7 @@ on the line in the configuration file as well, for instance:
Calling Python Functions from C
===============================
So far we have concentrated on making C functions callable from Python. The
The tutorial concentrated on making C functions callable from Python. The
reverse is also useful: calling Python functions from C. This is especially the
case for libraries that support so-called "callback" functions. If a C
interface makes use of callbacks, the equivalent Python often needs to provide a
@ -581,7 +267,7 @@ be part of a module definition::
}
This function must be registered with the interpreter using the
:c:macro:`METH_VARARGS` flag; this is described in section :ref:`methodtable`. The
:c:macro:`METH_VARARGS` flag in :c:type:`PyMethodDef.ml_flags`. The
:c:func:`PyArg_ParseTuple` function and its arguments are documented in section
:ref:`parsetuple`.
@ -676,14 +362,21 @@ the above example, we use :c:func:`Py_BuildValue` to construct the dictionary. :
Py_DECREF(result);
.. index:: single: PyArg_ParseTuple (C function)
.. _parsetuple:
Extracting Parameters in Extension Functions
============================================
.. index:: single: PyArg_ParseTuple (C function)
The :ref:`tutorial <first-extension-module>` uses a ":c:data:`METH_O`"
function, which is limited to a single Python argument.
If you want more, you can use :c:data:`METH_VARARGS` instead.
With this flag, the C function will receive a :py:class:`tuple` of arguments
instead of a single object.
The :c:func:`PyArg_ParseTuple` function is declared as follows::
For unpacking the tuple, CPython provides the :c:func:`PyArg_ParseTuple`
function, declared as follows::
int PyArg_ParseTuple(PyObject *arg, const char *format, ...);
@ -693,6 +386,19 @@ whose syntax is explained in :ref:`arg-parsing` in the Python/C API Reference
Manual. The remaining arguments must be addresses of variables whose type is
determined by the format string.
For example, to receive a single Python :py:class:`str` object and turn it
into a C buffer, you would use ``"s"`` as the format string::
const char *command;
if (!PyArg_ParseTuple(args, "s", &command)) {
return NULL;
}
If an error is detected in the argument list, :c:func:`!PyArg_ParseTuple`
returns ``NULL`` (the error indicator for functions returning object pointers);
your function may return ``NULL``, relying on the exception set by
:c:func:`PyArg_ParseTuple`.
Note that while :c:func:`PyArg_ParseTuple` checks that the Python arguments have
the required types, it cannot check the validity of the addresses of C variables
passed to the call: if you make mistakes there, your code will probably crash or
@ -703,7 +409,6 @@ Note that any Python object references which are provided to the caller are
Some example calls::
#define PY_SSIZE_T_CLEAN
#include <Python.h>
::
@ -773,6 +478,17 @@ Some example calls::
Keyword Parameters for Extension Functions
==========================================
If you also want your function to accept
:term:`keyword arguments <keyword argument>`, use the :c:data:`METH_KEYWORDS`
flag in combination with :c:data:`METH_VARARGS`.
(:c:data:`!METH_KEYWORDS` can also be used with other flags; see its
documentation for the allowed combinations.)
In this case, the C function should accept a third ``PyObject *`` parameter
which will be a dictionary of keywords.
Use :c:func:`PyArg_ParseTupleAndKeywords` to parse the arguments to such a
function.
.. index:: single: PyArg_ParseTupleAndKeywords (C function)
The :c:func:`PyArg_ParseTupleAndKeywords` function is declared as follows::
@ -833,19 +549,6 @@ Philbrick (philbrick@hks.com)::
{NULL, NULL, 0, NULL} /* sentinel */
};
static struct PyModuleDef keywdarg_module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "keywdarg",
.m_size = 0,
.m_methods = keywdarg_methods,
};
PyMODINIT_FUNC
PyInit_keywdarg(void)
{
return PyModuleDef_Init(&keywdarg_module);
}
.. _buildvalue:
@ -986,11 +689,11 @@ needed. Ownership of a reference can be transferred. There are three ways to
dispose of an owned reference: pass it on, store it, or call :c:func:`Py_DECREF`.
Forgetting to dispose of an owned reference creates a memory leak.
It is also possible to :dfn:`borrow` [#]_ a reference to an object. The
It is also possible to :dfn:`borrow` [#borrow]_ a reference to an object. The
borrower of a reference should not call :c:func:`Py_DECREF`. The borrower must
not hold on to the object longer than the owner from which it was borrowed.
Using a borrowed reference after the owner has disposed of it risks using freed
memory and should be avoided completely [#]_.
memory and should be avoided completely [#dont-check-refcount]_.
The advantage of borrowing over owning a reference is that you don't need to
take care of disposing of the reference on all possible paths through the code
@ -1169,7 +872,7 @@ checking.
The C function calling mechanism guarantees that the argument list passed to C
functions (``args`` in the examples) is never ``NULL`` --- in fact it guarantees
that it is always a tuple [#]_.
that it is always a tuple [#old-calling-convention]_.
It is a severe error to ever let a ``NULL`` pointer "escape" to the Python user.
@ -1226,8 +929,8 @@ the module whose functions one wishes to call might not have been loaded yet!
Portability therefore requires not to make any assumptions about symbol
visibility. This means that all symbols in extension modules should be declared
``static``, except for the module's initialization function, in order to
avoid name clashes with other extension modules (as discussed in section
:ref:`methodtable`). And it means that symbols that *should* be accessible from
avoid name clashes with other extension modules. And it means that symbols
that *should* be accessible from
other extension modules must be exported in a different way.
Python provides a special mechanism to pass C-level information (pointers) from
@ -1269,8 +972,9 @@ file corresponding to the module provides a macro that takes care of importing
the module and retrieving its C API pointers; client modules only have to call
this macro before accessing the C API.
The exporting module is a modification of the :mod:`!spam` module from section
:ref:`extending-simpleexample`. The function :func:`!spam.system` does not call
The exporting module is a modification of the :mod:`!spam` module from the
:ref:`tutorial <first-extension-module>`.
The function :func:`!spam.system` does not call
the C library function :c:func:`system` directly, but a function
:c:func:`!PySpam_System`, which would of course do something more complicated in
reality (such as adding "spam" to every command). This function
@ -1412,15 +1116,14 @@ code distribution).
.. rubric:: Footnotes
.. [#] An interface for this function already exists in the standard module :mod:`os`
--- it was chosen as a simple and straightforward example.
.. [#borrow] The metaphor of "borrowing" a reference is not completely correct:
the owner still has a copy of the reference.
.. [#] The metaphor of "borrowing" a reference is not completely correct: the owner
still has a copy of the reference.
.. [#] Checking that the reference count is at least 1 **does not work** --- the
.. [#dont-check-refcount] Checking that the reference count is at least 1
**does not work** --- the
reference count itself could be in freed memory and may thus be reused for
another object!
.. [#] These guarantees don't hold when you use the "old" style calling convention ---
.. [#old-calling-convention] These guarantees don't hold when you use the
"old" style calling convention ---
this is still found in much existing code.

View file

@ -0,0 +1,667 @@
.. highlight:: c
.. _extending-simpleexample:
.. _first-extension-module:
*********************************
Your first C API extension module
*********************************
This tutorial will take you through creating a simple
Python extension module written in C or C++.
We will use the low-level Python C API directly.
For easier ways to create extension modules, see
the :ref:`recommended third party tools <c-api-tools>`.
The tutorial assumes basic knowledge about Python: you should be able to
define functions in Python code before starting to write them in C.
See :ref:`tutorial-index` for an introduction to Python itself.
The tutorial should be approachable for anyone who can write a basic C library.
While we will mention several concepts that a C beginner would not be expected
to know, like ``static`` functions or linkage declarations, understanding these
is not necessary for success.
We will focus on giving you a "feel" of what Python's C API is like.
It will not teach you important concepts, like error handling
and reference counting, which are covered in later chapters.
We will assume that you use a Unix-like system (including macOS and
Linux), or Windows.
On other systems, you might need to adjust some details -- for example,
a system command name.
You need to have a suitable C compiler and Python development headers installed.
On Linux, headers are often in a package like ``python3-dev``
or ``python3-devel``.
You need to be able to install Python packages.
This tutorial uses `pip <https://pip.pypa.io/>`__ (``pip install``), but you
can substitute any tool that can build and install ``pyproject.toml``-based
projects, like `uv <https://docs.astral.sh/uv/>`_ (``uv pip install``).
Preferably, have a :ref:`virtual environment <venv-def>` activated.
.. note::
This tutorial uses APIs that were added in CPython 3.15.
To create an extension that's compatible with earlier versions of CPython,
please follow an earlier version of this documentation.
This tutorial uses C syntax added in C11 and C++20.
If your extension needs to be compatible with earlier standards,
please follow tutorials in documentation for Python 3.14 or below.
What we'll do
=============
Let's create an extension module called ``spam`` [#why-spam]_,
which will include a Python interface to the C
standard library function :c:func:`system`.
This function is defined in ``stdlib.h``.
It takes a C string as argument, runs the argument as a system
command, and returns a result value as an integer.
A manual page for :c:func:`system` might summarize it this way::
#include <stdlib.h>
int system(const char *command);
Note that like many functions in the C standard library,
this function is already exposed in Python.
In production, use :py:func:`os.system` or :py:func:`subprocess.run`
rather than the module you'll write here.
We want this function to be callable from Python as follows:
.. code-block:: pycon
>>> import spam
>>> status = spam.system("whoami")
User Name
>>> status
0
.. note::
The system command ``whoami`` prints out your username.
It's useful in tutorials like this one because it has the same name on
both Unix and Windows.
Start with the headers
======================
Begin by creating a directory for this tutorial, and switching to it
on the command line.
Then, create a file named :file:`spammodule.c` in your directory.
[#why-spammodule]_
In this file, we'll include two headers: :file:`Python.h` to pull in
all declarations of the Python C API, and :file:`stdlib.h` for the
:c:func:`system` function. [#stdlib-h]_
Add the following lines to :file:`spammodule.c`:
.. literalinclude:: ../includes/capi-extension/spammodule-01.c
:start-at: <Python.h>
:end-at: <stdlib.h>
Be sure to put :file:`stdlib.h`, and any other standard library includes,
*after* :file:`Python.h`.
On some systems, Python may define some pre-processor definitions
that affect the standard headers.
Running your build tool
=======================
With only the includes in place, your extension won't do anything.
Still, it's a good time to compile it and try to import it.
This will ensure that your build tool works, so that you can make
and test incremental changes as you follow the rest of the text.
CPython itself does not come with a tool to build extension modules;
it is recommended to use a third-party project for this.
In this tutorial, we'll use `meson-python`_.
(If you want to use another one, see :ref:`first-extension-other-tools`.)
.. at the time of writing, meson-python has the least overhead for a
simple extension using PyModExport.
Change this if another tool makes things easier.
``meson-python`` requires defining a "project" using two extra files.
First, add ``pyproject.toml`` with these contents:
.. code-block:: toml
[build-system]
build-backend = 'mesonpy'
requires = ['meson-python']
[project]
# Placeholder project information
# (change this before distributing the module)
name = 'sampleproject'
version = '0'
Then, create ``meson.build`` containing the following:
.. code-block:: meson
project('sampleproject', 'c')
py = import('python').find_installation(pure: false)
py.extension_module(
'spam', # name of the importable Python module
'spammodule.c', # the C source file
install: true,
)
.. note::
See `meson-python documentation <meson-python>`_ for details on
configuration.
Now, build install the *project in the current directory* (``.``) via ``pip``:
.. code-block:: sh
python -m pip install .
.. tip::
If you don't have ``pip`` installed, run ``python -m ensurepip``,
preferably in a :ref:`virtual environment <venv-def>`.
(Or, if you prefer another tool that can build and install
``pyproject.toml``-based projects, use that.)
.. _meson-python: https://mesonbuild.com/meson-python/
.. _virtual environment: https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/#create-and-use-virtual-environments
Note that you will need to run this command again every time you change your
extension.
Unlike Python, C has an explicit compilation step.
When your extension is compiled and installed, start Python and try to
import it.
This should fail with the following exception:
.. code-block:: pycon
>>> import spam
Traceback (most recent call last):
...
ImportError: dynamic module does not define module export function (PyModExport_spam or PyInit_spam)
Module export hook
==================
The exception you got when you tried to import the module told you that Python
is looking for a "module export function", also known as a
:ref:`module export hook <extension-export-hook>`.
Let's define one.
First, add a prototype below the ``#include`` lines:
.. literalinclude:: ../includes/capi-extension/spammodule-01.c
:start-after: /// Export hook prototype
:end-before: ///
.. tip::
The prototype is not strictly necessary, but some modern compilers emit
warnings without it.
It's generally better to add the prototype than to disable the warning.
The :c:macro:`PyMODEXPORT_FUNC` macro declares the function's
return type, and adds any special linkage declarations needed
to make the function visible and usable when CPython loads it.
After the prototype, add the function itself.
For now, make it return ``NULL``:
.. code-block:: c
PyMODEXPORT_FUNC
PyModExport_spam(void)
{
return NULL;
}
Compile and load the module again.
You should get a different error this time.
.. code-block:: pycon
>>> import spam
Traceback (most recent call last):
...
SystemError: module export hook for module 'spam' failed without setting an exception
Simply returning ``NULL`` is *not* correct behavior for an export hook,
and CPython complains about it.
That's good -- it means that CPython found the function!
Let's now make it do something useful.
The slot table
==============
Rather than ``NULL``, the export hook should return the information needed to
create a module.
Let's start with the basics: the name and docstring.
The information should be defined in a ``static`` array of
:c:type:`PyModuleDef_Slot` entries, which are essentially key-value pairs.
Define this array just before your export hook:
.. code-block:: c
static PyModuleDef_Slot spam_slots[] = {
{Py_mod_name, "spam"},
{Py_mod_doc, "A wonderful module with an example function"},
{0, NULL}
};
For both :c:data:`Py_mod_name` and :c:data:`Py_mod_doc`, the values are C
strings -- that is, NUL-terminated, UTF-8 encoded byte arrays.
Note the zero-filled sentinel entry at the end.
If you forget it, you'll trigger undefined behavior.
The array is defined as ``static`` -- that is, not visible outside this ``.c`` file.
This will be a common theme.
CPython only needs to access the export hook; all global variables
and all other functions should generally be ``static``, so that they don't
clash with other extensions.
Return this array from your export hook instead of ``NULL``:
.. code-block:: c
:emphasize-lines: 4
PyMODEXPORT_FUNC
PyModExport_spam(void)
{
return spam_slots;
}
Now, recompile and try it out:
.. code-block:: pycon
>>> import spam
>>> print(spam)
<module 'spam' from '/home/encukou/dev/cpython/spam.so'>
You have an extension module!
Try ``help(spam)`` to see the docstring.
The next step will be adding a function.
.. _backtoexample:
Exposing a function
===================
To expose the :c:func:`system` C function directly to Python,
we'll need to write a layer of glue code to convert arguments from Python
objects to C values, and the C return value back to Python.
One of the simplest ways to write glue code is a ":c:data:`METH_O`" function,
which takes two Python objects and returns one.
All Python objects -- regardless of the Python type -- are represented in C
as pointers to the :c:type:`PyObject` structure.
Add such a function above the slots array::
static PyObject *
spam_system(PyObject *self, PyObject *arg)
{
Py_RETURN_NONE;
}
For now, we ignore the arguments, and use the :c:macro:`Py_RETURN_NONE`
macro, which expands to a ``return`` statement that properly returns
a Python :py:data:`None` object.
Recompile your extension to make sure you don't have syntax errors.
We haven't yet added ``spam_system`` to the module, so you might get a
warning that ``spam_system`` is unused.
.. _methodtable:
Method definitions
------------------
To expose the C function to Python, you will need to provide several pieces of
information in a structure called
:c:type:`PyMethodDef` [#why-pymethoddef]_:
* ``ml_name``: the name of the Python function;
* ``ml_doc``: a docstring;
* ``ml_meth``: the C function to be called; and
* ``ml_flags``: a set of flags describing details like how Python arguments are
passed to the C function.
We'll use :c:data:`METH_O` here -- the flag that matches our
``spam_system`` function's signature.
Because modules typically create several functions, these definitions
need to be collected in an array, with a zero-filled sentinel at the end.
Add this array just below the ``spam_system`` function:
.. literalinclude:: ../includes/capi-extension/spammodule-01.c
:start-after: /// Module method table
:end-before: ///
As with module slots, a zero-filled sentinel marks the end of the array.
Next, we'll add the method to the module.
Add a :c:data:`Py_mod_methods` slot to your :c:type:`PyMethodDef` array:
.. literalinclude:: ../includes/capi-extension/spammodule-01.c
:start-after: /// Module slot table
:end-before: ///
:emphasize-lines: 5
Recompile your extension again, and test it.
Be sure to restart the Python interpreter, so that ``import spam`` picks
up the new version of the module.
You should now be able to call the function:
.. code-block:: pycon
>>> import spam
>>> print(spam.system)
<built-in function system>
>>> print(spam.system('whoami'))
None
Note that our ``spam.system`` does not yet run the ``whoami`` command;
it only returns ``None``.
Check that the function accepts exactly one argument, as specified by
the :c:data:`METH_O` flag:
.. code-block:: pycon
>>> print(spam.system('too', 'many', 'arguments'))
Traceback (most recent call last):
...
TypeError: spam.system() takes exactly one argument (3 given)
Returning an integer
====================
Now, let's take a look at the return value.
Instead of ``None``, we'll want ``spam.system`` to return a number -- that is,
a Python :py:type:`int` object.
Eventually this will be the exit code of a system command,
but let's start with a fixed value, say, ``3``.
The Python C API provides a function to create a Python :py:type:`int` object
from a C ``int`` value: :c:func:`PyLong_FromLong`. [#why-pylongfromlong]_
To call it, replace the ``Py_RETURN_NONE`` with the following 3 lines:
.. this could be a one-liner, but we want to show the data types here
.. code-block:: c
:emphasize-lines: 4-6
static PyObject *
spam_system(PyObject *self, PyObject *arg)
{
int status = 3;
PyObject *result = PyLong_FromLong(status);
return result;
}
Recompile, restart the Python interpreter again,
and check that the function now returns 3:
.. code-block:: pycon
>>> import spam
>>> spam.system('whoami')
3
Accepting a string
==================
Finally, let's handle the function argument.
Our C function, :c:func:`!spam_system`, takes two arguments.
The first one, ``PyObject *self``, will be set to the ``spam`` module
object.
This isn't useful in our case, so we'll ignore it.
The other one, ``PyObject *arg``, will be set to the object that the user
passed from Python.
We expect that it should be a Python string.
In order to use the information in it, we will need
to convert it to a C value -- in this case, a C string (``const char *``).
There's a slight type mismatch here: Python's :py:class:`str` objects store
Unicode text, but C strings are arrays of bytes.
So, we'll need to *encode* the data, and we'll use the UTF-8 encoding for it.
(UTF-8 might not always be correct for system commands, but it's what
:py:meth:`str.encode` uses by default,
and the C API has special support for it.)
The function to encode a Python string into a UTF-8 buffer is named
:c:func:`PyUnicode_AsUTF8` [#why-pyunicodeasutf8]_.
Call it like this:
.. code-block:: c
:emphasize-lines: 4
static PyObject *
spam_system(PyObject *self, PyObject *arg)
{
const char *command = PyUnicode_AsUTF8(arg);
int status = 3;
PyObject *result = PyLong_FromLong(status);
return result;
}
If :c:func:`PyUnicode_AsUTF8` is successful, *command* will point to the
resulting array of bytes.
This buffer is managed by the *arg* object, which means we don't need to free
it, but we must follow some rules:
* We should only use the buffer inside the ``spam_system`` function.
When ``spam_system`` returns, *arg* and the buffer it manages might be
garbage-collected.
* We must not modify it. This is why we use ``const``.
If :c:func:`PyUnicode_AsUTF8` was *not* successful, it returns a ``NULL``
pointer.
When calling *any* Python C API, we always need to handle such error cases.
The way to do this in general is left for later chapters of this documentation.
For now, be assured that we are already handling errors from
:c:func:`PyLong_FromLong` correctly.
For the :c:func:`PyUnicode_AsUTF8` call, the correct way to handle errors is
returning ``NULL`` from ``spam_system``.
Add an ``if`` block for this:
.. code-block:: c
:emphasize-lines: 5-7
static PyObject *
spam_system(PyObject *self, PyObject *arg)
{
const char *command = PyUnicode_AsUTF8(arg);
if (command == NULL) {
return NULL;
}
int status = 3;
PyObject *result = PyLong_FromLong(status);
return result;
}
That's it for the setup.
Now, all that is left is calling the C library function :c:func:`system` with
the ``char *`` buffer, and using its result instead of the ``3``:
.. code-block:: c
:emphasize-lines: 8
static PyObject *
spam_system(PyObject *self, PyObject *arg)
{
const char *command = PyUnicode_AsUTF8(arg);
if (command == NULL) {
return NULL;
}
int status = system(command);
PyObject *result = PyLong_FromLong(status);
return result;
}
Compile your module, restart Python, and test.
This time, you should see your username -- the output of the ``whoami``
system command:
.. code-block:: pycon
>>> import spam
>>> result = spam.system('whoami')
User Name
>>> result
0
You might also want to test error cases:
.. code-block:: pycon
>>> import spam
>>> result = spam.system('nonexistent-command')
sh: line 1: nonexistent-command: command not found
>>> result
32512
>>> spam.system(3)
Traceback (most recent call last):
...
TypeError: bad argument type for built-in operation
The result
==========
Congratulations!
You have written a complete Python C API extension module,
and completed this tutorial!
Here is the entire source file, for your convenience:
.. _extending-spammodule-source:
.. literalinclude:: ../includes/capi-extension/spammodule-01.c
:start-at: ///
.. _first-extension-other-tools:
Appendix: Other build tools
===========================
You should be able to follow this tutorial -- except the
*Running your build tool* section itself -- with a build tool other
than ``meson-python``.
The Python Packaging User Guide has a `list of recommended tools <https://packaging.python.org/en/latest/guides/tool-recommendations/#build-backends-for-extension-modules>`_;
be sure to choose one for the C language.
Workaround for missing PyInit function
--------------------------------------
If your build tool output complains about missing ``PyInit_spam``,
add the following function to your module for now:
.. code-block:: c
// A workaround
void *PyInit_spam(void) { return NULL; }
This is a shim for an old-style :ref:`initialization function <extension-export-hook>`,
which was required in extension modules for CPython 3.14 and below.
Current CPython does not need it, but some build tools may still assume that
all extension modules need to define it.
If you use this workaround, you will get the exception
``SystemError: initialization of spam failed without raising an exception``
instead of
``ImportError: dynamic module does not define module export function``.
Compiling directly
------------------
Using a third-party build tool is heavily recommended,
as it will take care of various details of your platform and Python
installation, of naming the resulting extension, and, later, of distributing
your work.
If you are building an extension for as *specific* system, or for yourself
only, you might instead want to run your compiler directly.
The way to do this is system-specific; be prepared for issues you will need
to solve yourself.
Linux
^^^^^
On Linux, the Python development package may include a ``python3-config``
command that prints out the required compiler flags.
If you use it, check that it corresponds to the CPython interpreter you'll use
to load the module.
Then, start with the following command:
.. code-block:: sh
gcc --shared $(python3-config --cflags --ldflags) spammodule.c -o spam.so
This should generate a ``spam.so`` file that you need to put in a directory
on :py:attr:`sys.path`.
.. rubric:: Footnotes
.. [#why-spam] ``spam`` is the favorite food of Monty Python fans...
.. [#why-spammodule] The source file name is entirely up to you,
though some tools can be picky about the ``.c`` extension.
This tutorial uses the traditional ``*module.c`` suffix.
Some people would just use :file:`spam.c` to implement a module
named ``spam``,
projects where Python isn't the primary language might use ``py_spam.c``,
and so on.
.. [#stdlib-h] Including :file:`stdlib.h` is technically not necessary,
since :file:`Python.h` includes it and
:ref:`several other standard headers <capi-system-includes>` for its own use
or for backwards compatibility.
However, it is good practice to explicitly include what you need.
.. [#why-pymethoddef] The :c:type:`!PyMethodDef` structure is also used
to create methods of classes, so there's no separate
":c:type:`!PyFunctionDef`".
.. [#why-pylongfromlong] The name :c:func:`PyLong_FromLong`
might not seem obvious.
``PyLong`` refers to a the Python :py:class:`int`, which was originally
called ``long``; the ``FromLong`` refers to the C ``long`` (or ``long int``)
type.
.. [#why-pyunicodeasutf8] Here, ``PyUnicode`` refers to the original name of
the Python :py:class:`str` class: ``unicode``.

View file

@ -5,15 +5,17 @@
##################################################
This document describes how to write modules in C or C++ to extend the Python
interpreter with new modules. Those modules can not only define new functions
but also new object types and their methods. The document also describes how
interpreter with new modules. Those modules can do what Python code does --
define functions, object types and methods -- but also interact with native
libraries or achieve better performance by avoiding the overhead of an
interpreter. The document also describes how
to embed the Python interpreter in another application, for use as an extension
language. Finally, it shows how to compile and link extension modules so that
they can be loaded dynamically (at run time) into the interpreter, if the
underlying operating system supports this feature.
This document assumes basic knowledge about Python. For an informal
introduction to the language, see :ref:`tutorial-index`. :ref:`reference-index`
This document assumes basic knowledge about C and Python. For an informal
introduction to Python, see :ref:`tutorial-index`. :ref:`reference-index`
gives a more formal definition of the language. :ref:`library-index` documents
the existing object types, functions and modules (both built-in and written in
Python) that give the language its wide application range.
@ -21,37 +23,75 @@ Python) that give the language its wide application range.
For a detailed description of the whole Python/C API, see the separate
:ref:`c-api-index`.
To support extensions, Python's C API (Application Programmers Interface)
defines a set of functions, macros and variables that provide access to most
aspects of the Python run-time system. The Python API is incorporated in a C
source file by including the header ``"Python.h"``.
.. note::
The C extension interface is specific to CPython, and extension modules do
not work on other Python implementations. In many cases, it is possible to
avoid writing C extensions and preserve portability to other implementations.
For example, if your use case is calling C library functions or system calls,
you should consider using the :mod:`ctypes` module or the `cffi
<https://cffi.readthedocs.io/>`_ library rather than writing
custom C code.
These modules let you write Python code to interface with C code and are more
portable between implementations of Python than writing and compiling a C
extension module.
.. toctree::
:hidden:
first-extension-module.rst
extending.rst
newtypes_tutorial.rst
newtypes.rst
building.rst
windows.rst
embedding.rst
Recommended third party tools
=============================
This guide only covers the basic tools for creating extensions provided
This document only covers the basic tools for creating extensions provided
as part of this version of CPython. Some :ref:`third party tools
<c-api-tools>` offer both simpler and more sophisticated approaches to creating
C and C++ extensions for Python.
While this document is aimed at extension authors, it should also be helpful to
the authors of such tools.
For example, the tutorial module can serve as a simple test case for a build
tool or sample expected output of a code generator.
Creating extensions without third party tools
=============================================
C API Tutorial
==============
This tutorial describes how to write a simple module in C or C++,
using the Python C API -- that is, using the basic tools provided
as part of this version of CPython.
#. :ref:`first-extension-module`
Guides for intermediate topics
==============================
This section of the guide covers creating C and C++ extensions without
assistance from third party tools. It is intended primarily for creators
of those tools, rather than being a recommended way to create your own
C extensions.
.. seealso::
:pep:`489` -- Multi-phase extension module initialization
.. toctree::
:maxdepth: 2
:numbered:
extending.rst
newtypes_tutorial.rst
newtypes.rst
building.rst
windows.rst
* :ref:`extending-intro`
* :ref:`defining-new-types`
* :ref:`new-types-topics`
* :ref:`building`
* :ref:`building-on-windows`
Embedding the CPython runtime in a larger application
=====================================================
@ -61,8 +101,4 @@ interpreter as the main application, it is desirable to instead embed
the CPython runtime inside a larger application. This section covers
some of the details involved in doing that successfully.
.. toctree::
:maxdepth: 2
:numbered:
embedding.rst
* :ref:`embedding`

View file

@ -134,6 +134,14 @@ Glossary
iterator's :meth:`~object.__anext__` method until it raises a
:exc:`StopAsyncIteration` exception. Introduced by :pep:`492`.
atomic operation
An operation that appears to execute as a single, indivisible step: no
other thread can observe it half-done, and its effects become visible all
at once. Python does not guarantee that high-level statements are atomic
(for example, ``x += 1`` performs multiple bytecode operations and is not
atomic). Atomicity is only guaranteed where explicitly documented. See
also :term:`race condition` and :term:`data race`.
attached thread state
A :term:`thread state` that is active for the current OS thread.
@ -289,6 +297,22 @@ Glossary
advanced mathematical feature. If you're not aware of a need for them,
it's almost certain you can safely ignore them.
concurrency
The ability of a computer program to perform multiple tasks at the same
time. Python provides libraries for writing programs that make use of
different forms of concurrency. :mod:`asyncio` is a library for dealing
with asynchronous tasks and coroutines. :mod:`threading` provides
access to operating system threads and :mod:`multiprocessing` to
operating system processes. Multi-core processors can execute threads and
processes on different CPU cores at the same time (see
:term:`parallelism`).
concurrent modification
When multiple threads modify shared data at the same time. Concurrent
modification without proper synchronization can cause
:term:`race conditions <race condition>`, and might also trigger a
:term:`data race <data race>`, data corruption, or both.
context
This term has different meanings depending on where and how it is used.
Some common meanings:
@ -363,6 +387,28 @@ Glossary
the :term:`cyclic garbage collector <garbage collection>` is to identify these groups and break the reference
cycles so that the memory can be reclaimed.
data race
A situation where multiple threads access the same memory location
concurrently, at least one of the accesses is a write, and the threads
do not use any synchronization to control their access. Data races
lead to :term:`non-deterministic` behavior and can cause data corruption.
Proper use of :term:`locks <lock>` and other :term:`synchronization primitives
<synchronization primitive>` prevents data races. Note that data races
can only happen in native code, but that :term:`native code` might be
exposed in a Python API. See also :term:`race condition` and
:term:`thread-safe`.
deadlock
A situation in which two or more tasks (threads, processes, or coroutines)
wait indefinitely for each other to release resources or complete actions,
preventing any from making progress. For example, if thread A holds lock
1 and waits for lock 2, while thread B holds lock 2 and waits for lock 1,
both threads will wait indefinitely. In Python this often arises from
acquiring multiple locks in conflicting orders or from circular
join/await dependencies. Deadlocks can be avoided by always acquiring
multiple :term:`locks <lock>` in a consistent order. See also
:term:`lock` and :term:`reentrant`.
decorator
A function returning another function, usually applied as a function
transformation using the ``@wrapper`` syntax. Common examples for
@ -662,6 +708,14 @@ Glossary
requires the GIL to be held in order to use it. This refers to having an
:term:`attached thread state`.
global state
Data that is accessible throughout a program, such as module-level
variables, class variables, or C static variables in :term:`extension modules
<extension module>`. In multi-threaded programs, global state shared
between threads typically requires synchronization to avoid
:term:`race conditions <race condition>` and
:term:`data races <data race>`.
hash-based pyc
A bytecode cache file that uses the hash rather than the last-modified
time of the corresponding source file to determine its validity. See
@ -706,7 +760,9 @@ Glossary
tuples. Such an object cannot be altered. A new object has to
be created if a different value has to be stored. They play an important
role in places where a constant hash value is needed, for example as a key
in a dictionary.
in a dictionary. Immutable objects are inherently :term:`thread-safe`
because their state cannot be modified after creation, eliminating concerns
about improperly synchronized :term:`concurrent modification`.
import path
A list of locations (or :term:`path entries <path entry>`) that are
@ -796,8 +852,9 @@ Glossary
CPython does not consistently apply the requirement that an iterator
define :meth:`~iterator.__iter__`.
And also please note that the free-threading CPython does not guarantee
the thread-safety of iterator operations.
And also please note that :term:`free-threaded <free threading>`
CPython does not guarantee :term:`thread-safe` behavior of iterator
operations.
key function
@ -835,10 +892,11 @@ Glossary
:keyword:`if` statements.
In a multi-threaded environment, the LBYL approach can risk introducing a
race condition between "the looking" and "the leaping". For example, the
code, ``if key in mapping: return mapping[key]`` can fail if another
:term:`race condition` between "the looking" and "the leaping". For example,
the code, ``if key in mapping: return mapping[key]`` can fail if another
thread removes *key* from *mapping* after the test, but before the lookup.
This issue can be solved with locks or by using the EAFP approach.
This issue can be solved with :term:`locks <lock>` or by using the
:term:`EAFP` approach. See also :term:`thread-safe`.
lexical analyzer
@ -857,6 +915,19 @@ Glossary
clause is optional. If omitted, all elements in ``range(256)`` are
processed.
lock
A :term:`synchronization primitive` that allows only one thread at a
time to access a shared resource. A thread must acquire a lock before
accessing the protected resource and release it afterward. If a thread
attempts to acquire a lock that is already held by another thread, it
will block until the lock becomes available. Python's :mod:`threading`
module provides :class:`~threading.Lock` (a basic lock) and
:class:`~threading.RLock` (a :term:`reentrant` lock). Locks are used
to prevent :term:`race conditions <race condition>` and ensure
:term:`thread-safe` access to shared data. Alternative design patterns
to locks exist such as queues, producer/consumer patterns, and
thread-local state. See also :term:`deadlock`, and :term:`reentrant`.
loader
An object that loads a module.
It must define the :meth:`!exec_module` and :meth:`!create_module` methods
@ -942,8 +1013,11 @@ Glossary
See :term:`method resolution order`.
mutable
Mutable objects can change their value but keep their :func:`id`. See
also :term:`immutable`.
An :term:`object` with state that is allowed to change during the course
of the program. In multi-threaded programs, mutable objects that are
shared between threads require careful synchronization to avoid
:term:`race conditions <race condition>`. See also :term:`immutable`,
:term:`thread-safe`, and :term:`concurrent modification`.
named tuple
The term "named tuple" applies to any type or class that inherits from
@ -995,6 +1069,13 @@ Glossary
See also :term:`module`.
native code
Code that is compiled to machine instructions and runs directly on the
processor, as opposed to code that is interpreted or runs in a virtual
machine. In the context of Python, native code typically refers to
C, C++, Rust or Fortran code in :term:`extension modules <extension module>`
that can be called from Python. See also :term:`extension module`.
nested scope
The ability to refer to a variable in an enclosing definition. For
instance, a function defined inside another function can refer to
@ -1011,6 +1092,15 @@ Glossary
properties, :meth:`~object.__getattribute__`, class methods, and static
methods.
non-deterministic
Behavior where the outcome of a program can vary between executions with
the same inputs. In multi-threaded programs, non-deterministic behavior
often results from :term:`race conditions <race condition>` where the
relative timing or interleaving of threads affects the result.
Proper synchronization using :term:`locks <lock>` and other
:term:`synchronization primitives <synchronization primitive>` helps
ensure deterministic behavior.
object
Any data with state (attributes or value) and defined behavior
(methods). Also the ultimate base class of any :term:`new-style
@ -1041,6 +1131,16 @@ Glossary
See also :term:`regular package` and :term:`namespace package`.
parallelism
Executing multiple operations at the same time (e.g. on multiple CPU
cores). In Python builds with the
:term:`global interpreter lock (GIL) <global interpreter lock>`, only one
thread runs Python bytecode at a time, so taking advantage of multiple
CPU cores typically involves multiple processes
(e.g. :mod:`multiprocessing`) or native extensions that release the GIL.
In :term:`free-threaded <free threading>` Python, multiple Python threads
can run Python code simultaneously on different cores.
parameter
A named entity in a :term:`function` (or method) definition that
specifies an :term:`argument` (or in some cases, arguments) that the
@ -1215,6 +1315,18 @@ Glossary
>>> email.mime.text.__name__
'email.mime.text'
race condition
A condition of a program where the its behavior
depends on the relative timing or ordering of events, particularly in
multi-threaded programs. Race conditions can lead to
:term:`non-deterministic` behavior and bugs that are difficult to
reproduce. A :term:`data race` is a specific type of race condition
involving unsynchronized access to shared memory. The :term:`LBYL`
coding style is particularly susceptible to race conditions in
multi-threaded code. Using :term:`locks <lock>` and other
:term:`synchronization primitives <synchronization primitive>`
helps prevent race conditions.
reference count
The number of references to an object. When the reference count of an
object drops to zero, it is deallocated. Some objects are
@ -1236,6 +1348,25 @@ Glossary
See also :term:`namespace package`.
reentrant
A property of a function or :term:`lock` that allows it to be called or
acquired multiple times by the same thread without causing errors or a
:term:`deadlock`.
For functions, reentrancy means the function can be safely called again
before a previous invocation has completed, which is important when
functions may be called recursively or from signal handlers. Thread-unsafe
functions may be :term:`non-deterministic` if they're called reentrantly in a
multithreaded program.
For locks, Python's :class:`threading.RLock` (reentrant lock) is
reentrant, meaning a thread that already holds the lock can acquire it
again without blocking. In contrast, :class:`threading.Lock` is not
reentrant - attempting to acquire it twice from the same thread will cause
a deadlock.
See also :term:`lock` and :term:`deadlock`.
REPL
An acronym for the "readevalprint loop", another name for the
:term:`interactive` interpreter shell.
@ -1340,6 +1471,18 @@ Glossary
See also :term:`borrowed reference`.
synchronization primitive
A basic building block for coordinating (synchronizing) the execution of
multiple threads to ensure :term:`thread-safe` access to shared resources.
Python's :mod:`threading` module provides several synchronization primitives
including :class:`~threading.Lock`, :class:`~threading.RLock`,
:class:`~threading.Semaphore`, :class:`~threading.Condition`,
:class:`~threading.Event`, and :class:`~threading.Barrier`. Additionally,
the :mod:`queue` module provides multi-producer, multi-consumer queues
that are especially useful in multithreaded programs. These
primitives help prevent :term:`race conditions <race condition>` and
coordinate thread execution. See also :term:`lock`.
t-string
t-strings
String literals prefixed with ``t`` or ``T`` are commonly called
@ -1392,6 +1535,19 @@ Glossary
See :ref:`Thread State and the Global Interpreter Lock <threads>` for more
information.
thread-safe
A module, function, or class that behaves correctly when used by multiple
threads concurrently. Thread-safe code uses appropriate
:term:`synchronization primitives <synchronization primitive>` like
:term:`locks <lock>` to protect shared mutable state, or is designed
to avoid shared mutable state entirely. In the
:term:`free-threaded <free threading>` build, built-in types like
:class:`dict`, :class:`list`, and :class:`set` use internal locking
to make many operations thread-safe, although thread safety is not
necessarily guaranteed. Code that is not thread-safe may experience
:term:`race conditions <race condition>` and :term:`data races <data race>`
when used in multi-threaded programs.
token
A small unit of source code, generated by the

View file

@ -0,0 +1,55 @@
/* This file needs to be kept in sync with the tutorial
* at Doc/extending/first-extension-module.rst
*/
/// Includes
#include <Python.h>
#include <stdlib.h> // for system()
/// Implementation of spam.system
static PyObject *
spam_system(PyObject *self, PyObject *arg)
{
const char *command = PyUnicode_AsUTF8(arg);
if (command == NULL) {
return NULL;
}
int status = system(command);
PyObject *result = PyLong_FromLong(status);
return result;
}
/// Module method table
static PyMethodDef spam_methods[] = {
{
.ml_name="system",
.ml_meth=spam_system,
.ml_flags=METH_O,
.ml_doc="Execute a shell command.",
},
{NULL, NULL, 0, NULL} /* Sentinel */
};
/// Module slot table
static PyModuleDef_Slot spam_slots[] = {
{Py_mod_name, "spam"},
{Py_mod_doc, "A wonderful module with an example function"},
{Py_mod_methods, spam_methods},
{0, NULL}
};
/// Export hook prototype
PyMODEXPORT_FUNC PyModExport_spam(void);
/// Module export hook
PyMODEXPORT_FUNC
PyModExport_spam(void)
{
return spam_slots;
}

View file

@ -139,12 +139,13 @@ Node classes
The :meth:`~object.__repr__` output of :class:`~ast.AST` nodes includes
the values of the node fields.
.. deprecated:: 3.8
.. deprecated-removed:: 3.8 3.14
Old classes :class:`!ast.Num`, :class:`!ast.Str`, :class:`!ast.Bytes`,
:class:`!ast.NameConstant` and :class:`!ast.Ellipsis` are still available,
but they will be removed in future Python releases. In the meantime,
instantiating them will return an instance of a different class.
Previous versions of Python provided the AST classes :class:`!ast.Num`,
:class:`!ast.Str`, :class:`!ast.Bytes`, :class:`!ast.NameConstant` and
:class:`!ast.Ellipsis`, which were deprecated in Python 3.8. These classes
were removed in Python 3.14, and their functionality has been replaced with
:class:`ast.Constant`.
.. deprecated:: 3.9
@ -2419,12 +2420,12 @@ and classes for traversing abstract syntax trees:
during traversal. For this a special visitor exists
(:class:`NodeTransformer`) that allows modifications.
.. deprecated:: 3.8
.. deprecated-removed:: 3.8 3.14
Methods :meth:`!visit_Num`, :meth:`!visit_Str`, :meth:`!visit_Bytes`,
:meth:`!visit_NameConstant` and :meth:`!visit_Ellipsis` are deprecated
now and will not be called in future Python versions. Add the
:meth:`visit_Constant` method to handle all constant nodes.
:meth:`!visit_NameConstant` and :meth:`!visit_Ellipsis` will not be called
in Python 3.14+. Add the :meth:`visit_Constant` method instead to handle
all constant nodes.
.. class:: NodeTransformer()

View file

@ -107,7 +107,7 @@ Queue
The queue can no longer grow.
Future calls to :meth:`~Queue.put` raise :exc:`QueueShutDown`.
Currently blocked callers of :meth:`~Queue.put` will be unblocked
and will raise :exc:`QueueShutDown` in the formerly blocked thread.
and will raise :exc:`QueueShutDown` in the formerly awaiting task.
If *immediate* is false (the default), the queue can be wound
down normally with :meth:`~Queue.get` calls to extract tasks

View file

@ -2651,9 +2651,42 @@ Broadly speaking, ``d.strftime(fmt)`` acts like the :mod:`time` module's
``time.strftime(fmt, d.timetuple())`` although not all objects support a
:meth:`~date.timetuple` method.
For the :meth:`.datetime.strptime` class method, the default value is
``1900-01-01T00:00:00.000``: any components not specified in the format string
will be pulled from the default value. [#]_
For the :meth:`.datetime.strptime` and :meth:`.date.strptime` class methods,
the default value is ``1900-01-01T00:00:00.000``: any components not specified
in the format string will be pulled from the default value.
.. note::
When used to parse partial dates lacking a year, :meth:`.datetime.strptime`
and :meth:`.date.strptime` will raise when encountering February 29 because
the default year of 1900 is *not* a leap year. Always add a default leap
year to partial date strings before parsing.
.. testsetup::
# doctest seems to turn the warning into an error which makes it
# show up and require matching and prevents the actual interesting
# exception from being raised.
# Manually apply the catch_warnings context manager
import warnings
catch_warnings = warnings.catch_warnings()
catch_warnings.__enter__()
warnings.simplefilter("ignore")
.. testcleanup::
catch_warnings.__exit__()
.. doctest::
>>> from datetime import datetime
>>> value = "2/29"
>>> datetime.strptime(value, "%m/%d")
Traceback (most recent call last):
...
ValueError: day 29 must be in range 1..28 for month 2 in year 1900
>>> datetime.strptime(f"1904 {value}", "%Y %m/%d")
datetime.datetime(1904, 2, 29, 0, 0)
Using ``datetime.strptime(date_string, format)`` is equivalent to::
@ -2790,7 +2823,7 @@ Notes:
include a year in the format. If the value you need to parse lacks a year,
append an explicit dummy leap year. Otherwise your code will raise an
exception when it encounters leap day because the default year used by the
parser is not a leap year. Users run into this bug every four years...
parser (1900) is not a leap year. Users run into that bug every leap year.
.. doctest::
@ -2817,5 +2850,3 @@ Notes:
.. [#] See R. H. van Gent's `guide to the mathematics of the ISO 8601 calendar
<https://web.archive.org/web/20220531051136/https://webspace.science.uu.nl/~gent0113/calendar/isocalendar.htm>`_
for a good explanation.
.. [#] Passing ``datetime.strptime('Feb 29', '%b %d')`` will fail since 1900 is not a leap year.

View file

@ -947,12 +947,13 @@ Utilities and Decorators
the member's name. Care must be taken if mixing *auto()* with manually
specified values.
*auto* instances are only resolved when at the top level of an assignment:
*auto* instances are only resolved when at the top level of an assignment, either by
itself or as part of a tuple:
* ``FIRST = auto()`` will work (auto() is replaced with ``1``);
* ``SECOND = auto(), -2`` will work (auto is replaced with ``2``, so ``2, -2`` is
used to create the ``SECOND`` enum member;
* ``THREE = [auto(), -3]`` will *not* work (``<auto instance>, -3`` is used to
* ``THREE = [auto(), -3]`` will *not* work (``[<auto instance>, -3]`` is used to
create the ``THREE`` enum member)
.. versionchanged:: 3.11.1

View file

@ -328,6 +328,17 @@ To map anonymous memory, -1 should be passed as the fileno along with the length
.. versionadded:: 3.13
.. method:: set_name(name, /)
Annotate the memory mapping with the given *name* for easier identification
in ``/proc/<pid>/maps`` if the kernel supports the feature and :option:`-X dev <-X>` is passed
to Python or if Python is built in :ref:`debug mode <debug-build>`.
The length of *name* must not exceed 67 bytes including the ``'\0'`` terminator.
.. availability:: Linux >= 5.17 (kernel built with ``CONFIG_ANON_VMA_NAME`` option)
.. versionadded:: next
.. method:: size()
Return the length of the file, which can be larger than the size of the

View file

@ -520,7 +520,8 @@ can be overridden by the local file.
To remove all commands from a breakpoint, type ``commands`` and follow it
immediately with ``end``; that is, give no commands.
With no *bpnumber* argument, ``commands`` refers to the last breakpoint set.
With no *bpnumber* argument, ``commands`` refers to the most recently set
breakpoint that still exists.
You can use breakpoint commands to start your program up again. Simply use
the :pdbcmd:`continue` command, or :pdbcmd:`step`,

View file

@ -200,6 +200,36 @@ On most systems, attaching to another process requires appropriate permissions.
See :ref:`profiling-permissions` for platform-specific requirements.
.. _replay-command:
The ``replay`` command
----------------------
The ``replay`` command converts binary profile files to other output formats::
python -m profiling.sampling replay profile.bin
python -m profiling.sampling replay --flamegraph -o profile.html profile.bin
This command is useful when you have captured profiling data in binary format
and want to analyze it later or convert it to a visualization format. Binary
profiles can be replayed multiple times to different formats without
re-profiling.
::
# Convert binary to pstats (default, prints to stdout)
python -m profiling.sampling replay profile.bin
# Convert binary to flame graph
python -m profiling.sampling replay --flamegraph -o output.html profile.bin
# Convert binary to gecko format for Firefox Profiler
python -m profiling.sampling replay --gecko -o profile.json profile.bin
# Convert binary to heatmap
python -m profiling.sampling replay --heatmap -o my_heatmap profile.bin
Profiling in production
-----------------------
@ -1041,6 +1071,59 @@ intuitive view that shows exactly where time is spent without requiring
interpretation of hierarchical visualizations.
Binary format
-------------
Binary format (:option:`--binary`) produces a compact binary file for efficient
storage of profiling data::
python -m profiling.sampling run --binary -o profile.bin script.py
python -m profiling.sampling attach --binary -o profile.bin 12345
The :option:`--compression` option controls data compression:
- ``auto`` (default): Use zstd compression if available, otherwise no
compression
- ``zstd``: Force zstd compression (requires :mod:`compression.zstd` support)
- ``none``: Disable compression
::
python -m profiling.sampling run --binary --compression=zstd -o profile.bin script.py
To analyze binary profiles, use the :ref:`replay-command` to convert them to
other formats like flame graphs or pstats output.
Record and replay workflow
==========================
The binary format combined with the replay command enables a record-and-replay
workflow that separates data capture from analysis. Rather than generating
visualizations during profiling, you capture raw data to a compact binary file
and convert it to different formats later.
This approach has three main benefits:
- Sampling runs faster because the work of building data structures for
visualization is deferred until replay.
- A single binary capture can be converted to multiple output formats
without re-profiling: pstats for a quick overview, flame graph for visual
exploration, heatmap for line-level detail.
- Binary files are compact and easy to share with colleagues who can convert
them to their preferred format.
A typical workflow::
# Capture profile in production or during tests
python -m profiling.sampling attach --binary -o profile.bin 12345
# Later, analyze with different formats
python -m profiling.sampling replay profile.bin
python -m profiling.sampling replay --flamegraph -o profile.html profile.bin
python -m profiling.sampling replay --heatmap -o heatmap profile.bin
Live mode
=========
@ -1252,6 +1335,10 @@ Global options
Attach to and profile a running process by PID.
.. option:: replay
Convert a binary profile file to another output format.
Sampling options
----------------
@ -1335,12 +1422,22 @@ Output options
Generate HTML heatmap with line-level sample counts.
.. option:: --binary
Generate high-performance binary format for later conversion with the
``replay`` command.
.. option:: --compression <type>
Compression for binary format: ``auto`` (use zstd if available, default),
``zstd``, or ``none``.
.. option:: -o <path>, --output <path>
Output file or directory path. Default behavior varies by format:
``--pstats`` writes to stdout, ``--flamegraph`` and ``--gecko`` generate
files like ``flamegraph.PID.html``, and ``--heatmap`` creates a directory
named ``heatmap_PID``.
:option:`--pstats` writes to stdout, while other formats generate a file
named ``<format>_<PID>.<ext>`` (for example, ``flamegraph_12345.html``).
:option:`--heatmap` creates a directory named ``heatmap_<PID>``.
pstats display options

View file

@ -78,7 +78,7 @@ Bookkeeping functions
instead of the system time (see the :func:`os.urandom` function for details
on availability).
If *a* is an int, it is used directly.
If *a* is an int, its absolute value is used directly.
With version 2 (the default), a :class:`str`, :class:`bytes`, or :class:`bytearray`
object gets converted to an :class:`int` and all of its bits are used.

View file

@ -46,8 +46,10 @@ Any object can be tested for truth value, for use in an :keyword:`if` or
By default, an object is considered true unless its class defines either a
:meth:`~object.__bool__` method that returns ``False`` or a
:meth:`~object.__len__` method that
returns zero, when called with the object. [1]_ Here are most of the built-in
objects considered false:
returns zero, when called with the object. [1]_ If one of the methods raises an
exception when called, the exception is propagated and the object does
not have a truth value (for example, :data:`NotImplemented`).
Here are most of the built-in objects considered false:
.. index::
single: None (Built-in object)

View file

@ -109,6 +109,7 @@ class PydocTopicsBuilder(TextBuilder):
def init(self) -> None:
super().init()
self.topics: dict[str, str] = {}
self.module_docs: dict[str, str] = {}
def get_outdated_docs(self) -> str:
# Return a string describing what an update build will build.
@ -130,6 +131,15 @@ class PydocTopicsBuilder(TextBuilder):
continue
doc_labels.setdefault(docname, []).append((topic_label, label_id))
py_domain = env.domains['py']
for module_name, module_info in py_domain.data['modules'].items():
docname = module_info[0]
if docname.startswith('library/'):
doc_file = docname.replace('library/', '')
self.module_docs[module_name] = (
f"{doc_file}#module-{module_name}"
)
for docname, label_ids in status_iterator(
doc_labels.items(),
"building topics... ",
@ -161,6 +171,22 @@ topics = {{
"""
self.outdir.joinpath("topics.py").write_text(topics, encoding="utf-8")
module_docs_repr = "\n".join(
f" '{module}': '{doc_file}',"
for module, doc_file in sorted(self.module_docs.items())
)
module_docs = f"""\
# Autogenerated by Sphinx on {asctime()}
# as part of the release process.
module_docs = {{
{module_docs_repr}
}}
"""
self.outdir.joinpath("module_docs.py").write_text(
module_docs, encoding="utf-8"
)
def _display_labels(item: tuple[str, Sequence[tuple[str, str]]]) -> str:
_docname, label_ids = item

View file

@ -73,6 +73,7 @@ Summary -- Release highlights
<whatsnew315-utf8-default>`
* :pep:`782`: :ref:`A new PyBytesWriter C API to create a Python bytes object
<whatsnew315-pep782>`
* :ref:`The JIT compiler has been significantly upgraded <whatsnew315-jit>`
* :ref:`Improved error messages <whatsnew315-improved-error-messages>`
@ -591,6 +592,11 @@ mmap
not be duplicated.
(Contributed by Serhiy Storchaka in :gh:`78502`.)
* Added the :meth:`mmap.mmap.set_name` method
to annotate an anonymous memory mapping
if Linux kernel supports :manpage:`PR_SET_VMA_ANON_NAME <PR_SET_VMA(2const)>` (Linux 5.17 or newer).
(Contributed by Donghee Na in :gh:`142419`.)
os
--
@ -843,6 +849,16 @@ zlib
Optimizations
=============
* Builds using Visual Studio 2026 (MSVC 18) may now use the new
:ref:`tail-calling interpreter <whatsnew314-tail-call-interpreter>`.
Results on an early experimental MSVC compiler reported roughly 15% speedup
on the geometric mean of pyperformance on Windows x86-64 over
the switch-case interpreter. We have
observed speedups ranging from 15% for large pure-Python libraries
to 40% for long-running small pure-Python scripts on Windows.
(Contributed by Chris Eibl, Ken Jin, and Brandt Bucher in :gh:`143068`.
Special thanks to the MSVC team including Hulon Jenkins.)
csv
---
@ -850,6 +866,91 @@ csv
(Contributed by Maurycy Pawłowski-Wieroński in :gh:`137628`.)
.. _whatsnew315-jit:
Upgraded JIT compiler
=====================
Results from the `pyperformance <https://github.com/python/pyperformance>`__
benchmark suite report
`3-4% <https://github.com/facebookexperimental/free-threading-benchmarking/blob/main/results/bm-20251214-3.15.0a2%2B-6cddf04-JIT/bm-20251214-vultr-x86_64-python-6cddf04344a1e8ca9df5-3.15.0a2%2B-6cddf04-vs-base.svg>`__
geometric mean performance improvement for the JIT over the standard CPython
interpreter built with all optimizations enabled. The speedups for JIT
builds versus no JIT builds range from roughly 20% slowdown to over
100% speedup (ignoring the ``unpack_sequence`` microbenchmark) on
x86-64 Linux and AArch64 macOS systems.
.. attention::
These results are not yet final.
The major upgrades to the JIT are:
* LLVM 21 build-time dependency
* New tracing frontend
* Basic register allocation in the JIT
* More JIT optimizations
* Better machine code generation
.. rubric:: LLVM 21 build-time dependency
The JIT compiler now uses LLVM 21 for build-time stencil generation. As
always, LLVM is only needed when building CPython with the JIT enabled;
end users running Python do not need LLVM installed. Instructions for
installing LLVM can be found in the `JIT compiler documentation
<https://github.com/python/cpython/blob/main/Tools/jit/README.md>`__
for all supported platforms.
(Contributed by Savannah Ostrowski in :gh:`140973`.)
.. rubric:: A new tracing frontend
The JIT compiler now supports significantly more bytecode operations and
control flow than in Python 3.14, enabling speedups on a wider variety of
code. For example, simple Python object creation is now understood by the
3.15 JIT compiler. Overloaded operations and generators are also partially
supported. This was made possible by an overhauled JIT tracing frontend
that records actual execution paths through code, rather than estimating
them as the previous implementation did.
(Contributed by Ken Jin in :gh:`139109`. Support for Windows added by
Mark Shannon in :gh:`141703`.)
.. rubric:: Basic register allocation in the JIT
A basic form of register allocation has been added to the JIT compiler's
optimizer. This allows the JIT compiler to avoid certain stack operations
altogether and instead operate on registers. This allows the JIT to produce
more efficient traces by avoiding reads and writes to memory.
(Contributed by Mark Shannon in :gh:`135379`.)
.. rubric:: More JIT optimizations
More `constant-propagation <https://en.wikipedia.org/wiki/Constant_folding>`__
is now performed. This means when the JIT compiler detects that certain user
code results in constants, the code can be simplified by the JIT.
(Contributed by Ken Jin and Savannah Ostrowski in :gh:`132732`.)
The JIT avoids :term:`reference count`\ s where possible. This generally
reduces the cost of most operations in Python.
(Contributed by Ken Jin, Donghee Na, Zheao Li, Savannah Ostrowski,
Noam Cohen, Tomas Roun, PuQing in :gh:`134584`.)
.. rubric:: Better machine code generation
The JIT compiler's machine code generator now produces better machine code
for x86-64 and AArch64 macOS and Linux targets. In general, users should
experience lower memory usage for generated machine code and more efficient
machine code versus the old JIT.
(Contributed by Brandt Bucher in :gh:`136528` and :gh:`136528`.
Implementation for AArch64 contributed by Mark Shannon in :gh:`139855`.
Additional optimizations for AArch64 contributed by Mark Shannon and
Diego Russo in :gh:`140683` and :gh:`142305`.)
Removed
=======
@ -1018,9 +1119,9 @@ New deprecations
* ``__version__``
* The ``__version__`` attribute has been deprecated in these standard library
modules and will be removed in Python 3.20.
Use :py:data:`sys.version_info` instead.
* The ``__version__``, ``version`` and ``VERSION`` attributes have been
deprecated in these standard library modules and will be removed in
Python 3.20. Use :py:data:`sys.version_info` instead.
- :mod:`argparse`
- :mod:`csv`
@ -1041,6 +1142,9 @@ New deprecations
- :mod:`tkinter.font`
- :mod:`tkinter.ttk`
- :mod:`wsgiref.simple_server`
- :mod:`xml.etree.ElementTree`
- :mod:`!xml.sax.expatreader`
- :mod:`xml.sax.handler`
- :mod:`zlib`
(Contributed by Hugo van Kemenade and Stan Ulbrych in :gh:`76007`.)

View file

@ -523,6 +523,9 @@ _Py_atomic_store_uintptr_release(uintptr_t *obj, uintptr_t value);
static inline void
_Py_atomic_store_ssize_release(Py_ssize_t *obj, Py_ssize_t value);
static inline void
_Py_atomic_store_int8_release(int8_t *obj, int8_t value);
static inline void
_Py_atomic_store_int_release(int *obj, int value);

View file

@ -572,6 +572,10 @@ static inline void
_Py_atomic_store_int_release(int *obj, int value)
{ __atomic_store_n(obj, value, __ATOMIC_RELEASE); }
static inline void
_Py_atomic_store_int8_release(int8_t *obj, int8_t value)
{ __atomic_store_n(obj, value, __ATOMIC_RELEASE); }
static inline void
_Py_atomic_store_ssize_release(Py_ssize_t *obj, Py_ssize_t value)
{ __atomic_store_n(obj, value, __ATOMIC_RELEASE); }

View file

@ -1066,6 +1066,19 @@ _Py_atomic_store_int_release(int *obj, int value)
#endif
}
static inline void
_Py_atomic_store_int8_release(int8_t *obj, int8_t value)
{
#if defined(_M_X64) || defined(_M_IX86)
*(int8_t volatile *)obj = value;
#elif defined(_M_ARM64)
_Py_atomic_ASSERT_ARG_TYPE(unsigned __int8);
__stlr8((unsigned __int8 volatile *)obj, (unsigned __int8)value);
#else
# error "no implementation of _Py_atomic_store_int8_release"
#endif
}
static inline void
_Py_atomic_store_ssize_release(Py_ssize_t *obj, Py_ssize_t value)
{

View file

@ -1023,6 +1023,14 @@ _Py_atomic_store_int_release(int *obj, int value)
memory_order_release);
}
static inline void
_Py_atomic_store_int8_release(int8_t *obj, int8_t value)
{
_Py_USING_STD;
atomic_store_explicit((_Atomic(int8_t)*)obj, value,
memory_order_release);
}
static inline void
_Py_atomic_store_uint_release(unsigned int *obj, unsigned int value)
{

View file

@ -123,7 +123,7 @@ _PyEval_EvalFrame(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwfl
#ifdef _Py_TIER2
#ifdef _Py_JIT
_Py_CODEUNIT *_Py_LazyJitTrampoline(
_Py_CODEUNIT *_Py_LazyJitShim(
struct _PyExecutorObject *current_executor, _PyInterpreterFrame *frame,
_PyStackRef *stack_pointer, PyThreadState *tstate
);
@ -415,6 +415,17 @@ _Py_VectorCall_StackRefSteal(
int total_args,
_PyStackRef kwnames);
PyAPI_FUNC(PyObject*)
_Py_VectorCallInstrumentation_StackRefSteal(
_PyStackRef callable,
_PyStackRef* arguments,
int total_args,
_PyStackRef kwnames,
bool call_instrumentation,
_PyInterpreterFrame* frame,
_Py_CODEUNIT* this_instr,
PyThreadState* tstate);
PyAPI_FUNC(PyObject *)
_Py_BuiltinCallFast_StackRefSteal(
_PyStackRef callable,
@ -464,6 +475,11 @@ _Py_assert_within_stack_bounds(
_PyInterpreterFrame *frame, _PyStackRef *stack_pointer,
const char *filename, int lineno);
// Like PyMapping_GetOptionalItem, but returns the PyObject* instead of taking
// it as an out parameter. This helps MSVC's escape analysis when used with
// tail calling.
PyAPI_FUNC(PyObject*) _PyMapping_GetOptionalItem2(PyObject* obj, PyObject* key, int* err);
#ifdef __cplusplus
}
#endif

View file

@ -272,8 +272,7 @@ _PyDict_SendEvent(int watcher_bits,
PyObject *value);
static inline void
_PyDict_NotifyEvent(PyInterpreterState *interp,
PyDict_WatchEvent event,
_PyDict_NotifyEvent(PyDict_WatchEvent event,
PyDictObject *mp,
PyObject *key,
PyObject *value)

View file

@ -1653,9 +1653,11 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(co_varnames));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(code));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(col_offset));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(collector));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(command));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(comment_factory));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(compile_mode));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(compression));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(config));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(consts));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(context));
@ -1718,7 +1720,9 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(event));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(eventmask));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(exc));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(exc_tb));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(exc_type));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(exc_val));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(exc_value));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(excepthook));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(exception));
@ -1974,6 +1978,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(print_file_and_line));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(priority));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(progress));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(progress_callback));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(progress_routine));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(proto));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(protocol));
@ -2014,6 +2019,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(reversed));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(rounding));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(salt));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(sample_interval_us));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(sched_priority));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(scheduler));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(script));
@ -2053,8 +2059,10 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(spam));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(src));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(src_dir_fd));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(stack_frames));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(stacklevel));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(start));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(start_time_us));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(statement));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(stats));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(status));
@ -2095,6 +2103,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(times));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(timespec));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(timestamp));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(timestamp_us));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(timetuple));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(timeunit));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(top));

View file

@ -376,9 +376,11 @@ struct _Py_global_strings {
STRUCT_FOR_ID(co_varnames)
STRUCT_FOR_ID(code)
STRUCT_FOR_ID(col_offset)
STRUCT_FOR_ID(collector)
STRUCT_FOR_ID(command)
STRUCT_FOR_ID(comment_factory)
STRUCT_FOR_ID(compile_mode)
STRUCT_FOR_ID(compression)
STRUCT_FOR_ID(config)
STRUCT_FOR_ID(consts)
STRUCT_FOR_ID(context)
@ -441,7 +443,9 @@ struct _Py_global_strings {
STRUCT_FOR_ID(event)
STRUCT_FOR_ID(eventmask)
STRUCT_FOR_ID(exc)
STRUCT_FOR_ID(exc_tb)
STRUCT_FOR_ID(exc_type)
STRUCT_FOR_ID(exc_val)
STRUCT_FOR_ID(exc_value)
STRUCT_FOR_ID(excepthook)
STRUCT_FOR_ID(exception)
@ -697,6 +701,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(print_file_and_line)
STRUCT_FOR_ID(priority)
STRUCT_FOR_ID(progress)
STRUCT_FOR_ID(progress_callback)
STRUCT_FOR_ID(progress_routine)
STRUCT_FOR_ID(proto)
STRUCT_FOR_ID(protocol)
@ -737,6 +742,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(reversed)
STRUCT_FOR_ID(rounding)
STRUCT_FOR_ID(salt)
STRUCT_FOR_ID(sample_interval_us)
STRUCT_FOR_ID(sched_priority)
STRUCT_FOR_ID(scheduler)
STRUCT_FOR_ID(script)
@ -776,8 +782,10 @@ struct _Py_global_strings {
STRUCT_FOR_ID(spam)
STRUCT_FOR_ID(src)
STRUCT_FOR_ID(src_dir_fd)
STRUCT_FOR_ID(stack_frames)
STRUCT_FOR_ID(stacklevel)
STRUCT_FOR_ID(start)
STRUCT_FOR_ID(start_time_us)
STRUCT_FOR_ID(statement)
STRUCT_FOR_ID(stats)
STRUCT_FOR_ID(status)
@ -818,6 +826,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(times)
STRUCT_FOR_ID(timespec)
STRUCT_FOR_ID(timestamp)
STRUCT_FOR_ID(timestamp_us)
STRUCT_FOR_ID(timetuple)
STRUCT_FOR_ID(timeunit)
STRUCT_FOR_ID(top)

View file

@ -947,7 +947,6 @@ struct _is {
struct _PyExecutorObject *executor_deletion_list_head;
struct _PyExecutorObject *cold_executor;
struct _PyExecutorObject *cold_dynamic_executor;
int executor_deletion_list_remaining_capacity;
size_t executor_creation_counter;
_rare_events rare_events;
PyDict_WatchCallback builtins_dict_watcher;

View file

@ -17,25 +17,27 @@ extern "C" {
#endif
#if defined(HAVE_PR_SET_VMA_ANON_NAME) && defined(__linux__)
static inline void
static inline int
_PyAnnotateMemoryMap(void *addr, size_t size, const char *name)
{
#ifndef Py_DEBUG
if (!_Py_GetConfig()->dev_mode) {
return;
return 0;
}
#endif
// The name length cannot exceed 80 (including the '\0').
assert(strlen(name) < 80);
int old_errno = errno;
prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, (unsigned long)addr, size, name);
/* Ignore errno from prctl */
/* See: https://bugzilla.redhat.com/show_bug.cgi?id=2302746 */
errno = old_errno;
int res = prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, (unsigned long)addr, size, name);
if (res < 0) {
return -1;
}
return 0;
}
#else
static inline void
static inline int
_PyAnnotateMemoryMap(void *Py_UNUSED(addr), size_t Py_UNUSED(size), const char *Py_UNUSED(name))
{
return 0;
}
#endif

View file

@ -1081,7 +1081,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = {
[BINARY_OP] = { true, INSTR_FMT_IBC0000, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
[BINARY_OP_ADD_FLOAT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG },
[BINARY_OP_ADD_INT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG },
[BINARY_OP_ADD_UNICODE] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG },
[BINARY_OP_ADD_UNICODE] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG },
[BINARY_OP_EXTEND] = { true, INSTR_FMT_IXC0000, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG },
[BINARY_OP_INPLACE_ADD_UNICODE] = { true, INSTR_FMT_IXC0000, HAS_LOCAL_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[BINARY_OP_MULTIPLY_FLOAT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG },
@ -1331,16 +1331,16 @@ _PyOpcode_macro_expansion[256] = {
[BINARY_OP] = { .nuops = 1, .uops = { { _BINARY_OP, OPARG_SIMPLE, 4 } } },
[BINARY_OP_ADD_FLOAT] = { .nuops = 5, .uops = { { _GUARD_TOS_FLOAT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_FLOAT, OPARG_SIMPLE, 0 }, { _BINARY_OP_ADD_FLOAT, OPARG_SIMPLE, 5 }, { _POP_TOP_FLOAT, OPARG_SIMPLE, 5 }, { _POP_TOP_FLOAT, OPARG_SIMPLE, 5 } } },
[BINARY_OP_ADD_INT] = { .nuops = 5, .uops = { { _GUARD_TOS_INT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_INT, OPARG_SIMPLE, 0 }, { _BINARY_OP_ADD_INT, OPARG_SIMPLE, 5 }, { _POP_TOP_INT, OPARG_SIMPLE, 5 }, { _POP_TOP_INT, OPARG_SIMPLE, 5 } } },
[BINARY_OP_ADD_UNICODE] = { .nuops = 3, .uops = { { _GUARD_TOS_UNICODE, OPARG_SIMPLE, 0 }, { _GUARD_NOS_UNICODE, OPARG_SIMPLE, 0 }, { _BINARY_OP_ADD_UNICODE, OPARG_SIMPLE, 5 } } },
[BINARY_OP_ADD_UNICODE] = { .nuops = 5, .uops = { { _GUARD_TOS_UNICODE, OPARG_SIMPLE, 0 }, { _GUARD_NOS_UNICODE, OPARG_SIMPLE, 0 }, { _BINARY_OP_ADD_UNICODE, OPARG_SIMPLE, 5 }, { _POP_TOP_UNICODE, OPARG_SIMPLE, 5 }, { _POP_TOP_UNICODE, OPARG_SIMPLE, 5 } } },
[BINARY_OP_EXTEND] = { .nuops = 2, .uops = { { _GUARD_BINARY_OP_EXTEND, 4, 1 }, { _BINARY_OP_EXTEND, 4, 1 } } },
[BINARY_OP_INPLACE_ADD_UNICODE] = { .nuops = 3, .uops = { { _GUARD_TOS_UNICODE, OPARG_SIMPLE, 0 }, { _GUARD_NOS_UNICODE, OPARG_SIMPLE, 0 }, { _BINARY_OP_INPLACE_ADD_UNICODE, OPARG_SIMPLE, 5 } } },
[BINARY_OP_MULTIPLY_FLOAT] = { .nuops = 5, .uops = { { _GUARD_TOS_FLOAT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_FLOAT, OPARG_SIMPLE, 0 }, { _BINARY_OP_MULTIPLY_FLOAT, OPARG_SIMPLE, 5 }, { _POP_TOP_FLOAT, OPARG_SIMPLE, 5 }, { _POP_TOP_FLOAT, OPARG_SIMPLE, 5 } } },
[BINARY_OP_MULTIPLY_INT] = { .nuops = 5, .uops = { { _GUARD_TOS_INT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_INT, OPARG_SIMPLE, 0 }, { _BINARY_OP_MULTIPLY_INT, OPARG_SIMPLE, 5 }, { _POP_TOP_INT, OPARG_SIMPLE, 5 }, { _POP_TOP_INT, OPARG_SIMPLE, 5 } } },
[BINARY_OP_SUBSCR_DICT] = { .nuops = 2, .uops = { { _GUARD_NOS_DICT, OPARG_SIMPLE, 0 }, { _BINARY_OP_SUBSCR_DICT, OPARG_SIMPLE, 5 } } },
[BINARY_OP_SUBSCR_GETITEM] = { .nuops = 4, .uops = { { _CHECK_PEP_523, OPARG_SIMPLE, 5 }, { _BINARY_OP_SUBSCR_CHECK_FUNC, OPARG_SIMPLE, 5 }, { _BINARY_OP_SUBSCR_INIT_CALL, OPARG_SIMPLE, 5 }, { _PUSH_FRAME, OPARG_SIMPLE, 5 } } },
[BINARY_OP_SUBSCR_LIST_INT] = { .nuops = 3, .uops = { { _GUARD_TOS_INT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_LIST, OPARG_SIMPLE, 0 }, { _BINARY_OP_SUBSCR_LIST_INT, OPARG_SIMPLE, 5 } } },
[BINARY_OP_SUBSCR_LIST_INT] = { .nuops = 5, .uops = { { _GUARD_TOS_INT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_LIST, OPARG_SIMPLE, 0 }, { _BINARY_OP_SUBSCR_LIST_INT, OPARG_SIMPLE, 5 }, { _POP_TOP_INT, OPARG_SIMPLE, 5 }, { _POP_TOP, OPARG_SIMPLE, 5 } } },
[BINARY_OP_SUBSCR_LIST_SLICE] = { .nuops = 3, .uops = { { _GUARD_TOS_SLICE, OPARG_SIMPLE, 0 }, { _GUARD_NOS_LIST, OPARG_SIMPLE, 0 }, { _BINARY_OP_SUBSCR_LIST_SLICE, OPARG_SIMPLE, 5 } } },
[BINARY_OP_SUBSCR_STR_INT] = { .nuops = 3, .uops = { { _GUARD_TOS_INT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_UNICODE, OPARG_SIMPLE, 0 }, { _BINARY_OP_SUBSCR_STR_INT, OPARG_SIMPLE, 5 } } },
[BINARY_OP_SUBSCR_STR_INT] = { .nuops = 5, .uops = { { _GUARD_TOS_INT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_UNICODE, OPARG_SIMPLE, 0 }, { _BINARY_OP_SUBSCR_STR_INT, OPARG_SIMPLE, 5 }, { _POP_TOP_INT, OPARG_SIMPLE, 5 }, { _POP_TOP, OPARG_SIMPLE, 5 } } },
[BINARY_OP_SUBSCR_TUPLE_INT] = { .nuops = 3, .uops = { { _GUARD_TOS_INT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_TUPLE, OPARG_SIMPLE, 0 }, { _BINARY_OP_SUBSCR_TUPLE_INT, OPARG_SIMPLE, 5 } } },
[BINARY_OP_SUBTRACT_FLOAT] = { .nuops = 5, .uops = { { _GUARD_TOS_FLOAT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_FLOAT, OPARG_SIMPLE, 0 }, { _BINARY_OP_SUBTRACT_FLOAT, OPARG_SIMPLE, 5 }, { _POP_TOP_FLOAT, OPARG_SIMPLE, 5 }, { _POP_TOP_FLOAT, OPARG_SIMPLE, 5 } } },
[BINARY_OP_SUBTRACT_INT] = { .nuops = 5, .uops = { { _GUARD_TOS_INT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_INT, OPARG_SIMPLE, 0 }, { _BINARY_OP_SUBTRACT_INT, OPARG_SIMPLE, 5 }, { _POP_TOP_INT, OPARG_SIMPLE, 5 }, { _POP_TOP_INT, OPARG_SIMPLE, 5 } } },
@ -1425,7 +1425,7 @@ _PyOpcode_macro_expansion[256] = {
[LOAD_ATTR] = { .nuops = 1, .uops = { { _LOAD_ATTR, OPARG_SIMPLE, 8 } } },
[LOAD_ATTR_CLASS] = { .nuops = 3, .uops = { { _CHECK_ATTR_CLASS, 2, 1 }, { _LOAD_ATTR_CLASS, 4, 5 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } },
[LOAD_ATTR_CLASS_WITH_METACLASS_CHECK] = { .nuops = 4, .uops = { { _CHECK_ATTR_CLASS, 2, 1 }, { _GUARD_TYPE_VERSION, 2, 3 }, { _LOAD_ATTR_CLASS, 4, 5 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } },
[LOAD_ATTR_INSTANCE_VALUE] = { .nuops = 4, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_MANAGED_OBJECT_HAS_VALUES, OPARG_SIMPLE, 3 }, { _LOAD_ATTR_INSTANCE_VALUE, 1, 3 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } },
[LOAD_ATTR_INSTANCE_VALUE] = { .nuops = 5, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_MANAGED_OBJECT_HAS_VALUES, OPARG_SIMPLE, 3 }, { _LOAD_ATTR_INSTANCE_VALUE, 1, 3 }, { _POP_TOP, OPARG_SIMPLE, 4 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } },
[LOAD_ATTR_METHOD_LAZY_DICT] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_ATTR_METHOD_LAZY_DICT, 1, 3 }, { _LOAD_ATTR_METHOD_LAZY_DICT, 4, 5 } } },
[LOAD_ATTR_METHOD_NO_DICT] = { .nuops = 2, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _LOAD_ATTR_METHOD_NO_DICT, 4, 5 } } },
[LOAD_ATTR_METHOD_WITH_VALUES] = { .nuops = 4, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT, OPARG_SIMPLE, 3 }, { _GUARD_KEYS_VERSION, 2, 3 }, { _LOAD_ATTR_METHOD_WITH_VALUES, 4, 5 } } },
@ -1434,7 +1434,7 @@ _PyOpcode_macro_expansion[256] = {
[LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = { .nuops = 4, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT, OPARG_SIMPLE, 3 }, { _GUARD_KEYS_VERSION, 2, 3 }, { _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES, 4, 5 } } },
[LOAD_ATTR_PROPERTY] = { .nuops = 5, .uops = { { _CHECK_PEP_523, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _LOAD_ATTR_PROPERTY_FRAME, 4, 5 }, { _SAVE_RETURN_OFFSET, OPARG_SAVE_RETURN_OFFSET, 9 }, { _PUSH_FRAME, OPARG_SIMPLE, 9 } } },
[LOAD_ATTR_SLOT] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _LOAD_ATTR_SLOT, 1, 3 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } },
[LOAD_ATTR_WITH_HINT] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _LOAD_ATTR_WITH_HINT, 1, 3 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } },
[LOAD_ATTR_WITH_HINT] = { .nuops = 4, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _LOAD_ATTR_WITH_HINT, 1, 3 }, { _POP_TOP, OPARG_SIMPLE, 4 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } },
[LOAD_BUILD_CLASS] = { .nuops = 1, .uops = { { _LOAD_BUILD_CLASS, OPARG_SIMPLE, 0 } } },
[LOAD_COMMON_CONSTANT] = { .nuops = 1, .uops = { { _LOAD_COMMON_CONSTANT, OPARG_SIMPLE, 0 } } },
[LOAD_CONST] = { .nuops = 1, .uops = { { _LOAD_CONST, OPARG_SIMPLE, 0 } } },
@ -1484,7 +1484,7 @@ _PyOpcode_macro_expansion[256] = {
[STORE_ATTR] = { .nuops = 1, .uops = { { _STORE_ATTR, OPARG_SIMPLE, 3 } } },
[STORE_ATTR_INSTANCE_VALUE] = { .nuops = 4, .uops = { { _GUARD_TYPE_VERSION_AND_LOCK, 2, 1 }, { _GUARD_DORV_NO_DICT, OPARG_SIMPLE, 3 }, { _STORE_ATTR_INSTANCE_VALUE, 1, 3 }, { _POP_TOP, OPARG_SIMPLE, 4 } } },
[STORE_ATTR_SLOT] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _STORE_ATTR_SLOT, 1, 3 }, { _POP_TOP, OPARG_SIMPLE, 4 } } },
[STORE_ATTR_WITH_HINT] = { .nuops = 2, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _STORE_ATTR_WITH_HINT, 1, 3 } } },
[STORE_ATTR_WITH_HINT] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _STORE_ATTR_WITH_HINT, 1, 3 }, { _POP_TOP, OPARG_SIMPLE, 4 } } },
[STORE_DEREF] = { .nuops = 1, .uops = { { _STORE_DEREF, OPARG_SIMPLE, 0 } } },
[STORE_FAST] = { .nuops = 1, .uops = { { _STORE_FAST, OPARG_SIMPLE, 0 } } },
[STORE_FAST_LOAD_FAST] = { .nuops = 2, .uops = { { _STORE_FAST, OPARG_TOP, 0 }, { _LOAD_FAST, OPARG_BOTTOM, 0 } } },

View file

@ -25,7 +25,6 @@ typedef struct {
uint8_t opcode;
uint8_t oparg;
uint8_t valid;
uint8_t linked;
uint8_t chain_depth; // Must be big enough for MAX_CHAIN_DEPTH - 1.
bool warm;
int32_t index; // Index of ENTER_EXECUTOR (if code isn't NULL, below).
@ -55,11 +54,6 @@ typedef struct _PyExecutorObject {
_PyExitData exits[1];
} _PyExecutorObject;
/* If pending deletion list gets large enough, then scan,
* and free any executors that aren't executing
* i.e. any that aren't a thread's current_executor. */
#define EXECUTOR_DELETE_LIST_MAX 100
// Export for '_opcode' shared extension (JIT compiler).
PyAPI_FUNC(_PyExecutorObject*) _Py_GetExecutor(PyCodeObject *code, int offset);
@ -80,7 +74,6 @@ PyAPI_FUNC(void) _Py_Executors_InvalidateCold(PyInterpreterState *interp);
#else
# define _Py_Executors_InvalidateDependency(A, B, C) ((void)0)
# define _Py_Executors_InvalidateAll(A, B) ((void)0)
# define _Py_Executors_InvalidateCold(A) ((void)0)
#endif

View file

@ -14,21 +14,6 @@ extern "C" {
#include "pycore_pyarena.h" // PyArena
_Py_DECLARE_STR(empty, "")
#if defined(Py_DEBUG) && defined(Py_GIL_DISABLED)
#define _parser_runtime_state_INIT \
{ \
.mutex = {0}, \
.dummy_name = { \
.kind = Name_kind, \
.v.Name.id = &_Py_STR(empty), \
.v.Name.ctx = Load, \
.lineno = 1, \
.col_offset = 0, \
.end_lineno = 1, \
.end_col_offset = 0, \
}, \
}
#else
#define _parser_runtime_state_INIT \
{ \
.dummy_name = { \
@ -41,7 +26,6 @@ _Py_DECLARE_STR(empty, "")
.end_col_offset = 0, \
}, \
}
#endif
extern struct _mod* _PyParser_ASTFromString(
const char *str,

View file

@ -41,6 +41,8 @@ extern "C" {
_Py_atomic_load_uint8(&value)
#define FT_ATOMIC_STORE_UINT8(value, new_value) \
_Py_atomic_store_uint8(&value, new_value)
#define FT_ATOMIC_LOAD_INT8_RELAXED(value) \
_Py_atomic_load_int8_relaxed(&value)
#define FT_ATOMIC_LOAD_UINT8_RELAXED(value) \
_Py_atomic_load_uint8_relaxed(&value)
#define FT_ATOMIC_LOAD_UINT16_RELAXED(value) \
@ -55,6 +57,10 @@ extern "C" {
_Py_atomic_store_ptr_release(&value, new_value)
#define FT_ATOMIC_STORE_UINTPTR_RELEASE(value, new_value) \
_Py_atomic_store_uintptr_release(&value, new_value)
#define FT_ATOMIC_STORE_INT8_RELAXED(value, new_value) \
_Py_atomic_store_int8_relaxed(&value, new_value)
#define FT_ATOMIC_STORE_INT8_RELEASE(value, new_value) \
_Py_atomic_store_int8_release(&value, new_value)
#define FT_ATOMIC_STORE_SSIZE_RELAXED(value, new_value) \
_Py_atomic_store_ssize_relaxed(&value, new_value)
#define FT_ATOMIC_STORE_SSIZE_RELEASE(value, new_value) \
@ -134,6 +140,7 @@ extern "C" {
#define FT_ATOMIC_LOAD_PTR_RELAXED(value) value
#define FT_ATOMIC_LOAD_UINT8(value) value
#define FT_ATOMIC_STORE_UINT8(value, new_value) value = new_value
#define FT_ATOMIC_LOAD_INT8_RELAXED(value) value
#define FT_ATOMIC_LOAD_UINT8_RELAXED(value) value
#define FT_ATOMIC_LOAD_UINT16_RELAXED(value) value
#define FT_ATOMIC_LOAD_UINT32_RELAXED(value) value
@ -141,6 +148,8 @@ extern "C" {
#define FT_ATOMIC_STORE_PTR_RELAXED(value, new_value) value = new_value
#define FT_ATOMIC_STORE_PTR_RELEASE(value, new_value) value = new_value
#define FT_ATOMIC_STORE_UINTPTR_RELEASE(value, new_value) value = new_value
#define FT_ATOMIC_STORE_INT8_RELAXED(value, new_value) value = new_value
#define FT_ATOMIC_STORE_INT8_RELEASE(value, new_value) value = new_value
#define FT_ATOMIC_STORE_SSIZE_RELAXED(value, new_value) value = new_value
#define FT_ATOMIC_STORE_SSIZE_RELEASE(value, new_value) value = new_value
#define FT_ATOMIC_STORE_UINT8_RELAXED(value, new_value) value = new_value

View file

@ -1651,9 +1651,11 @@ extern "C" {
INIT_ID(co_varnames), \
INIT_ID(code), \
INIT_ID(col_offset), \
INIT_ID(collector), \
INIT_ID(command), \
INIT_ID(comment_factory), \
INIT_ID(compile_mode), \
INIT_ID(compression), \
INIT_ID(config), \
INIT_ID(consts), \
INIT_ID(context), \
@ -1716,7 +1718,9 @@ extern "C" {
INIT_ID(event), \
INIT_ID(eventmask), \
INIT_ID(exc), \
INIT_ID(exc_tb), \
INIT_ID(exc_type), \
INIT_ID(exc_val), \
INIT_ID(exc_value), \
INIT_ID(excepthook), \
INIT_ID(exception), \
@ -1972,6 +1976,7 @@ extern "C" {
INIT_ID(print_file_and_line), \
INIT_ID(priority), \
INIT_ID(progress), \
INIT_ID(progress_callback), \
INIT_ID(progress_routine), \
INIT_ID(proto), \
INIT_ID(protocol), \
@ -2012,6 +2017,7 @@ extern "C" {
INIT_ID(reversed), \
INIT_ID(rounding), \
INIT_ID(salt), \
INIT_ID(sample_interval_us), \
INIT_ID(sched_priority), \
INIT_ID(scheduler), \
INIT_ID(script), \
@ -2051,8 +2057,10 @@ extern "C" {
INIT_ID(spam), \
INIT_ID(src), \
INIT_ID(src_dir_fd), \
INIT_ID(stack_frames), \
INIT_ID(stacklevel), \
INIT_ID(start), \
INIT_ID(start_time_us), \
INIT_ID(statement), \
INIT_ID(stats), \
INIT_ID(status), \
@ -2093,6 +2101,7 @@ extern "C" {
INIT_ID(times), \
INIT_ID(timespec), \
INIT_ID(timestamp), \
INIT_ID(timestamp_us), \
INIT_ID(timetuple), \
INIT_ID(timeunit), \
INIT_ID(top), \

View file

@ -77,9 +77,7 @@ struct _fileutils_state {
struct _parser_runtime_state {
#ifdef Py_DEBUG
long memo_statistics[_PYPEGEN_NSTATISTICS];
#ifdef Py_GIL_DISABLED
PyMutex mutex;
#endif
#else
int _not_used;
#endif

View file

@ -21,7 +21,10 @@ struct _PyTraceMalloc_Config {
} initialized;
/* Is tracemalloc tracing memory allocations?
Variable protected by the TABLES_LOCK(). */
Variable protected by the TABLES_LOCK() and stored atomically.
Atomic store is used so that it can read without locking for the
general case of checking if tracemalloc is enabled.
*/
int tracing;
/* limit of the number of frames in a traceback, 1 by default.

View file

@ -82,6 +82,13 @@ typedef struct _PyThreadStateImpl {
PyObject *asyncio_running_loop; // Strong reference
PyObject *asyncio_running_task; // Strong reference
// Distinguishes between yield and return from PyEval_EvalFrame().
// See gen_send_ex2() in Objects/genobject.c
enum {
GENERATOR_RETURN = 0,
GENERATOR_YIELD = 1,
} generator_return_kind;
/* Head of circular linked-list of all tasks which are instances of `asyncio.Task`
or subclasses of it used in `asyncio.all_tasks`.
*/

View file

@ -1284,6 +1284,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(collector);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(command);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
@ -1296,6 +1300,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(compression);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(config);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
@ -1544,10 +1552,18 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(exc_tb);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(exc_type);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(exc_val);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(exc_value);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
@ -2568,6 +2584,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(progress_callback);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(progress_routine);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
@ -2728,6 +2748,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(sample_interval_us);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(sched_priority);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
@ -2884,6 +2908,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(stack_frames);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(stacklevel);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
@ -2892,6 +2920,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(start_time_us);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(statement);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
@ -3052,6 +3084,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(timestamp_us);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(timetuple);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));

File diff suppressed because it is too large Load diff

View file

@ -111,7 +111,7 @@ const uint32_t _PyUop_Flags[MAX_UOP_ID+1] = {
[_BINARY_OP_MULTIPLY_FLOAT] = HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_PURE_FLAG,
[_BINARY_OP_ADD_FLOAT] = HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_PURE_FLAG,
[_BINARY_OP_SUBTRACT_FLOAT] = HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_PURE_FLAG,
[_BINARY_OP_ADD_UNICODE] = HAS_ERROR_FLAG | HAS_PURE_FLAG,
[_BINARY_OP_ADD_UNICODE] = HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_PURE_FLAG,
[_BINARY_OP_INPLACE_ADD_UNICODE] = HAS_LOCAL_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_GUARD_BINARY_OP_EXTEND] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
[_BINARY_OP_EXTEND] = HAS_ESCAPES_FLAG,
@ -119,7 +119,7 @@ const uint32_t _PyUop_Flags[MAX_UOP_ID+1] = {
[_STORE_SLICE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_BINARY_OP_SUBSCR_LIST_INT] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
[_BINARY_OP_SUBSCR_LIST_SLICE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_BINARY_OP_SUBSCR_STR_INT] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
[_BINARY_OP_SUBSCR_STR_INT] = HAS_DEOPT_FLAG,
[_GUARD_NOS_TUPLE] = HAS_EXIT_FLAG,
[_GUARD_TOS_TUPLE] = HAS_EXIT_FLAG,
[_BINARY_OP_SUBSCR_TUPLE_INT] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
@ -189,9 +189,9 @@ const uint32_t _PyUop_Flags[MAX_UOP_ID+1] = {
[_GUARD_TYPE_VERSION] = HAS_EXIT_FLAG,
[_GUARD_TYPE_VERSION_AND_LOCK] = HAS_EXIT_FLAG,
[_CHECK_MANAGED_OBJECT_HAS_VALUES] = HAS_DEOPT_FLAG,
[_LOAD_ATTR_INSTANCE_VALUE] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
[_LOAD_ATTR_INSTANCE_VALUE] = HAS_DEOPT_FLAG,
[_LOAD_ATTR_MODULE] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
[_LOAD_ATTR_WITH_HINT] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
[_LOAD_ATTR_WITH_HINT] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG,
[_LOAD_ATTR_SLOT] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
[_CHECK_ATTR_CLASS] = HAS_EXIT_FLAG,
[_LOAD_ATTR_CLASS] = HAS_ESCAPES_FLAG,
@ -335,6 +335,7 @@ const uint32_t _PyUop_Flags[MAX_UOP_ID+1] = {
[_POP_TWO_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG,
[_POP_CALL_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG,
[_POP_CALL_ONE_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG,
[_SHUFFLE_3_LOAD_CONST_INLINE_BORROW] = 0,
[_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG,
[_LOAD_CONST_UNDER_INLINE] = 0,
[_LOAD_CONST_UNDER_INLINE_BORROW] = 0,
@ -1050,12 +1051,12 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = {
},
},
[_BINARY_OP_ADD_UNICODE] = {
.best = { 0, 1, 2, 3 },
.best = { 0, 1, 2, 2 },
.entries = {
{ 1, 0, _BINARY_OP_ADD_UNICODE_r01 },
{ 1, 1, _BINARY_OP_ADD_UNICODE_r11 },
{ 1, 2, _BINARY_OP_ADD_UNICODE_r21 },
{ 2, 3, _BINARY_OP_ADD_UNICODE_r32 },
{ 3, 0, _BINARY_OP_ADD_UNICODE_r03 },
{ 3, 1, _BINARY_OP_ADD_UNICODE_r13 },
{ 3, 2, _BINARY_OP_ADD_UNICODE_r23 },
{ -1, -1, -1 },
},
},
[_BINARY_OP_INPLACE_ADD_UNICODE] = {
@ -1108,7 +1109,7 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = {
.entries = {
{ -1, -1, -1 },
{ -1, -1, -1 },
{ 1, 2, _BINARY_OP_SUBSCR_LIST_INT_r21 },
{ 3, 2, _BINARY_OP_SUBSCR_LIST_INT_r23 },
{ -1, -1, -1 },
},
},
@ -1126,7 +1127,7 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = {
.entries = {
{ -1, -1, -1 },
{ -1, -1, -1 },
{ 1, 2, _BINARY_OP_SUBSCR_STR_INT_r21 },
{ 3, 2, _BINARY_OP_SUBSCR_STR_INT_r23 },
{ -1, -1, -1 },
},
},
@ -1752,11 +1753,11 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = {
},
},
[_LOAD_ATTR_INSTANCE_VALUE] = {
.best = { 1, 1, 1, 1 },
.best = { 0, 1, 2, 2 },
.entries = {
{ -1, -1, -1 },
{ 1, 1, _LOAD_ATTR_INSTANCE_VALUE_r11 },
{ -1, -1, -1 },
{ 2, 0, _LOAD_ATTR_INSTANCE_VALUE_r02 },
{ 2, 1, _LOAD_ATTR_INSTANCE_VALUE_r12 },
{ 3, 2, _LOAD_ATTR_INSTANCE_VALUE_r23 },
{ -1, -1, -1 },
},
},
@ -1773,7 +1774,7 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = {
.best = { 1, 1, 1, 1 },
.entries = {
{ -1, -1, -1 },
{ 1, 1, _LOAD_ATTR_WITH_HINT_r11 },
{ 2, 1, _LOAD_ATTR_WITH_HINT_r12 },
{ -1, -1, -1 },
{ -1, -1, -1 },
},
@ -1837,7 +1838,7 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = {
.entries = {
{ -1, -1, -1 },
{ -1, -1, -1 },
{ 0, 2, _STORE_ATTR_WITH_HINT_r20 },
{ 1, 2, _STORE_ATTR_WITH_HINT_r21 },
{ -1, -1, -1 },
},
},
@ -2130,10 +2131,10 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = {
},
},
[_FOR_ITER_GEN_FRAME] = {
.best = { 2, 2, 2, 2 },
.best = { 0, 1, 2, 2 },
.entries = {
{ -1, -1, -1 },
{ -1, -1, -1 },
{ 3, 0, _FOR_ITER_GEN_FRAME_r03 },
{ 3, 1, _FOR_ITER_GEN_FRAME_r13 },
{ 3, 2, _FOR_ITER_GEN_FRAME_r23 },
{ -1, -1, -1 },
},
@ -3065,6 +3066,15 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = {
{ 1, 3, _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW_r31 },
},
},
[_SHUFFLE_3_LOAD_CONST_INLINE_BORROW] = {
.best = { 0, 1, 2, 3 },
.entries = {
{ 3, 0, _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r03 },
{ 3, 1, _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r13 },
{ 3, 2, _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r23 },
{ 3, 3, _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r33 },
},
},
[_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW] = {
.best = { 3, 3, 3, 3 },
.entries = {
@ -3414,18 +3424,17 @@ const uint16_t _PyUop_Uncached[MAX_UOP_REGS_ID+1] = {
[_BINARY_OP_SUBTRACT_FLOAT_r03] = _BINARY_OP_SUBTRACT_FLOAT,
[_BINARY_OP_SUBTRACT_FLOAT_r13] = _BINARY_OP_SUBTRACT_FLOAT,
[_BINARY_OP_SUBTRACT_FLOAT_r23] = _BINARY_OP_SUBTRACT_FLOAT,
[_BINARY_OP_ADD_UNICODE_r01] = _BINARY_OP_ADD_UNICODE,
[_BINARY_OP_ADD_UNICODE_r11] = _BINARY_OP_ADD_UNICODE,
[_BINARY_OP_ADD_UNICODE_r21] = _BINARY_OP_ADD_UNICODE,
[_BINARY_OP_ADD_UNICODE_r32] = _BINARY_OP_ADD_UNICODE,
[_BINARY_OP_ADD_UNICODE_r03] = _BINARY_OP_ADD_UNICODE,
[_BINARY_OP_ADD_UNICODE_r13] = _BINARY_OP_ADD_UNICODE,
[_BINARY_OP_ADD_UNICODE_r23] = _BINARY_OP_ADD_UNICODE,
[_BINARY_OP_INPLACE_ADD_UNICODE_r20] = _BINARY_OP_INPLACE_ADD_UNICODE,
[_GUARD_BINARY_OP_EXTEND_r22] = _GUARD_BINARY_OP_EXTEND,
[_BINARY_OP_EXTEND_r21] = _BINARY_OP_EXTEND,
[_BINARY_SLICE_r31] = _BINARY_SLICE,
[_STORE_SLICE_r30] = _STORE_SLICE,
[_BINARY_OP_SUBSCR_LIST_INT_r21] = _BINARY_OP_SUBSCR_LIST_INT,
[_BINARY_OP_SUBSCR_LIST_INT_r23] = _BINARY_OP_SUBSCR_LIST_INT,
[_BINARY_OP_SUBSCR_LIST_SLICE_r21] = _BINARY_OP_SUBSCR_LIST_SLICE,
[_BINARY_OP_SUBSCR_STR_INT_r21] = _BINARY_OP_SUBSCR_STR_INT,
[_BINARY_OP_SUBSCR_STR_INT_r23] = _BINARY_OP_SUBSCR_STR_INT,
[_GUARD_NOS_TUPLE_r02] = _GUARD_NOS_TUPLE,
[_GUARD_NOS_TUPLE_r12] = _GUARD_NOS_TUPLE,
[_GUARD_NOS_TUPLE_r22] = _GUARD_NOS_TUPLE,
@ -3529,9 +3538,11 @@ const uint16_t _PyUop_Uncached[MAX_UOP_REGS_ID+1] = {
[_CHECK_MANAGED_OBJECT_HAS_VALUES_r11] = _CHECK_MANAGED_OBJECT_HAS_VALUES,
[_CHECK_MANAGED_OBJECT_HAS_VALUES_r22] = _CHECK_MANAGED_OBJECT_HAS_VALUES,
[_CHECK_MANAGED_OBJECT_HAS_VALUES_r33] = _CHECK_MANAGED_OBJECT_HAS_VALUES,
[_LOAD_ATTR_INSTANCE_VALUE_r11] = _LOAD_ATTR_INSTANCE_VALUE,
[_LOAD_ATTR_INSTANCE_VALUE_r02] = _LOAD_ATTR_INSTANCE_VALUE,
[_LOAD_ATTR_INSTANCE_VALUE_r12] = _LOAD_ATTR_INSTANCE_VALUE,
[_LOAD_ATTR_INSTANCE_VALUE_r23] = _LOAD_ATTR_INSTANCE_VALUE,
[_LOAD_ATTR_MODULE_r11] = _LOAD_ATTR_MODULE,
[_LOAD_ATTR_WITH_HINT_r11] = _LOAD_ATTR_WITH_HINT,
[_LOAD_ATTR_WITH_HINT_r12] = _LOAD_ATTR_WITH_HINT,
[_LOAD_ATTR_SLOT_r11] = _LOAD_ATTR_SLOT,
[_CHECK_ATTR_CLASS_r01] = _CHECK_ATTR_CLASS,
[_CHECK_ATTR_CLASS_r11] = _CHECK_ATTR_CLASS,
@ -3544,7 +3555,7 @@ const uint16_t _PyUop_Uncached[MAX_UOP_REGS_ID+1] = {
[_GUARD_DORV_NO_DICT_r22] = _GUARD_DORV_NO_DICT,
[_GUARD_DORV_NO_DICT_r33] = _GUARD_DORV_NO_DICT,
[_STORE_ATTR_INSTANCE_VALUE_r21] = _STORE_ATTR_INSTANCE_VALUE,
[_STORE_ATTR_WITH_HINT_r20] = _STORE_ATTR_WITH_HINT,
[_STORE_ATTR_WITH_HINT_r21] = _STORE_ATTR_WITH_HINT,
[_STORE_ATTR_SLOT_r21] = _STORE_ATTR_SLOT,
[_COMPARE_OP_r21] = _COMPARE_OP,
[_COMPARE_OP_FLOAT_r01] = _COMPARE_OP_FLOAT,
@ -3609,6 +3620,8 @@ const uint16_t _PyUop_Uncached[MAX_UOP_REGS_ID+1] = {
[_ITER_NEXT_RANGE_r03] = _ITER_NEXT_RANGE,
[_ITER_NEXT_RANGE_r13] = _ITER_NEXT_RANGE,
[_ITER_NEXT_RANGE_r23] = _ITER_NEXT_RANGE,
[_FOR_ITER_GEN_FRAME_r03] = _FOR_ITER_GEN_FRAME,
[_FOR_ITER_GEN_FRAME_r13] = _FOR_ITER_GEN_FRAME,
[_FOR_ITER_GEN_FRAME_r23] = _FOR_ITER_GEN_FRAME,
[_INSERT_NULL_r10] = _INSERT_NULL,
[_LOAD_SPECIAL_r00] = _LOAD_SPECIAL,
@ -3816,6 +3829,10 @@ const uint16_t _PyUop_Uncached[MAX_UOP_REGS_ID+1] = {
[_POP_TWO_LOAD_CONST_INLINE_BORROW_r21] = _POP_TWO_LOAD_CONST_INLINE_BORROW,
[_POP_CALL_LOAD_CONST_INLINE_BORROW_r21] = _POP_CALL_LOAD_CONST_INLINE_BORROW,
[_POP_CALL_ONE_LOAD_CONST_INLINE_BORROW_r31] = _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW,
[_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r03] = _SHUFFLE_3_LOAD_CONST_INLINE_BORROW,
[_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r13] = _SHUFFLE_3_LOAD_CONST_INLINE_BORROW,
[_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r23] = _SHUFFLE_3_LOAD_CONST_INLINE_BORROW,
[_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r33] = _SHUFFLE_3_LOAD_CONST_INLINE_BORROW,
[_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW_r31] = _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW,
[_LOAD_CONST_UNDER_INLINE_r02] = _LOAD_CONST_UNDER_INLINE,
[_LOAD_CONST_UNDER_INLINE_r12] = _LOAD_CONST_UNDER_INLINE,
@ -3904,10 +3921,9 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = {
[_BINARY_OP_ADD_INT_r13] = "_BINARY_OP_ADD_INT_r13",
[_BINARY_OP_ADD_INT_r23] = "_BINARY_OP_ADD_INT_r23",
[_BINARY_OP_ADD_UNICODE] = "_BINARY_OP_ADD_UNICODE",
[_BINARY_OP_ADD_UNICODE_r01] = "_BINARY_OP_ADD_UNICODE_r01",
[_BINARY_OP_ADD_UNICODE_r11] = "_BINARY_OP_ADD_UNICODE_r11",
[_BINARY_OP_ADD_UNICODE_r21] = "_BINARY_OP_ADD_UNICODE_r21",
[_BINARY_OP_ADD_UNICODE_r32] = "_BINARY_OP_ADD_UNICODE_r32",
[_BINARY_OP_ADD_UNICODE_r03] = "_BINARY_OP_ADD_UNICODE_r03",
[_BINARY_OP_ADD_UNICODE_r13] = "_BINARY_OP_ADD_UNICODE_r13",
[_BINARY_OP_ADD_UNICODE_r23] = "_BINARY_OP_ADD_UNICODE_r23",
[_BINARY_OP_EXTEND] = "_BINARY_OP_EXTEND",
[_BINARY_OP_EXTEND_r21] = "_BINARY_OP_EXTEND_r21",
[_BINARY_OP_INPLACE_ADD_UNICODE] = "_BINARY_OP_INPLACE_ADD_UNICODE",
@ -3930,11 +3946,11 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = {
[_BINARY_OP_SUBSCR_INIT_CALL_r21] = "_BINARY_OP_SUBSCR_INIT_CALL_r21",
[_BINARY_OP_SUBSCR_INIT_CALL_r31] = "_BINARY_OP_SUBSCR_INIT_CALL_r31",
[_BINARY_OP_SUBSCR_LIST_INT] = "_BINARY_OP_SUBSCR_LIST_INT",
[_BINARY_OP_SUBSCR_LIST_INT_r21] = "_BINARY_OP_SUBSCR_LIST_INT_r21",
[_BINARY_OP_SUBSCR_LIST_INT_r23] = "_BINARY_OP_SUBSCR_LIST_INT_r23",
[_BINARY_OP_SUBSCR_LIST_SLICE] = "_BINARY_OP_SUBSCR_LIST_SLICE",
[_BINARY_OP_SUBSCR_LIST_SLICE_r21] = "_BINARY_OP_SUBSCR_LIST_SLICE_r21",
[_BINARY_OP_SUBSCR_STR_INT] = "_BINARY_OP_SUBSCR_STR_INT",
[_BINARY_OP_SUBSCR_STR_INT_r21] = "_BINARY_OP_SUBSCR_STR_INT_r21",
[_BINARY_OP_SUBSCR_STR_INT_r23] = "_BINARY_OP_SUBSCR_STR_INT_r23",
[_BINARY_OP_SUBSCR_TUPLE_INT] = "_BINARY_OP_SUBSCR_TUPLE_INT",
[_BINARY_OP_SUBSCR_TUPLE_INT_r21] = "_BINARY_OP_SUBSCR_TUPLE_INT_r21",
[_BINARY_OP_SUBTRACT_FLOAT] = "_BINARY_OP_SUBTRACT_FLOAT",
@ -4168,6 +4184,8 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = {
[_FORMAT_WITH_SPEC] = "_FORMAT_WITH_SPEC",
[_FORMAT_WITH_SPEC_r21] = "_FORMAT_WITH_SPEC_r21",
[_FOR_ITER_GEN_FRAME] = "_FOR_ITER_GEN_FRAME",
[_FOR_ITER_GEN_FRAME_r03] = "_FOR_ITER_GEN_FRAME_r03",
[_FOR_ITER_GEN_FRAME_r13] = "_FOR_ITER_GEN_FRAME_r13",
[_FOR_ITER_GEN_FRAME_r23] = "_FOR_ITER_GEN_FRAME_r23",
[_FOR_ITER_TIER_TWO] = "_FOR_ITER_TIER_TWO",
[_FOR_ITER_TIER_TWO_r23] = "_FOR_ITER_TIER_TWO_r23",
@ -4457,7 +4475,9 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = {
[_LOAD_ATTR_CLASS] = "_LOAD_ATTR_CLASS",
[_LOAD_ATTR_CLASS_r11] = "_LOAD_ATTR_CLASS_r11",
[_LOAD_ATTR_INSTANCE_VALUE] = "_LOAD_ATTR_INSTANCE_VALUE",
[_LOAD_ATTR_INSTANCE_VALUE_r11] = "_LOAD_ATTR_INSTANCE_VALUE_r11",
[_LOAD_ATTR_INSTANCE_VALUE_r02] = "_LOAD_ATTR_INSTANCE_VALUE_r02",
[_LOAD_ATTR_INSTANCE_VALUE_r12] = "_LOAD_ATTR_INSTANCE_VALUE_r12",
[_LOAD_ATTR_INSTANCE_VALUE_r23] = "_LOAD_ATTR_INSTANCE_VALUE_r23",
[_LOAD_ATTR_METHOD_LAZY_DICT] = "_LOAD_ATTR_METHOD_LAZY_DICT",
[_LOAD_ATTR_METHOD_LAZY_DICT_r02] = "_LOAD_ATTR_METHOD_LAZY_DICT_r02",
[_LOAD_ATTR_METHOD_LAZY_DICT_r12] = "_LOAD_ATTR_METHOD_LAZY_DICT_r12",
@ -4481,7 +4501,7 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = {
[_LOAD_ATTR_SLOT] = "_LOAD_ATTR_SLOT",
[_LOAD_ATTR_SLOT_r11] = "_LOAD_ATTR_SLOT_r11",
[_LOAD_ATTR_WITH_HINT] = "_LOAD_ATTR_WITH_HINT",
[_LOAD_ATTR_WITH_HINT_r11] = "_LOAD_ATTR_WITH_HINT_r11",
[_LOAD_ATTR_WITH_HINT_r12] = "_LOAD_ATTR_WITH_HINT_r12",
[_LOAD_BUILD_CLASS] = "_LOAD_BUILD_CLASS",
[_LOAD_BUILD_CLASS_r01] = "_LOAD_BUILD_CLASS_r01",
[_LOAD_COMMON_CONSTANT] = "_LOAD_COMMON_CONSTANT",
@ -4760,6 +4780,11 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = {
[_SET_IP_r33] = "_SET_IP_r33",
[_SET_UPDATE] = "_SET_UPDATE",
[_SET_UPDATE_r10] = "_SET_UPDATE_r10",
[_SHUFFLE_3_LOAD_CONST_INLINE_BORROW] = "_SHUFFLE_3_LOAD_CONST_INLINE_BORROW",
[_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r03] = "_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r03",
[_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r13] = "_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r13",
[_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r23] = "_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r23",
[_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r33] = "_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r33",
[_SPILL_OR_RELOAD] = "_SPILL_OR_RELOAD",
[_SPILL_OR_RELOAD_r01] = "_SPILL_OR_RELOAD_r01",
[_SPILL_OR_RELOAD_r02] = "_SPILL_OR_RELOAD_r02",
@ -4782,7 +4807,7 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = {
[_STORE_ATTR_SLOT] = "_STORE_ATTR_SLOT",
[_STORE_ATTR_SLOT_r21] = "_STORE_ATTR_SLOT_r21",
[_STORE_ATTR_WITH_HINT] = "_STORE_ATTR_WITH_HINT",
[_STORE_ATTR_WITH_HINT_r20] = "_STORE_ATTR_WITH_HINT_r20",
[_STORE_ATTR_WITH_HINT_r21] = "_STORE_ATTR_WITH_HINT_r21",
[_STORE_DEREF] = "_STORE_DEREF",
[_STORE_DEREF_r10] = "_STORE_DEREF_r10",
[_STORE_FAST] = "_STORE_FAST",
@ -5477,6 +5502,8 @@ int _PyUop_num_popped(int opcode, int oparg)
return 2;
case _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW:
return 3;
case _SHUFFLE_3_LOAD_CONST_INLINE_BORROW:
return 3;
case _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW:
return 4;
case _LOAD_CONST_UNDER_INLINE:

View file

@ -27,7 +27,7 @@
#define PY_RELEASE_SERIAL 3
/* Version as a string */
#define PY_VERSION "3.15.0a3"
#define PY_VERSION "3.15.0a3+"
/*--end constants--*/

View file

@ -0,0 +1,489 @@
# Profiling Binary Format
The profiling module includes a binary file format for storing sampling
profiler data. This document describes the format's structure and the
design decisions behind it.
The implementation is in
[`Modules/_remote_debugging/binary_io_writer.c`](../Modules/_remote_debugging/binary_io_writer.c)
and [`Modules/_remote_debugging/binary_io_reader.c`](../Modules/_remote_debugging/binary_io_reader.c),
with declarations in
[`Modules/_remote_debugging/binary_io.h`](../Modules/_remote_debugging/binary_io.h).
## Overview
The sampling profiler can generate enormous amounts of data. A typical
profiling session sampling at 1000 Hz for 60 seconds produces 60,000 samples.
Each sample contains a full call stack, often 20-50 frames deep, and each
frame includes a filename, function name, and line number. In a text-based
format like collapsed stacks, this would mean repeating the same long file
paths and function names thousands of times.
The binary format addresses this through two key strategies:
1. **Deduplication**: Strings and frames are stored once in lookup tables,
then referenced by small integer indices. A 100-character file path that
appears in 50,000 samples is stored once, not 50,000 times.
2. **Compact encoding**: Variable-length integers (varints) encode small
values in fewer bytes. Since most indices are small (under 128), they
typically need only one byte instead of four.
Together with optional zstd compression, these techniques reduce file sizes
by 10-50x compared to text formats while also enabling faster I/O.
## File Layout
The file consists of five sections:
```
+------------------+ Offset 0
| Header | 64 bytes (fixed)
+------------------+ Offset 64
| |
| Sample Data | Variable size (optionally compressed)
| |
+------------------+ string_table_offset
| String Table | Variable size
+------------------+ frame_table_offset
| Frame Table | Variable size
+------------------+ file_size - 32
| Footer | 32 bytes (fixed)
+------------------+ file_size
```
The layout is designed for streaming writes during profiling. The profiler
cannot know in advance how many unique strings or frames will be encountered,
so these tables must be built incrementally and written at the end.
The header comes first so readers can quickly validate the file and locate
the metadata tables. The sample data follows immediately, allowing the writer
to stream samples directly to disk (or through a compression stream) without
buffering the entire dataset in memory.
The string and frame tables are placed after sample data because they grow
as new unique entries are discovered during profiling. By deferring their
output until finalization, the writer avoids the complexity of reserving
space or rewriting portions of the file.
The footer at the end contains counts needed to allocate arrays before
parsing the tables. Placing it at a fixed offset from the end (rather than
at a variable offset recorded in the header) means readers can locate it
with a single seek to `file_size - 32`, without first reading the header.
## Header
```
Offset Size Type Description
+--------+------+---------+----------------------------------------+
| 0 | 4 | uint32 | Magic number (0x54414348 = "TACH") |
| 4 | 4 | uint32 | Format version |
| 8 | 4 | bytes | Python version (major, minor, micro, |
| | | | reserved) |
| 12 | 8 | uint64 | Start timestamp (microseconds) |
| 20 | 8 | uint64 | Sample interval (microseconds) |
| 28 | 4 | uint32 | Total sample count |
| 32 | 4 | uint32 | Thread count |
| 36 | 8 | uint64 | String table offset |
| 44 | 8 | uint64 | Frame table offset |
| 52 | 4 | uint32 | Compression type (0=none, 1=zstd) |
| 56 | 8 | bytes | Reserved (zero-filled) |
+--------+------+---------+----------------------------------------+
```
The magic number `0x54414348` ("TACH" for Tachyon) identifies the file format
and also serves as an **endianness marker**. When read on a system with
different byte order than the writer, it appears as `0x48434154`. The reader
uses this to detect cross-endian files and automatically byte-swap all
multi-byte integer fields.
The Python version field records the major, minor, and micro version numbers
of the Python interpreter that generated the file. This allows analysis tools
to detect version mismatches when replaying data collected on a different
Python version, which may have different internal structures or behaviors.
The header is written as zeros initially, then overwritten with actual values
during finalization. This requires the output stream to be seekable, which
is acceptable since the format targets regular files rather than pipes or
network streams.
## Sample Data
Sample data begins at offset 64 and extends to `string_table_offset`. Samples
use delta compression to minimize redundancy when consecutive samples from the
same thread have identical or similar call stacks.
### Stack Encoding Types
Each sample record begins with thread identification, then an encoding byte:
| Code | Name | Description |
|------|------|-------------|
| 0x00 | REPEAT | RLE: identical stack repeated N times |
| 0x01 | FULL | Complete stack (first sample or no match) |
| 0x02 | SUFFIX | Shares N frames from bottom of previous stack |
| 0x03 | POP_PUSH | Remove M frames from top, add N new frames |
### Record Formats
**REPEAT (0x00) - Run-Length Encoded Identical Stacks:**
```
+-----------------+-----------+----------------------------------------+
| thread_id | 8 bytes | Thread identifier (uint64, fixed) |
| interpreter_id | 4 bytes | Interpreter ID (uint32, fixed) |
| encoding | 1 byte | 0x00 (REPEAT) |
| count | varint | Number of samples in this RLE group |
| samples | varies | Interleaved: [delta: varint, status: 1]|
| | | repeated count times |
+-----------------+-----------+----------------------------------------+
```
The stack is inherited from this thread's previous sample. Each sample in the
group gets its own timestamp delta and status byte, stored as interleaved pairs
(delta1, status1, delta2, status2, ...) rather than separate arrays.
**FULL (0x01) - Complete Stack:**
```
+-----------------+-----------+----------------------------------------+
| thread_id | 8 bytes | Thread identifier (uint64, fixed) |
| interpreter_id | 4 bytes | Interpreter ID (uint32, fixed) |
| encoding | 1 byte | 0x01 (FULL) |
| timestamp_delta | varint | Microseconds since thread's last sample|
| status | 1 byte | Thread state flags |
| stack_depth | varint | Number of frames in call stack |
| frame_indices | varint[] | Array of frame table indices |
+-----------------+-----------+----------------------------------------+
```
Used for the first sample from a thread, or when delta encoding would not
provide savings.
**SUFFIX (0x02) - Shared Suffix Match:**
```
+-----------------+-----------+----------------------------------------+
| thread_id | 8 bytes | Thread identifier (uint64, fixed) |
| interpreter_id | 4 bytes | Interpreter ID (uint32, fixed) |
| encoding | 1 byte | 0x02 (SUFFIX) |
| timestamp_delta | varint | Microseconds since thread's last sample|
| status | 1 byte | Thread state flags |
| shared_count | varint | Frames shared from bottom of prev stack|
| new_count | varint | New frames at top of stack |
| new_frames | varint[] | Array of new_count frame indices |
+-----------------+-----------+----------------------------------------+
```
Used when a function call added frames to the top of the stack. The shared
frames from the previous stack are kept, and new frames are prepended.
**POP_PUSH (0x03) - Pop and Push:**
```
+-----------------+-----------+----------------------------------------+
| thread_id | 8 bytes | Thread identifier (uint64, fixed) |
| interpreter_id | 4 bytes | Interpreter ID (uint32, fixed) |
| encoding | 1 byte | 0x03 (POP_PUSH) |
| timestamp_delta | varint | Microseconds since thread's last sample|
| status | 1 byte | Thread state flags |
| pop_count | varint | Frames to remove from top of prev stack|
| push_count | varint | New frames to add at top |
| new_frames | varint[] | Array of push_count frame indices |
+-----------------+-----------+----------------------------------------+
```
Used when the code path changed: some frames were popped (function returns)
and new frames were pushed (different function calls).
### Thread and Interpreter Identification
Thread IDs are 64-bit values that can be large (memory addresses on some
platforms) and vary unpredictably. Using a fixed 8-byte encoding avoids
the overhead of varint encoding for large values and simplifies parsing
since the reader knows exactly where each field begins.
The interpreter ID identifies which Python sub-interpreter the thread
belongs to, allowing analysis tools to separate activity across interpreters
in processes using multiple sub-interpreters.
### Status Byte
The status byte is a bitfield encoding thread state at sample time:
| Bit | Flag | Meaning |
|-----|-----------------------|--------------------------------------------|
| 0 | THREAD_STATUS_HAS_GIL | Thread holds the GIL (Global Interpreter Lock) |
| 1 | THREAD_STATUS_ON_CPU | Thread is actively running on a CPU core |
| 2 | THREAD_STATUS_UNKNOWN | Thread state could not be determined |
| 3 | THREAD_STATUS_GIL_REQUESTED | Thread is waiting to acquire the GIL |
| 4 | THREAD_STATUS_HAS_EXCEPTION | Thread has a pending exception |
Multiple flags can be set simultaneously (e.g., a thread can hold the GIL
while also running on CPU). Analysis tools use these to filter samples or
visualize thread states over time.
### Timestamp Delta Encoding
Timestamps use delta encoding rather than absolute values. Absolute
timestamps in microseconds require 8 bytes each, but consecutive samples
from the same thread are typically separated by the sampling interval
(e.g., 1000 microseconds), so the delta between them is small and fits
in 1-2 varint bytes. The writer tracks the previous timestamp for each
thread separately. The first sample from a thread encodes its delta from
the profiling start time; subsequent samples encode the delta from that
thread's previous sample. This per-thread tracking is necessary because
samples are interleaved across threads in arrival order, not grouped by
thread.
For REPEAT (RLE) records, timestamp deltas and status bytes are stored as
interleaved pairs (delta, status, delta, status, ...) - one pair per
repeated sample - allowing efficient batching while preserving the exact
timing and state of each sample.
### Frame Indexing
Each frame in a call stack is represented by an index into the frame table
rather than inline data. This provides massive space savings because call
stacks are highly repetitive: the same function appears in many samples
(hot functions), call stacks often share common prefixes (main -> app ->
handler -> ...), and recursive functions create repeated frame sequences.
A frame index is typically 1-2 varint bytes. Inline frame data would be
20-200+ bytes (two strings plus a line number). For a profile with 100,000
samples averaging 30 frames each, this reduces frame data from potentially
gigabytes to tens of megabytes.
Frame indices are written innermost-first (the currently executing frame
has index 0 in the array). This ordering works well with delta compression:
function calls typically add frames at the top (index 0), while shared
frames remain at the bottom.
## String Table
The string table stores deduplicated UTF-8 strings (filenames and function
names). It begins at `string_table_offset` and contains entries in order of
their assignment during writing:
```
+----------------+
| length: varint |
| data: bytes |
+----------------+ (repeated for each string)
```
Strings are stored in the order they were first encountered during writing.
The first unique filename gets index 0, the second gets index 1, and so on.
Length-prefixing (rather than null-termination) allows strings containing
null bytes and enables readers to allocate exact-sized buffers. The varint
length encoding means short strings (under 128 bytes) need only one length
byte.
## Frame Table
The frame table stores deduplicated frame entries:
```
+----------------------+
| filename_idx: varint |
| funcname_idx: varint |
| lineno: svarint |
+----------------------+ (repeated for each frame)
```
Each unique (filename, funcname, lineno) combination gets one entry. Two
calls to the same function at different line numbers produce different
frame entries; two calls at the same line number share one entry.
Strings and frames are deduplicated separately because they have different
cardinalities and reference patterns. A codebase might have hundreds of
unique source files but thousands of unique functions. Many functions share
the same filename, so storing the filename index in each frame entry (rather
than the full string) provides an additional layer of deduplication. A frame
entry is just three varints (typically 3-6 bytes) rather than two full
strings plus a line number.
Line numbers use signed varint (zigzag encoding) rather than unsigned to
handle edge cases. Synthetic frames—generated frames that don't correspond
directly to Python source code, such as C extension boundaries or internal
interpreter frames—use line number 0 or -1 to indicate the absence of a
source location. Zigzag encoding ensures these small negative values encode
efficiently (1 becomes 1, which is one byte) rather than requiring the
maximum varint length.
## Footer
```
Offset Size Type Description
+--------+------+---------+----------------------------------------+
| 0 | 4 | uint32 | String count |
| 4 | 4 | uint32 | Frame count |
| 8 | 8 | uint64 | Total file size |
| 16 | 16 | bytes | Checksum (reserved, currently zeros) |
+--------+------+---------+----------------------------------------+
```
The string and frame counts allow readers to pre-allocate arrays of the
correct size before parsing the tables. Without these counts, readers would
need to either scan the tables twice (once to count, once to parse) or use
dynamically-growing arrays.
The file size field provides a consistency check: if the actual file size
does not match, the file may be truncated or corrupted.
The checksum field is reserved for future use. A checksum would allow
detection of corruption but adds complexity and computation cost. The
current implementation leaves this as zeros.
## Variable-Length Integer Encoding
The format uses LEB128 (Little Endian Base 128) for unsigned integers and
zigzag + LEB128 for signed integers. These encodings are widely used
(Protocol Buffers, DWARF debug info, WebAssembly) and well-understood.
### Unsigned Varint (LEB128)
Each byte stores 7 bits of data. The high bit indicates whether more bytes
follow:
```
Value Encoded bytes
0-127 [0xxxxxxx] (1 byte)
128-16383 [1xxxxxxx] [0xxxxxxx] (2 bytes)
16384+ [1xxxxxxx] [1xxxxxxx] ... (3+ bytes)
```
Most indices in profiling data are small. A profile with 1000 unique frames
needs at most 2 bytes per frame index. The common case (indices under 128)
needs only 1 byte.
### Signed Varint (Zigzag)
Standard LEB128 encodes 1 as a very large unsigned value, requiring many
bytes. Zigzag encoding interleaves positive and negative values:
```
0 -> 0 -1 -> 1 1 -> 2 -2 -> 3 2 -> 4
```
This ensures small-magnitude values (whether positive or negative) encode
in few bytes.
## Compression
When compression is enabled, the sample data region contains a zstd stream.
The string table, frame table, and footer remain uncompressed so readers can
access metadata without decompressing the entire file. A tool that only needs
to report "this file contains 50,000 samples of 3 threads" can read the header
and footer without touching the compressed sample data. This also simplifies
the format: the header's offset fields point directly to the tables rather
than to positions within a decompressed stream.
Zstd provides an excellent balance of compression ratio and speed. Profiling
data compresses very well (often 5-10x) due to repetitive patterns: the same
small set of frame indices appears repeatedly, and delta-encoded timestamps
cluster around the sampling interval. Zstd's streaming API allows compression
without buffering the entire dataset. The writer feeds sample data through
the compressor incrementally, flushing compressed chunks to disk as they
become available.
Level 5 compression is used as a default. Lower levels (1-3) are faster but
compress less; higher levels (6+) compress more but slow down writing. Level
5 provides good compression with minimal impact on profiling overhead.
## Reading and Writing
### Writing
1. Open the output file and write 64 zero bytes as a placeholder header
2. Initialize empty string and frame dictionaries for deduplication
3. For each sample:
- Intern any new strings, assigning sequential indices
- Intern any new frames, assigning sequential indices
- Encode the sample record and write to the buffer
- Flush the buffer through compression (if enabled) when full
4. Flush remaining buffered data and finalize compression
5. Write the string table (length-prefixed strings in index order)
6. Write the frame table (varint-encoded entries in index order)
7. Write the footer with final counts
8. Seek to offset 0 and write the header with actual values
The writer maintains two dictionaries: one mapping strings to indices, one
mapping (filename_idx, funcname_idx, lineno) tuples to frame indices. These
enable O(1) lookup during interning.
### Reading
1. Read the header magic number to detect endianness (set `needs_swap` flag
if the magic appears byte-swapped)
2. Validate version and read remaining header fields (byte-swapping if needed)
3. Seek to end 32 and read the footer (byte-swapping counts if needed)
4. Allocate string array of `string_count` elements
5. Parse the string table, populating the array
6. Allocate frame array of `frame_count * 3` uint32 elements
7. Parse the frame table, populating the array
8. If compressed, decompress the sample data region
9. Iterate through samples, resolving indices to strings/frames
(byte-swapping thread_id and interpreter_id if needed)
The reader builds lookup arrays rather than dictionaries since it only needs
index-to-value mapping, not value-to-index.
## Platform Considerations
### Byte Ordering and Cross-Platform Portability
The binary format uses **native byte order** for all multi-byte integer
fields when writing. However, the reader supports **cross-endian reading**:
files written on a little-endian system (x86, ARM) can be read on a
big-endian system (s390x, PowerPC), and vice versa.
The magic number doubles as an endianness marker. When read on a system with
different byte order, it appears byte-swapped (`0x48434154` instead of
`0x54414348`). The reader detects this and automatically byte-swaps all
fixed-width integer fields during parsing.
Writers must use `memcpy()` from properly-sized integer types when writing
fixed-width integer fields. When the source variable's type differs from the
field width (e.g., `size_t` written as 4 bytes), explicit casting to the
correct type (e.g., `uint32_t`) is required before `memcpy()`. On big-endian
systems, copying from an oversized type would copy the wrong bytes—high-order
zeros instead of the actual value.
The reader tracks whether byte-swapping is needed via a `needs_swap` flag set
during header parsing. All fixed-width fields in the header, footer, and
sample data are conditionally byte-swapped using Python's internal byte-swap
functions (`_Py_bswap32`, `_Py_bswap64` from `pycore_bitutils.h`).
Variable-length integers (varints) are byte-order independent since they
encode values one byte at a time using the LEB128 scheme, so they require
no special handling for cross-endian reading.
### Memory-Mapped I/O
On Unix systems (Linux, macOS), the reader uses `mmap()` to map the file
into the process address space. The kernel handles paging data in and out
as needed, no explicit read() calls or buffer management are required,
multiple readers can share the same physical pages, and sequential access
patterns benefit from kernel read-ahead.
The implementation uses `madvise()` to hint the access pattern to the kernel:
`MADV_SEQUENTIAL` indicates the file will be read linearly, enabling
aggressive read-ahead. `MADV_WILLNEED` requests pre-faulting of pages.
On Linux, `MAP_POPULATE` pre-faults all pages at mmap time rather than on
first access, moving page fault overhead from the parsing loop to the
initial mapping for more predictable performance. For large files (over
32 MB), `MADV_HUGEPAGE` requests transparent huge pages (2 MB instead of
4 KB) to reduce TLB pressure when accessing large amounts of data.
On Windows, the implementation falls back to standard file I/O with full
file buffering. Profiling data files are typically small enough (tens to
hundreds of megabytes) that this is acceptable.
The writer uses a 512 KB buffer to batch small writes. Each sample record
is typically tens of bytes; writing these individually would incur excessive
syscall overhead. The buffer accumulates data until full, then flushes in
one write() call (or feeds through the compression stream).
## Future Considerations
The format reserves space for future extensions. The 12 reserved bytes in
the header could hold additional metadata. The 16-byte checksum field in
the footer is currently unused. The version field allows incompatible
changes with graceful rejection. New compression types could be added
(compression_type > 1).
Any changes that alter the meaning of existing fields or the parsing logic
should increment the version number to prevent older readers from
misinterpreting new files.

View file

@ -86,14 +86,15 @@ class REPLThread(threading.Thread):
global return_code
try:
banner = (
f'asyncio REPL {sys.version} on {sys.platform}\n'
f'Use "await" directly instead of "asyncio.run()".\n'
f'Type "help", "copyright", "credits" or "license" '
f'for more information.\n'
)
if not sys.flags.quiet:
banner = (
f'asyncio REPL {sys.version} on {sys.platform}\n'
f'Use "await" directly instead of "asyncio.run()".\n'
f'Type "help", "copyright", "credits" or "license" '
f'for more information.\n'
)
console.write(banner)
console.write(banner)
if startup_path := os.getenv("PYTHONSTARTUP"):
sys.audit("cpython.run_startup", startup_path)
@ -240,4 +241,5 @@ if __name__ == '__main__':
break
console.write('exiting asyncio REPL...\n')
loop.close()
sys.exit(return_code)

View file

@ -794,7 +794,8 @@ class RawConfigParser(MutableMapping):
"""
elements_added = set()
for section, keys in dictionary.items():
section = str(section)
if section is not UNNAMED_SECTION:
section = str(section)
try:
self.add_section(section)
except (DuplicateSectionError, ValueError):

View file

@ -214,7 +214,7 @@ def format_string(f, val, grouping=False, monetary=False):
Grouping is applied if the third parameter is true.
Conversion uses monetary thousands separator and grouping strings if
forth parameter monetary is true."""
fourth parameter monetary is true."""
global _percent_re
if _percent_re is None:
import re

View file

@ -2181,11 +2181,7 @@ def _unlock_file(f):
def _create_carefully(path):
"""Create a file if it doesn't exist and open for reading and writing."""
fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR, 0o666)
try:
return open(path, 'rb+')
finally:
os.close(fd)
return open(path, 'xb+')
def _create_temporary(path):
"""Create a temp file based on path and open for reading and writing."""

View file

@ -391,17 +391,22 @@ class Pdb(bdb.Bdb, cmd.Cmd):
# Read ~/.pdbrc and ./.pdbrc
self.rcLines = []
if readrc:
home_rcfile = os.path.expanduser("~/.pdbrc")
local_rcfile = os.path.abspath(".pdbrc")
try:
with open(os.path.expanduser('~/.pdbrc'), encoding='utf-8') as rcFile:
self.rcLines.extend(rcFile)
except OSError:
pass
try:
with open(".pdbrc", encoding='utf-8') as rcFile:
self.rcLines.extend(rcFile)
with open(home_rcfile, encoding='utf-8') as rcfile:
self.rcLines.extend(rcfile)
except OSError:
pass
if local_rcfile != home_rcfile:
try:
with open(local_rcfile, encoding='utf-8') as rcfile:
self.rcLines.extend(rcfile)
except OSError:
pass
self.commands = {} # associates a command list to breakpoint numbers
self.commands_defining = False # True while in the process of defining
# a command list
@ -1315,7 +1320,14 @@ class Pdb(bdb.Bdb, cmd.Cmd):
reached.
"""
if not arg:
bnum = len(bdb.Breakpoint.bpbynumber) - 1
for bp in reversed(bdb.Breakpoint.bpbynumber):
if bp is None:
continue
bnum = bp.number
break
else:
self.error('cannot set commands: no existing breakpoint')
return
else:
try:
bnum = int(arg)

View file

@ -46,6 +46,7 @@ system restrictions or missing privileges.
"""
from .cli import main
from .errors import SamplingUnknownProcessError, SamplingModuleNotFoundError, SamplingScriptNotFoundError
def handle_permission_error():
"""Handle PermissionError by displaying appropriate error message."""
@ -64,3 +65,9 @@ if __name__ == '__main__':
main()
except PermissionError:
handle_permission_error()
except SamplingUnknownProcessError as err:
print(f"Tachyon cannot find the process: {err}", file=sys.stderr)
sys.exit(1)
except (SamplingModuleNotFoundError, SamplingScriptNotFoundError) as err:
print(f"Tachyon cannot find the target: {err}", file=sys.stderr)
sys.exit(1)

View file

@ -0,0 +1,5 @@
import locale
def fmt(value: int | float, decimals: int = 1) -> str:
return locale.format_string(f'%.{decimals}f', value, grouping=True)

View file

@ -5,6 +5,18 @@
This file extends the shared foundation with heatmap-specific styles.
========================================================================== */
/* Heatmap heat colors - using base.css colors with 60% opacity */
[data-theme="dark"] {
--heat-1: rgba(90, 123, 167, 0.60);
--heat-2: rgba(106, 148, 168, 0.60);
--heat-3: rgba(122, 172, 172, 0.60);
--heat-4: rgba(142, 196, 152, 0.60);
--heat-5: rgba(168, 216, 136, 0.60);
--heat-6: rgba(200, 222, 122, 0.60);
--heat-7: rgba(244, 212, 93, 0.60);
--heat-8: rgba(255, 122, 69, 0.60);
}
/* --------------------------------------------------------------------------
Layout Overrides (Heatmap-specific)
-------------------------------------------------------------------------- */
@ -1129,6 +1141,10 @@
.line-samples-cumulative {
padding: 0 4px;
}
.bytecode-panel {
margin: 8px 10px 8px 160px;
}
}
.bytecode-toggle {
@ -1160,13 +1176,77 @@
}
.bytecode-panel {
margin-left: 90px;
padding: 8px 15px;
background: var(--bg-secondary);
border-left: 3px solid var(--accent);
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 8px;
box-shadow: var(--shadow-md);
font-family: var(--font-mono);
font-size: 12px;
margin-bottom: 4px;
color: var(--text-primary);
line-height: 1.5;
word-wrap: break-word;
overflow-wrap: break-word;
padding: 0;
margin: 8px 10px 8px 250px;
position: relative;
z-index: 1;
overflow-y: auto;
max-height: 500px;
flex: 1;
transition: padding 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.bytecode-panel.expanded {
padding: 14px;
}
.bytecode-wrapper {
position: relative;
display: flex;
overflow: visible;
max-height: 0;
opacity: 0;
transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease-in-out;
}
.bytecode-wrapper.expanded {
max-height: 600px;
opacity: 1;
transition: max-height 0.5s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.4s ease-in-out;
}
/* Column backdrop matching table header columns (line/self/total) */
.bytecode-columns {
display: none;
position: absolute;
left: 0;
overflow: hidden;
pointer-events: none;
z-index: 0;
}
.bytecode-wrapper.expanded .bytecode-columns {
display: flex;
top: 0;
bottom: 0;
}
.bytecode-panel::-webkit-scrollbar {
width: 8px;
}
.bytecode-panel::-webkit-scrollbar-track {
background: var(--bg-secondary);
border-radius: 4px;
}
.bytecode-panel::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 4px;
}
.bytecode-panel::-webkit-scrollbar-thumb:hover {
background: var(--text-muted);
}
/* Specialization summary bar */

View file

@ -15,37 +15,13 @@ let coldCodeHidden = false;
// ============================================================================
function toggleTheme() {
const html = document.documentElement;
const current = html.getAttribute('data-theme') || 'light';
const next = current === 'light' ? 'dark' : 'light';
html.setAttribute('data-theme', next);
localStorage.setItem('heatmap-theme', next);
// Update theme button icon
const btn = document.getElementById('theme-btn');
if (btn) {
btn.querySelector('.icon-moon').style.display = next === 'dark' ? 'none' : '';
btn.querySelector('.icon-sun').style.display = next === 'dark' ? '' : 'none';
}
toggleAndSaveTheme();
applyLineColors();
// Rebuild scroll marker with new theme colors
buildScrollMarker();
}
function restoreUIState() {
// Restore theme
const savedTheme = localStorage.getItem('heatmap-theme');
if (savedTheme) {
document.documentElement.setAttribute('data-theme', savedTheme);
const btn = document.getElementById('theme-btn');
if (btn) {
btn.querySelector('.icon-moon').style.display = savedTheme === 'dark' ? 'none' : '';
btn.querySelector('.icon-sun').style.display = savedTheme === 'dark' ? '' : 'none';
}
}
}
// ============================================================================
// Utility Functions
// ============================================================================
@ -542,20 +518,23 @@ function toggleBytecode(button) {
const lineId = lineDiv.id;
const lineNum = lineId.replace('line-', '');
const panel = document.getElementById(`bytecode-${lineNum}`);
const wrapper = document.getElementById(`bytecode-wrapper-${lineNum}`);
if (!panel) return;
if (!panel || !wrapper) return;
const isExpanded = panel.style.display !== 'none';
const isExpanded = panel.classList.contains('expanded');
if (isExpanded) {
panel.style.display = 'none';
panel.classList.remove('expanded');
wrapper.classList.remove('expanded');
button.classList.remove('expanded');
button.innerHTML = '&#9654;'; // Right arrow
} else {
if (!panel.dataset.populated) {
populateBytecodePanel(panel, button);
}
panel.style.display = 'block';
panel.classList.add('expanded');
wrapper.classList.add('expanded');
button.classList.add('expanded');
button.innerHTML = '&#9660;'; // Down arrow
}
@ -598,10 +577,12 @@ function populateBytecodePanel(panel, button) {
else if (specPct >= 33) specClass = 'medium';
// Build specialization summary
const instruction_word = instructions.length === 1 ? 'instruction' : 'instructions';
const sample_word = totalSamples === 1 ? 'sample' : 'samples';
let html = `<div class="bytecode-spec-summary ${specClass}">
<span class="spec-pct">${specPct}%</span>
<span class="spec-label">specialized</span>
<span class="spec-detail">(${specializedCount}/${instructions.length} instructions, ${specializedSamples.toLocaleString()}/${totalSamples.toLocaleString()} samples)</span>
<span class="spec-detail">(${specializedCount}/${instructions.length} ${instruction_word}, ${specializedSamples.toLocaleString()}/${totalSamples.toLocaleString()} ${sample_word})</span>
</div>`;
html += '<div class="bytecode-header">' +

View file

@ -19,35 +19,10 @@ function applyHeatmapBarColors() {
// ============================================================================
function toggleTheme() {
const html = document.documentElement;
const current = html.getAttribute('data-theme') || 'light';
const next = current === 'light' ? 'dark' : 'light';
html.setAttribute('data-theme', next);
localStorage.setItem('heatmap-theme', next);
// Update theme button icon
const btn = document.getElementById('theme-btn');
if (btn) {
btn.querySelector('.icon-moon').style.display = next === 'dark' ? 'none' : '';
btn.querySelector('.icon-sun').style.display = next === 'dark' ? '' : 'none';
}
toggleAndSaveTheme();
applyHeatmapBarColors();
}
function restoreUIState() {
// Restore theme
const savedTheme = localStorage.getItem('heatmap-theme');
if (savedTheme) {
document.documentElement.setAttribute('data-theme', savedTheme);
const btn = document.getElementById('theme-btn');
if (btn) {
btn.querySelector('.icon-moon').style.display = savedTheme === 'dark' ? 'none' : '';
btn.querySelector('.icon-sun').style.display = savedTheme === 'dark' ? '' : 'none';
}
}
}
// ============================================================================
// Type Section Toggle (stdlib, project, etc)
// ============================================================================

View file

@ -1,5 +1,5 @@
<!doctype html>
<html lang="en" data-theme="light">
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

View file

@ -1,5 +1,5 @@
<!doctype html>
<html lang="en" data-theme="light">
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

View file

@ -39,6 +39,42 @@ function intensityToColor(intensity) {
return rootStyle.getPropertyValue(`--heat-${level}`).trim();
}
// ============================================================================
// Theme Support
// ============================================================================
// Get the preferred theme from localStorage or browser preference
function getPreferredTheme() {
const saved = localStorage.getItem('heatmap-theme');
if (saved) return saved;
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
// Apply theme and update UI. Returns the applied theme.
function applyTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
const btn = document.getElementById('theme-btn');
if (btn) {
btn.querySelector('.icon-moon').style.display = theme === 'dark' ? 'none' : '';
btn.querySelector('.icon-sun').style.display = theme === 'dark' ? '' : 'none';
}
return theme;
}
// Toggle theme and save preference. Returns the new theme.
function toggleAndSaveTheme() {
const current = document.documentElement.getAttribute('data-theme') || 'light';
const next = current === 'light' ? 'dark' : 'light';
applyTheme(next);
localStorage.setItem('heatmap-theme', next);
return next;
}
// Restore theme from localStorage, or use browser preference
function restoreUIState() {
applyTheme(getPreferredTheme());
}
// ============================================================================
// Favicon (Reuse logo image as favicon)
// ============================================================================

View file

@ -124,15 +124,15 @@
--header-gradient: linear-gradient(135deg, #21262d 0%, #30363d 100%);
/* Dark mode heat palette - muted colors that provide sufficient contrast with light text */
--heat-1: rgba(74, 123, 167, 0.35);
--heat-2: rgba(90, 159, 168, 0.38);
--heat-3: rgba(106, 181, 181, 0.40);
--heat-4: rgba(126, 196, 136, 0.42);
--heat-5: rgba(160, 216, 120, 0.45);
--heat-6: rgba(196, 222, 106, 0.48);
--heat-7: rgba(244, 212, 77, 0.50);
--heat-8: rgba(255, 107, 53, 0.55);
/* Dark mode heat palette - cool to warm gradient for visualization */
--heat-1: rgba(90, 123, 167, 1);
--heat-2: rgba(106, 148, 168, 1);
--heat-3: rgba(122, 172, 172, 1);
--heat-4: rgba(142, 196, 152, 1);
--heat-5: rgba(168, 216, 136, 1);
--heat-6: rgba(200, 222, 122, 1);
--heat-7: rgba(244, 212, 93, 1);
--heat-8: rgba(255, 122, 69, 1);
/* Code view specific - dark mode */
--code-bg: #0d1117;

View file

@ -0,0 +1,120 @@
"""Thin Python wrapper around C binary writer for profiling data."""
import time
import _remote_debugging
from .collector import Collector
# Compression type constants (must match binary_io.h)
COMPRESSION_NONE = 0
COMPRESSION_ZSTD = 1
def _resolve_compression(compression):
"""Resolve compression type from string or int.
Args:
compression: 'auto', 'zstd', 'none', or int (0/1)
Returns:
int: Compression type constant
"""
if isinstance(compression, int):
return compression
compression = compression.lower()
if compression == 'none':
return COMPRESSION_NONE
elif compression == 'zstd':
return COMPRESSION_ZSTD
elif compression == 'auto':
# Auto: use zstd if available, otherwise none
if _remote_debugging.zstd_available():
return COMPRESSION_ZSTD
return COMPRESSION_NONE
else:
raise ValueError(f"Unknown compression type: {compression}")
class BinaryCollector(Collector):
"""High-performance binary collector using C implementation.
This collector writes profiling data directly to a binary file format
with optional zstd compression. All I/O is performed in C for maximum
throughput.
The binary format uses string/frame deduplication and varint encoding
for efficient storage.
"""
def __init__(self, filename, sample_interval_usec, *, skip_idle=False,
compression='auto'):
"""Create a new binary collector.
Args:
filename: Path to output binary file
sample_interval_usec: Sampling interval in microseconds
skip_idle: If True, skip idle threads (not used in binary format)
compression: 'auto', 'zstd', 'none', or int (0=none, 1=zstd)
"""
self.filename = filename
self.sample_interval_usec = sample_interval_usec
self.skip_idle = skip_idle
compression_type = _resolve_compression(compression)
start_time_us = int(time.monotonic() * 1_000_000)
self._writer = _remote_debugging.BinaryWriter(
filename, sample_interval_usec, start_time_us, compression=compression_type
)
def collect(self, stack_frames, timestamp_us=None):
"""Collect profiling data from stack frames.
This passes stack_frames directly to the C writer which handles
all encoding and buffering.
Args:
stack_frames: List of InterpreterInfo objects from _remote_debugging
timestamp_us: Optional timestamp in microseconds. If not provided,
uses time.monotonic() to generate one.
"""
if timestamp_us is None:
timestamp_us = int(time.monotonic() * 1_000_000)
self._writer.write_sample(stack_frames, timestamp_us)
def collect_failed_sample(self):
"""Record a failed sample attempt (no-op for binary format)."""
pass
def export(self, filename=None):
"""Finalize and close the binary file.
Args:
filename: Ignored (binary files are written incrementally)
"""
self._writer.finalize()
@property
def total_samples(self):
return self._writer.total_samples
def get_stats(self):
"""Get encoding statistics.
Returns:
Dict with encoding statistics including repeat/full/suffix/pop-push
record counts, frames written/saved, and compression ratio.
"""
return self._writer.get_stats()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Context manager exit - finalize unless there was an error."""
if exc_type is None:
self._writer.finalize()
else:
self._writer.close()
return False

View file

@ -0,0 +1,128 @@
"""Thin Python wrapper around C binary reader for profiling data."""
class BinaryReader:
"""High-performance binary reader using C implementation.
This reader uses memory-mapped I/O (on Unix) for fast replay of
profiling data from binary files.
Use as a context manager:
with BinaryReader('profile.bin') as reader:
info = reader.get_info()
reader.replay_samples(collector, progress_callback)
"""
def __init__(self, filename):
"""Create a new binary reader.
Args:
filename: Path to input binary file
"""
self.filename = filename
self._reader = None
def __enter__(self):
import _remote_debugging
self._reader = _remote_debugging.BinaryReader(self.filename)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if self._reader is not None:
self._reader.close()
self._reader = None
return False
def get_info(self):
"""Get metadata about the binary file.
Returns:
dict: File metadata including:
- sample_count: Number of samples in the file
- sample_interval_us: Sampling interval in microseconds
- start_time_us: Start timestamp in microseconds
- string_count: Number of unique strings
- frame_count: Number of unique frames
- compression: Compression type used
"""
if self._reader is None:
raise RuntimeError("Reader not open. Use as context manager.")
return self._reader.get_info()
def replay_samples(self, collector, progress_callback=None):
"""Replay samples from binary file through a collector.
This allows converting binary profiling data to other formats
(e.g., flamegraph, pstats) by replaying through the appropriate
collector.
Args:
collector: A Collector instance with a collect() method
progress_callback: Optional callable(current, total) for progress
Returns:
int: Number of samples replayed
"""
if self._reader is None:
raise RuntimeError("Reader not open. Use as context manager.")
return self._reader.replay(collector, progress_callback)
@property
def sample_count(self):
if self._reader is None:
raise RuntimeError("Reader not open. Use as context manager.")
return self._reader.get_info()['sample_count']
def get_stats(self):
"""Get reconstruction statistics from replay.
Returns:
dict: Statistics about record types decoded and samples
reconstructed during replay.
"""
if self._reader is None:
raise RuntimeError("Reader not open. Use as context manager.")
return self._reader.get_stats()
def convert_binary_to_format(input_file, output_file, output_format,
sample_interval_usec=None, progress_callback=None):
"""Convert a binary profiling file to another format.
Args:
input_file: Path to input binary file
output_file: Path to output file
output_format: Target format ('flamegraph', 'collapsed', 'pstats', etc.)
sample_interval_usec: Override sample interval (uses file's if None)
progress_callback: Optional callable(current, total) for progress
Returns:
int: Number of samples converted
"""
from .gecko_collector import GeckoCollector
from .stack_collector import FlamegraphCollector, CollapsedStackCollector
from .pstats_collector import PStatsCollector
with BinaryReader(input_file) as reader:
info = reader.get_info()
interval = sample_interval_usec or info['sample_interval_us']
# Create appropriate collector based on format
if output_format == 'flamegraph':
collector = FlamegraphCollector(interval)
elif output_format == 'collapsed':
collector = CollapsedStackCollector(interval)
elif output_format == 'pstats':
collector = PStatsCollector(interval)
elif output_format == 'gecko':
collector = GeckoCollector(interval)
else:
raise ValueError(f"Unknown output format: {output_format}")
# Replay samples through collector
count = reader.replay_samples(collector, progress_callback)
# Export to target format
collector.export(output_file)
return count

View file

@ -2,6 +2,7 @@
import argparse
import importlib.util
import locale
import os
import selectors
import socket
@ -10,11 +11,14 @@ import sys
import time
from contextlib import nullcontext
from .sample import sample, sample_live
from .errors import SamplingUnknownProcessError, SamplingModuleNotFoundError, SamplingScriptNotFoundError
from .sample import sample, sample_live, _is_process_running
from .pstats_collector import PstatsCollector
from .stack_collector import CollapsedStackCollector, FlamegraphCollector
from .heatmap_collector import HeatmapCollector
from .gecko_collector import GeckoCollector
from .binary_collector import BinaryCollector
from .binary_reader import BinaryReader
from .constants import (
PROFILING_MODE_ALL,
PROFILING_MODE_WALL,
@ -74,6 +78,7 @@ FORMAT_EXTENSIONS = {
"flamegraph": "html",
"gecko": "json",
"heatmap": "html",
"binary": "bin",
}
COLLECTOR_MAP = {
@ -82,6 +87,7 @@ COLLECTOR_MAP = {
"flamegraph": FlamegraphCollector,
"gecko": GeckoCollector,
"heatmap": HeatmapCollector,
"binary": BinaryCollector,
}
def _setup_child_monitor(args, parent_pid):
@ -179,7 +185,7 @@ def _parse_mode(mode_string):
def _check_process_died(process):
"""Check if process died and raise an error with stderr if available."""
if process.poll() is None:
return # Process still running
return
# Process died - try to get stderr for error message
stderr_msg = ""
@ -364,7 +370,7 @@ def _add_mode_options(parser):
)
def _add_format_options(parser):
def _add_format_options(parser, include_compression=True, include_binary=True):
"""Add output format options to a parser."""
output_group = parser.add_argument_group("Output options")
format_group = output_group.add_mutually_exclusive_group()
@ -403,8 +409,24 @@ def _add_format_options(parser):
dest="format",
help="Generate interactive HTML heatmap visualization with line-level sample counts",
)
if include_binary:
format_group.add_argument(
"--binary",
action="store_const",
const="binary",
dest="format",
help="Generate high-performance binary format (use 'replay' command to convert)",
)
parser.set_defaults(format="pstats")
if include_compression:
output_group.add_argument(
"--compression",
choices=["auto", "zstd", "none"],
default="auto",
help="Compression for binary format: auto (use zstd if available), zstd, none",
)
output_group.add_argument(
"-o",
"--output",
@ -459,15 +481,18 @@ def _sort_to_mode(sort_choice):
return sort_map.get(sort_choice, SORT_MODE_NSAMPLES)
def _create_collector(format_type, interval, skip_idle, opcodes=False):
def _create_collector(format_type, interval, skip_idle, opcodes=False,
output_file=None, compression='auto'):
"""Create the appropriate collector based on format type.
Args:
format_type: The output format ('pstats', 'collapsed', 'flamegraph', 'gecko', 'heatmap')
format_type: The output format ('pstats', 'collapsed', 'flamegraph', 'gecko', 'heatmap', 'binary')
interval: Sampling interval in microseconds
skip_idle: Whether to skip idle samples
opcodes: Whether to collect opcode information (only used by gecko format
for creating interval markers in Firefox Profiler)
output_file: Output file path (required for binary format)
compression: Compression type for binary format ('auto', 'zstd', 'none')
Returns:
A collector instance of the appropriate type
@ -476,6 +501,13 @@ def _create_collector(format_type, interval, skip_idle, opcodes=False):
if collector_class is None:
raise ValueError(f"Unknown format: {format_type}")
# Binary format requires output file and compression
if format_type == "binary":
if output_file is None:
raise ValueError("Binary format requires an output file")
return collector_class(output_file, interval, skip_idle=skip_idle,
compression=compression)
# Gecko format never skips idle (it needs both GIL and CPU data)
# and is the only format that uses opcodes for interval markers
if format_type == "gecko":
@ -511,7 +543,12 @@ def _handle_output(collector, args, pid, mode):
pid: Process ID (for generating filenames)
mode: Profiling mode used
"""
if args.format == "pstats":
if args.format == "binary":
# Binary format already wrote to file incrementally, just finalize
collector.export(None)
filename = collector.filename
print(f"Binary profile written to {filename} ({collector.total_samples} samples)")
elif args.format == "pstats":
if args.outfile:
# If outfile is a directory, generate filename inside it
if os.path.isdir(args.outfile):
@ -544,6 +581,10 @@ def _validate_args(args, parser):
args: Parsed command-line arguments
parser: ArgumentParser instance for error reporting
"""
# Replay command has no special validation needed
if getattr(args, 'command', None) == "replay":
return
# Check if live mode is available
if hasattr(args, 'live') and args.live and LiveStatsCollector is None:
parser.error(
@ -556,7 +597,7 @@ def _validate_args(args, parser):
parser.error("--subprocesses is incompatible with --live mode.")
# Async-aware mode is incompatible with --native, --no-gc, --mode, and --all-threads
if args.async_aware:
if getattr(args, 'async_aware', False):
issues = []
if args.native:
issues.append("--native")
@ -573,7 +614,7 @@ def _validate_args(args, parser):
)
# --async-mode requires --async-aware
if hasattr(args, 'async_mode') and args.async_mode != "running" and not args.async_aware:
if hasattr(args, 'async_mode') and args.async_mode != "running" and not getattr(args, 'async_aware', False):
parser.error("--async-mode requires --async-aware to be enabled.")
# Live mode is incompatible with format options
@ -601,7 +642,7 @@ def _validate_args(args, parser):
return
# Validate gecko mode doesn't use non-wall mode
if args.format == "gecko" and args.mode != "wall":
if args.format == "gecko" and getattr(args, 'mode', 'wall') != "wall":
parser.error(
"--mode option is incompatible with --gecko. "
"Gecko format automatically includes both GIL-holding and CPU status analysis."
@ -609,7 +650,7 @@ def _validate_args(args, parser):
# Validate --opcodes is only used with compatible formats
opcodes_compatible_formats = ("live", "gecko", "flamegraph", "heatmap")
if args.opcodes and args.format not in opcodes_compatible_formats:
if getattr(args, 'opcodes', False) and args.format not in opcodes_compatible_formats:
parser.error(
f"--opcodes is only compatible with {', '.join('--' + f for f in opcodes_compatible_formats)}."
)
@ -633,6 +674,16 @@ def _validate_args(args, parser):
def main():
"""Main entry point for the CLI."""
# Set locale for number formatting, restore on exit
old_locale = locale.setlocale(locale.LC_ALL, None)
locale.setlocale(locale.LC_ALL, "")
try:
_main()
finally:
locale.setlocale(locale.LC_ALL, old_locale)
def _main():
# Create the main parser
parser = argparse.ArgumentParser(
description=_HELP_DESCRIPTION,
@ -721,6 +772,30 @@ Examples:
_add_format_options(attach_parser)
_add_pstats_options(attach_parser)
# === REPLAY COMMAND ===
replay_parser = subparsers.add_parser(
"replay",
help="Replay a binary profile and convert to another format",
formatter_class=CustomFormatter,
description="""Replay a binary profile file and convert to another format
Examples:
# Convert binary to flamegraph
`python -m profiling.sampling replay --flamegraph -o output.html profile.bin`
# Convert binary to pstats and print to stdout
`python -m profiling.sampling replay profile.bin`
# Convert binary to gecko format
`python -m profiling.sampling replay --gecko -o profile.json profile.bin`""",
)
replay_parser.add_argument(
"input_file",
help="Binary profile file to replay",
)
_add_format_options(replay_parser, include_compression=False, include_binary=False)
_add_pstats_options(replay_parser)
# Parse arguments
args = parser.parse_args()
@ -731,6 +806,7 @@ Examples:
command_handlers = {
"run": _handle_run,
"attach": _handle_attach,
"replay": _handle_replay,
}
# Execute the appropriate command
@ -743,6 +819,8 @@ Examples:
def _handle_attach(args):
"""Handle the 'attach' command."""
if not _is_process_running(args.pid):
raise SamplingUnknownProcessError(args.pid)
# Check if live mode is requested
if args.live:
_handle_live_attach(args, args.pid)
@ -760,8 +838,16 @@ def _handle_attach(args):
mode != PROFILING_MODE_WALL if mode != PROFILING_MODE_ALL else False
)
output_file = None
if args.format == "binary":
output_file = args.outfile or _generate_output_filename(args.format, args.pid)
# Create the appropriate collector
collector = _create_collector(args.format, args.interval, skip_idle, args.opcodes)
collector = _create_collector(
args.format, args.interval, skip_idle, args.opcodes,
output_file=output_file,
compression=getattr(args, 'compression', 'auto')
)
with _get_child_monitor_context(args, args.pid):
collector = sample(
@ -792,13 +878,13 @@ def _handle_run(args):
added_cwd = True
try:
if importlib.util.find_spec(args.target) is None:
sys.exit(f"Error: Module not found: {args.target}")
raise SamplingModuleNotFoundError(args.target)
finally:
if added_cwd:
sys.path.remove(cwd)
else:
if not os.path.exists(args.target):
sys.exit(f"Error: Script not found: {args.target}")
raise SamplingScriptNotFoundError(args.target)
# Check if live mode is requested
if args.live:
@ -829,8 +915,16 @@ def _handle_run(args):
mode != PROFILING_MODE_WALL if mode != PROFILING_MODE_ALL else False
)
output_file = None
if args.format == "binary":
output_file = args.outfile or _generate_output_filename(args.format, process.pid)
# Create the appropriate collector
collector = _create_collector(args.format, args.interval, skip_idle, args.opcodes)
collector = _create_collector(
args.format, args.interval, skip_idle, args.opcodes,
output_file=output_file,
compression=getattr(args, 'compression', 'auto')
)
with _get_child_monitor_context(args, process.pid):
try:
@ -949,5 +1043,48 @@ def _handle_live_run(args):
process.wait()
def _handle_replay(args):
"""Handle the 'replay' command - convert binary profile to another format."""
import os
if not os.path.exists(args.input_file):
sys.exit(f"Error: Input file not found: {args.input_file}")
with BinaryReader(args.input_file) as reader:
info = reader.get_info()
interval = info['sample_interval_us']
print(f"Replaying {info['sample_count']} samples from {args.input_file}")
print(f" Sample interval: {interval} us")
print(f" Compression: {'zstd' if info.get('compression_type', 0) == 1 else 'none'}")
collector = _create_collector(args.format, interval, skip_idle=False)
def progress_callback(current, total):
if total > 0:
pct = current / total
bar_width = 40
filled = int(bar_width * pct)
bar = '' * filled + '' * (bar_width - filled)
print(f"\r [{bar}] {pct*100:5.1f}% ({current:,}/{total:,})", end="", flush=True)
count = reader.replay_samples(collector, progress_callback)
print()
if args.format == "pstats":
if args.outfile:
collector.export(args.outfile)
else:
sort_choice = args.sort if args.sort is not None else "nsamples"
limit = args.limit if args.limit is not None else 15
sort_mode = _sort_to_mode(sort_choice)
collector.print_stats(sort_mode, limit, not args.no_summary, PROFILING_MODE_WALL)
else:
filename = args.outfile or _generate_output_filename(args.format, os.getpid())
collector.export(filename)
print(f"Replayed {count} samples")
if __name__ == "__main__":
main()

View file

@ -44,8 +44,17 @@ def extract_lineno(location):
class Collector(ABC):
@abstractmethod
def collect(self, stack_frames):
"""Collect profiling data from stack frames."""
def collect(self, stack_frames, timestamps_us=None):
"""Collect profiling data from stack frames.
Args:
stack_frames: List of InterpreterInfo objects
timestamps_us: Optional list of timestamps in microseconds. If provided
(from binary replay with RLE batching), use these instead of current
time. If None, collectors should use time.monotonic() or similar.
The list may contain multiple timestamps when samples are batched
together (same stack, different times).
"""
def collect_failed_sample(self):
"""Collect data about a failed sample attempt."""
@ -79,6 +88,17 @@ class Collector(ABC):
# Phase 3: Build linear stacks from each leaf to root (optimized - no sorting!)
yield from self._build_linear_stacks(leaf_task_ids, task_map, child_to_parent)
def _iter_stacks(self, stack_frames, skip_idle=False):
"""Yield (frames, thread_id) for all stacks, handling both sync and async modes."""
if stack_frames and hasattr(stack_frames[0], "awaited_by"):
for frames, thread_id, _ in self._iter_async_frames(stack_frames):
if frames:
yield frames, thread_id
else:
for frames, thread_id in self._iter_all_frames(stack_frames, skip_idle=skip_idle):
if frames:
yield frames, thread_id
def _build_task_graph(self, awaited_info_list):
task_map = {}
child_to_parent = {} # Maps child_id -> (selected_parent_id, parent_count)

View file

@ -0,0 +1,19 @@
"""Custom exceptions for the sampling profiler."""
class SamplingProfilerError(Exception):
"""Base exception for sampling profiler errors."""
class SamplingUnknownProcessError(SamplingProfilerError):
def __init__(self, pid):
self.pid = pid
super().__init__(f"Process with PID '{pid}' does not exist.")
class SamplingScriptNotFoundError(SamplingProfilerError):
def __init__(self, script_path):
self.script_path = script_path
super().__init__(f"Script '{script_path}' not found.")
class SamplingModuleNotFoundError(SamplingProfilerError):
def __init__(self, module_name):
self.module_name = module_name
super().__init__(f"Module '{module_name}' not found.")

View file

@ -66,7 +66,7 @@ class GeckoCollector(Collector):
self.sample_interval_usec = sample_interval_usec
self.skip_idle = skip_idle
self.opcodes_enabled = opcodes
self.start_time = time.time() * 1000 # milliseconds since epoch
self.start_time = time.monotonic() * 1000 # milliseconds since start
# Global string table (shared across all threads)
self.global_strings = ["(root)"] # Start with root
@ -103,6 +103,9 @@ class GeckoCollector(Collector):
# Opcode state tracking per thread: tid -> (opcode, lineno, col_offset, funcname, filename, start_time)
self.opcode_state = {}
# For binary replay: track base timestamp (first sample's timestamp)
self._replay_base_timestamp_us = None
def _track_state_transition(self, tid, condition, active_dict, inactive_dict,
active_name, inactive_name, category, current_time):
"""Track binary state transitions and emit markers.
@ -138,18 +141,35 @@ class GeckoCollector(Collector):
self._add_marker(tid, active_name, active_dict.pop(tid),
current_time, category)
def collect(self, stack_frames):
"""Collect a sample from stack frames."""
current_time = (time.time() * 1000) - self.start_time
def collect(self, stack_frames, timestamps_us=None):
"""Collect samples from stack frames.
Args:
stack_frames: List of interpreter/thread frame info
timestamps_us: List of timestamps in microseconds (None for live sampling)
"""
# Handle live sampling (no timestamps provided)
if timestamps_us is None:
current_time = (time.monotonic() * 1000) - self.start_time
times = [current_time]
else:
if not timestamps_us:
return
# Initialize base timestamp if needed
if self._replay_base_timestamp_us is None:
self._replay_base_timestamp_us = timestamps_us[0]
# Convert all timestamps to times (ms relative to first sample)
base = self._replay_base_timestamp_us
times = [(ts - base) / 1000 for ts in timestamps_us]
first_time = times[0]
# Update interval calculation
if self.sample_count > 0 and self.last_sample_time > 0:
self.interval = (
current_time - self.last_sample_time
) / self.sample_count
self.last_sample_time = current_time
self.interval = (times[-1] - self.last_sample_time) / self.sample_count
self.last_sample_time = times[-1]
# Process threads and track GC per thread
# Process threads
for interpreter_info in stack_frames:
for thread_info in interpreter_info.threads:
frames = thread_info.frame_info
@ -167,92 +187,86 @@ class GeckoCollector(Collector):
on_cpu = bool(status_flags & THREAD_STATUS_ON_CPU)
gil_requested = bool(status_flags & THREAD_STATUS_GIL_REQUESTED)
# Track GIL possession (Has GIL / No GIL)
# Track state transitions using first timestamp
self._track_state_transition(
tid, has_gil, self.has_gil_start, self.no_gil_start,
"Has GIL", "No GIL", CATEGORY_GIL, current_time
"Has GIL", "No GIL", CATEGORY_GIL, first_time
)
# Track CPU state (On CPU / Off CPU)
self._track_state_transition(
tid, on_cpu, self.on_cpu_start, self.off_cpu_start,
"On CPU", "Off CPU", CATEGORY_CPU, current_time
"On CPU", "Off CPU", CATEGORY_CPU, first_time
)
# Track code type (Python Code / Native Code)
# This is tri-state: Python (has_gil), Native (on_cpu without gil), or Neither
# Track code type
if has_gil:
self._track_state_transition(
tid, True, self.python_code_start, self.native_code_start,
"Python Code", "Native Code", CATEGORY_CODE_TYPE, current_time
"Python Code", "Native Code", CATEGORY_CODE_TYPE, first_time
)
elif on_cpu:
self._track_state_transition(
tid, True, self.native_code_start, self.python_code_start,
"Native Code", "Python Code", CATEGORY_CODE_TYPE, current_time
"Native Code", "Python Code", CATEGORY_CODE_TYPE, first_time
)
else:
# Thread is idle (neither has GIL nor on CPU) - close any open code markers
# This handles the third state that _track_state_transition doesn't cover
if tid in self.initialized_threads:
if tid in self.python_code_start:
self._add_marker(tid, "Python Code", self.python_code_start.pop(tid),
current_time, CATEGORY_CODE_TYPE)
first_time, CATEGORY_CODE_TYPE)
if tid in self.native_code_start:
self._add_marker(tid, "Native Code", self.native_code_start.pop(tid),
current_time, CATEGORY_CODE_TYPE)
first_time, CATEGORY_CODE_TYPE)
# Track "Waiting for GIL" intervals (one-sided tracking)
# Track GIL wait
if gil_requested:
self.gil_wait_start.setdefault(tid, current_time)
self.gil_wait_start.setdefault(tid, first_time)
elif tid in self.gil_wait_start:
self._add_marker(tid, "Waiting for GIL", self.gil_wait_start.pop(tid),
current_time, CATEGORY_GIL)
first_time, CATEGORY_GIL)
# Track exception state (Has Exception / No Exception)
# Track exception state
has_exception = bool(status_flags & THREAD_STATUS_HAS_EXCEPTION)
self._track_state_transition(
tid, has_exception, self.exception_start, self.no_exception_start,
"Has Exception", "No Exception", CATEGORY_EXCEPTION, current_time
"Has Exception", "No Exception", CATEGORY_EXCEPTION, first_time
)
# Track GC events by detecting <GC> frames in the stack trace
# This leverages the improved GC frame tracking from commit 336366fd7ca
# which precisely identifies the thread that initiated GC collection
# Track GC events
has_gc_frame = any(frame[2] == "<GC>" for frame in frames)
if has_gc_frame:
# This thread initiated GC collection
if tid not in self.gc_start_per_thread:
self.gc_start_per_thread[tid] = current_time
self.gc_start_per_thread[tid] = first_time
elif tid in self.gc_start_per_thread:
# End GC marker when no more GC frames are detected
self._add_marker(tid, "GC Collecting", self.gc_start_per_thread.pop(tid),
current_time, CATEGORY_GC)
first_time, CATEGORY_GC)
# Mark thread as initialized after processing all state transitions
# Mark thread as initialized
self.initialized_threads.add(tid)
# Categorize: idle if neither has GIL nor on CPU
# Skip idle threads if requested
is_idle = not has_gil and not on_cpu
# Skip idle threads if skip_idle is enabled
if self.skip_idle and is_idle:
continue
if not frames:
continue
# Process the stack
# Process stack once to get stack_index
stack_index = self._process_stack(thread_data, frames)
# Add sample - cache references to avoid dictionary lookups
# Add samples with timestamps
samples = thread_data["samples"]
samples["stack"].append(stack_index)
samples["time"].append(current_time)
samples["eventDelay"].append(None)
samples_stack = samples["stack"]
samples_time = samples["time"]
samples_delay = samples["eventDelay"]
# Track opcode state changes for interval markers (leaf frame only)
if self.opcodes_enabled:
for t in times:
samples_stack.append(stack_index)
samples_time.append(t)
samples_delay.append(None)
# Handle opcodes
if self.opcodes_enabled and frames:
leaf_frame = frames[0]
filename, location, funcname, opcode = leaf_frame
if isinstance(location, tuple):
@ -264,18 +278,15 @@ class GeckoCollector(Collector):
current_state = (opcode, lineno, col_offset, funcname, filename)
if tid not in self.opcode_state:
# First observation - start tracking
self.opcode_state[tid] = (*current_state, current_time)
self.opcode_state[tid] = (*current_state, first_time)
elif self.opcode_state[tid][:5] != current_state:
# State changed - emit marker for previous state
prev_opcode, prev_lineno, prev_col, prev_funcname, prev_filename, prev_start = self.opcode_state[tid]
self._add_opcode_interval_marker(
tid, prev_opcode, prev_lineno, prev_col, prev_funcname, prev_start, current_time
tid, prev_opcode, prev_lineno, prev_col, prev_funcname, prev_start, first_time
)
# Start tracking new state
self.opcode_state[tid] = (*current_state, current_time)
self.opcode_state[tid] = (*current_state, first_time)
self.sample_count += 1
self.sample_count += len(times)
def _create_thread(self, tid):
"""Create a new thread structure with processed profile format."""

View file

@ -5,6 +5,7 @@ import collections
import html
import importlib.resources
import json
import locale
import math
import os
import platform
@ -15,6 +16,7 @@ from pathlib import Path
from typing import Dict, List, Tuple
from ._css_utils import get_combined_css
from ._format_utils import fmt
from .collector import normalize_location, extract_lineno
from .stack_collector import StackTraceCollector
@ -343,7 +345,7 @@ class _HtmlRenderer:
<div class="type-header" onclick="toggleTypeSection(this)">
<span class="type-icon">{icon}</span>
<span class="type-title">{type_names[module_type]}</span>
<span class="type-stats">({tree.count} {file_word}, {tree.samples:,} {sample_word})</span>
<span class="type-stats">({tree.count} {file_word}, {tree.samples:n} {sample_word})</span>
</div>
<div class="type-content"{content_style}>
'''
@ -390,7 +392,7 @@ class _HtmlRenderer:
parts.append(f'{indent} <span class="folder-icon">▶</span>')
parts.append(f'{indent} <span class="folder-name">📁 {html.escape(name)}</span>')
parts.append(f'{indent} <span class="folder-stats">'
f'({node.count} {file_word}, {node.samples:,} {sample_word})</span>')
f'({node.count} {file_word}, {node.samples:n} {sample_word})</span>')
parts.append(f'{indent} </div>')
parts.append(f'{indent} <div class="folder-content" style="display: none;">')
@ -431,10 +433,11 @@ class _HtmlRenderer:
bar_width = min(stat.percentage, 100)
html_file = self.file_index[stat.filename]
s = "" if stat.total_samples == 1 else "s"
return (f'{indent}<div class="file-item">\n'
f'{indent} <a href="{html_file}" class="file-link" title="{full_path}">📄 {module_name}</a>\n'
f'{indent} <span class="file-samples">{stat.total_samples:,} samples</span>\n'
f'{indent} <span class="file-samples">{stat.total_samples:n} sample{s}</span>\n'
f'{indent} <div class="heatmap-bar-container"><div class="heatmap-bar" style="width: {bar_width}px; height: {self.heatmap_bar_height}px;" data-intensity="{intensity:.3f}"></div></div>\n'
f'{indent}</div>\n')
@ -518,7 +521,7 @@ class HeatmapCollector(StackTraceCollector):
}
self.stats.update(kwargs)
def process_frames(self, frames, thread_id):
def process_frames(self, frames, thread_id, weight=1):
"""Process stack frames and count samples per line.
Args:
@ -526,8 +529,9 @@ class HeatmapCollector(StackTraceCollector):
leaf-to-root order. location is (lineno, end_lineno, col_offset, end_col_offset).
opcode is None if not gathered.
thread_id: Thread ID for this stack trace
weight: Number of samples this stack represents (for batched RLE)
"""
self._total_samples += 1
self._total_samples += weight
self._seen_lines.clear()
for i, (filename, location, funcname, opcode) in enumerate(frames):
@ -545,15 +549,16 @@ class HeatmapCollector(StackTraceCollector):
self._seen_lines.add(line_key)
self._record_line_sample(filename, lineno, funcname, is_leaf=is_leaf,
count_cumulative=count_cumulative)
count_cumulative=count_cumulative, weight=weight)
if opcode is not None:
# Set opcodes_enabled flag when we first encounter opcode data
self.opcodes_enabled = True
self._record_bytecode_sample(filename, lineno, opcode,
end_lineno, col_offset, end_col_offset)
end_lineno, col_offset, end_col_offset,
weight=weight)
# Build call graph for adjacent frames
# Build call graph for adjacent frames (relationships are deduplicated anyway)
if i + 1 < len(frames):
next_frame = frames[i + 1]
next_lineno = extract_lineno(next_frame[1])
@ -575,24 +580,25 @@ class HeatmapCollector(StackTraceCollector):
return True
def _record_line_sample(self, filename, lineno, funcname, is_leaf=False,
count_cumulative=True):
count_cumulative=True, weight=1):
"""Record a sample for a specific line."""
# Track cumulative samples (all occurrences in stack)
if count_cumulative:
self.line_samples[(filename, lineno)] += 1
self.file_samples[filename][lineno] += 1
self.line_samples[(filename, lineno)] += weight
self.file_samples[filename][lineno] += weight
# Track self/leaf samples (only when at top of stack)
if is_leaf:
self.line_self_samples[(filename, lineno)] += 1
self.file_self_samples[filename][lineno] += 1
self.line_self_samples[(filename, lineno)] += weight
self.file_self_samples[filename][lineno] += weight
# Record function definition location
if funcname and (filename, funcname) not in self.function_definitions:
self.function_definitions[(filename, funcname)] = lineno
def _record_bytecode_sample(self, filename, lineno, opcode,
end_lineno=None, col_offset=None, end_col_offset=None):
end_lineno=None, col_offset=None, end_col_offset=None,
weight=1):
"""Record a sample for a specific bytecode instruction.
Args:
@ -602,6 +608,7 @@ class HeatmapCollector(StackTraceCollector):
end_lineno: End line number (may be -1 if not available)
col_offset: Column offset in UTF-8 bytes (may be -1 if not available)
end_col_offset: End column offset in UTF-8 bytes (may be -1 if not available)
weight: Number of samples this represents (for batched RLE)
"""
key = (filename, lineno)
@ -609,7 +616,7 @@ class HeatmapCollector(StackTraceCollector):
if opcode not in self.line_opcodes[key]:
self.line_opcodes[key][opcode] = {'count': 0, 'locations': set()}
self.line_opcodes[key][opcode]['count'] += 1
self.line_opcodes[key][opcode]['count'] += weight
# Store unique location info if column offset is available (not -1)
if col_offset is not None and col_offset >= 0:
@ -761,7 +768,8 @@ class HeatmapCollector(StackTraceCollector):
"""Print summary of exported heatmap."""
print(f"Heatmap output written to {output_dir}/")
print(f" - Index: {output_dir / 'index.html'}")
print(f" - {len(file_stats)} source file(s) analyzed")
s = "" if len(file_stats) == 1 else "s"
print(f" - {len(file_stats)} source file{s} analyzed")
def _calculate_file_stats(self) -> List[FileStats]:
"""Calculate statistics for each file.
@ -824,7 +832,7 @@ class HeatmapCollector(StackTraceCollector):
# Format error rate and missed samples with bar classes
error_rate = self.stats.get('error_rate')
if error_rate is not None:
error_rate_str = f"{error_rate:.1f}%"
error_rate_str = f"{fmt(error_rate)}%"
error_rate_width = min(error_rate, 100)
# Determine bar color class based on rate
if error_rate < 5:
@ -840,7 +848,7 @@ class HeatmapCollector(StackTraceCollector):
missed_samples = self.stats.get('missed_samples')
if missed_samples is not None:
missed_samples_str = f"{missed_samples:.1f}%"
missed_samples_str = f"{fmt(missed_samples)}%"
missed_samples_width = min(missed_samples, 100)
if missed_samples < 5:
missed_samples_class = "good"
@ -859,10 +867,10 @@ class HeatmapCollector(StackTraceCollector):
"<!-- INLINE_JS -->": f"<script>\n{self._template_loader.index_js}\n</script>",
"<!-- PYTHON_LOGO -->": self._template_loader.logo_html,
"<!-- PYTHON_VERSION -->": f"{sys.version_info.major}.{sys.version_info.minor}",
"<!-- NUM_FILES -->": str(len(file_stats)),
"<!-- TOTAL_SAMPLES -->": f"{self._total_samples:,}",
"<!-- DURATION -->": f"{self.stats.get('duration_sec', 0):.1f}s",
"<!-- SAMPLE_RATE -->": f"{self.stats.get('sample_rate', 0):.1f}",
"<!-- NUM_FILES -->": f"{len(file_stats):n}",
"<!-- TOTAL_SAMPLES -->": f"{self._total_samples:n}",
"<!-- DURATION -->": fmt(self.stats.get('duration_sec', 0)),
"<!-- SAMPLE_RATE -->": fmt(self.stats.get('sample_rate', 0)),
"<!-- ERROR_RATE -->": error_rate_str,
"<!-- ERROR_RATE_WIDTH -->": str(error_rate_width),
"<!-- ERROR_RATE_CLASS -->": error_rate_class,
@ -906,12 +914,12 @@ class HeatmapCollector(StackTraceCollector):
# Populate template
replacements = {
"<!-- FILENAME -->": html.escape(filename),
"<!-- TOTAL_SAMPLES -->": f"{file_stat.total_samples:,}",
"<!-- TOTAL_SELF_SAMPLES -->": f"{file_stat.total_self_samples:,}",
"<!-- NUM_LINES -->": str(file_stat.num_lines),
"<!-- PERCENTAGE -->": f"{file_stat.percentage:.2f}",
"<!-- MAX_SAMPLES -->": str(file_stat.max_samples),
"<!-- MAX_SELF_SAMPLES -->": str(file_stat.max_self_samples),
"<!-- TOTAL_SAMPLES -->": f"{file_stat.total_samples:n}",
"<!-- TOTAL_SELF_SAMPLES -->": f"{file_stat.total_self_samples:n}",
"<!-- NUM_LINES -->": f"{file_stat.num_lines:n}",
"<!-- PERCENTAGE -->": fmt(file_stat.percentage, 2),
"<!-- MAX_SAMPLES -->": f"{file_stat.max_samples:n}",
"<!-- MAX_SELF_SAMPLES -->": f"{file_stat.max_self_samples:n}",
"<!-- CODE_LINES -->": ''.join(code_lines_html),
"<!-- INLINE_CSS -->": f"<style>\n{self._template_loader.file_css}\n</style>",
"<!-- INLINE_JS -->": f"<script>\n{self._template_loader.file_js}\n</script>",
@ -948,9 +956,9 @@ class HeatmapCollector(StackTraceCollector):
else:
self_intensity = 0
self_display = f"{self_samples:,}" if self_samples > 0 else ""
cumulative_display = f"{cumulative_samples:,}"
tooltip = f"Self: {self_samples:,}, Total: {cumulative_samples:,}"
self_display = f"{self_samples:n}" if self_samples > 0 else ""
cumulative_display = f"{cumulative_samples:n}"
tooltip = f"Self: {self_samples:n}, Total: {cumulative_samples:n}"
else:
cumulative_intensity = 0
self_intensity = 0
@ -978,7 +986,17 @@ class HeatmapCollector(StackTraceCollector):
f'data-spec-pct="{spec_pct}" '
f'onclick="toggleBytecode(this)" title="Show bytecode">&#9654;</button>'
)
bytecode_panel_html = f' <div class="bytecode-panel" id="bytecode-{line_num}" style="display:none;"></div>\n'
# Wrapper contains columns + content panel
bytecode_panel_html = (
f' <div class="bytecode-wrapper" id="bytecode-wrapper-{line_num}">\n'
f' <div class="bytecode-columns">'
f'<div class="line-number"></div>'
f'<div class="line-samples-self"></div>'
f'<div class="line-samples-cumulative"></div>'
f'</div>\n'
f' <div class="bytecode-panel" id="bytecode-{line_num}"></div>\n'
f' </div>\n'
)
elif self.opcodes_enabled:
# Add invisible spacer to maintain consistent indentation when opcodes are enabled
bytecode_btn_html = '<div class="bytecode-spacer"></div>'
@ -1195,7 +1213,7 @@ class HeatmapCollector(StackTraceCollector):
file, line, func, count = valid_items[0]
target_html = self.file_index[file]
nav_data = json.dumps({'link': f"{target_html}#line-{line}", 'func': func})
title = f"Go to {btn_class}: {html.escape(func)} ({count:,} samples)"
title = f"Go to {btn_class}: {html.escape(func)} ({count:n} samples)"
return f'<button class="nav-btn {btn_class}" data-nav=\'{html.escape(nav_data)}\' title="{title}">{arrow}</button>'
# Multiple items - create menu
@ -1210,5 +1228,5 @@ class HeatmapCollector(StackTraceCollector):
for file, line, func, count in valid_items
]
items_json = html.escape(json.dumps(items_data))
title = f"{len(items_data)} {btn_class}s ({total_samples:,} samples)"
title = f"{len(items_data)} {btn_class}s ({total_samples:n} samples)"
return f'<button class="nav-btn {btn_class}" data-nav-multi=\'{items_json}\' title="{title}">{arrow}</button>'

View file

@ -348,7 +348,7 @@ class LiveStatsCollector(Collector):
self.failed_samples += 1
self.total_samples += 1
def collect(self, stack_frames):
def collect(self, stack_frames, timestamp_us=None):
"""Collect and display profiling data."""
if self.start_time is None:
self.start_time = time.perf_counter()

View file

@ -18,7 +18,7 @@ class PstatsCollector(Collector):
self.skip_idle = skip_idle
self._seen_locations = set()
def _process_frames(self, frames):
def _process_frames(self, frames, weight=1):
"""Process a single thread's frame stack."""
if not frames:
return
@ -32,12 +32,12 @@ class PstatsCollector(Collector):
location = (frame.filename, lineno, frame.funcname)
if location not in self._seen_locations:
self._seen_locations.add(location)
self.result[location]["cumulative_calls"] += 1
self.result[location]["cumulative_calls"] += weight
# The top frame gets counted as an inline call (directly executing)
top_lineno = extract_lineno(frames[0].location)
top_location = (frames[0].filename, top_lineno, frames[0].funcname)
self.result[top_location]["direct_calls"] += 1
self.result[top_location]["direct_calls"] += weight
# Track caller-callee relationships for call graph
for i in range(1, len(frames)):
@ -49,17 +49,12 @@ class PstatsCollector(Collector):
callee = (callee_frame.filename, callee_lineno, callee_frame.funcname)
caller = (caller_frame.filename, caller_lineno, caller_frame.funcname)
self.callers[callee][caller] += 1
self.callers[callee][caller] += weight
def collect(self, stack_frames):
if stack_frames and hasattr(stack_frames[0], "awaited_by"):
# Async frame processing
for frames, thread_id, task_id in self._iter_async_frames(stack_frames):
self._process_frames(frames)
else:
# Regular frame processing
for frames, thread_id in self._iter_all_frames(stack_frames, skip_idle=self.skip_idle):
self._process_frames(frames)
def collect(self, stack_frames, timestamps_us=None):
weight = len(timestamps_us) if timestamps_us else 1
for frames, _ in self._iter_stacks(stack_frames, skip_idle=self.skip_idle):
self._process_frames(frames, weight=weight)
def export(self, filename):
self.create_stats()

View file

@ -1,6 +1,5 @@
import _remote_debugging
import os
import pstats
import statistics
import sys
import sysconfig
@ -8,10 +7,7 @@ import time
from collections import deque
from _colorize import ANSIColors
from .pstats_collector import PstatsCollector
from .stack_collector import CollapsedStackCollector, FlamegraphCollector
from .heatmap_collector import HeatmapCollector
from .gecko_collector import GeckoCollector
from .binary_collector import BinaryCollector
from .constants import (
PROFILING_MODE_WALL,
PROFILING_MODE_CPU,
@ -19,6 +15,7 @@ from .constants import (
PROFILING_MODE_ALL,
PROFILING_MODE_EXCEPTION,
)
from ._format_utils import fmt
try:
from .live_collector import LiveStatsCollector
except ImportError:
@ -34,24 +31,30 @@ class SampleProfiler:
self.all_threads = all_threads
self.mode = mode # Store mode for later use
self.collect_stats = collect_stats
if _FREE_THREADED_BUILD:
self.unwinder = _remote_debugging.RemoteUnwinder(
self.pid, all_threads=self.all_threads, mode=mode, native=native, gc=gc,
opcodes=opcodes, skip_non_matching_threads=skip_non_matching_threads,
cache_frames=True, stats=collect_stats
)
else:
only_active_threads = bool(self.all_threads)
self.unwinder = _remote_debugging.RemoteUnwinder(
self.pid, only_active_thread=only_active_threads, mode=mode, native=native, gc=gc,
opcodes=opcodes, skip_non_matching_threads=skip_non_matching_threads,
cache_frames=True, stats=collect_stats
)
try:
self.unwinder = self._new_unwinder(native, gc, opcodes, skip_non_matching_threads)
except RuntimeError as err:
raise SystemExit(err) from err
# Track sample intervals and total sample count
self.sample_intervals = deque(maxlen=100)
self.total_samples = 0
self.realtime_stats = False
def _new_unwinder(self, native, gc, opcodes, skip_non_matching_threads):
if _FREE_THREADED_BUILD:
unwinder = _remote_debugging.RemoteUnwinder(
self.pid, all_threads=self.all_threads, mode=self.mode, native=native, gc=gc,
opcodes=opcodes, skip_non_matching_threads=skip_non_matching_threads,
cache_frames=True, stats=self.collect_stats
)
else:
unwinder = _remote_debugging.RemoteUnwinder(
self.pid, only_active_thread=bool(self.all_threads), mode=self.mode, native=native, gc=gc,
opcodes=opcodes, skip_non_matching_threads=skip_non_matching_threads,
cache_frames=True, stats=self.collect_stats
)
return unwinder
def sample(self, collector, duration_sec=10, *, async_aware=False):
sample_interval_sec = self.sample_interval_usec / 1_000_000
running_time = 0
@ -86,7 +89,7 @@ class SampleProfiler:
collector.collect_failed_sample()
errors += 1
except Exception as e:
if not self._is_process_running():
if not _is_process_running(self.pid):
break
raise e from None
@ -129,14 +132,17 @@ class SampleProfiler:
# Don't print stats for live mode (curses is handling display)
is_live_mode = LiveStatsCollector is not None and isinstance(collector, LiveStatsCollector)
if not is_live_mode:
print(f"Captured {num_samples} samples in {running_time:.2f} seconds")
print(f"Sample rate: {sample_rate:.2f} samples/sec")
print(f"Error rate: {error_rate:.2f}%")
print(f"Captured {num_samples:n} samples in {fmt(running_time, 2)} seconds")
print(f"Sample rate: {fmt(sample_rate, 2)} samples/sec")
print(f"Error rate: {fmt(error_rate, 2)}")
# Print unwinder stats if stats collection is enabled
if self.collect_stats:
self._print_unwinder_stats()
if isinstance(collector, BinaryCollector):
self._print_binary_stats(collector)
# Pass stats to flamegraph collector if it's the right type
if hasattr(collector, 'set_stats'):
collector.set_stats(self.sample_interval_usec, running_time, sample_rate, error_rate, missed_samples, mode=self.mode)
@ -145,25 +151,9 @@ class SampleProfiler:
print(
f"Warning: missed {expected_samples - num_samples} samples "
f"from the expected total of {expected_samples} "
f"({(expected_samples - num_samples) / expected_samples * 100:.2f}%)"
f"({fmt((expected_samples - num_samples) / expected_samples * 100, 2)}%)"
)
def _is_process_running(self):
if sys.platform == "linux" or sys.platform == "darwin":
try:
os.kill(self.pid, 0)
return True
except ProcessLookupError:
return False
elif sys.platform == "win32":
try:
_remote_debugging.RemoteUnwinder(self.pid)
except Exception:
return False
return True
else:
raise ValueError(f"Unsupported platform: {sys.platform}")
def _print_realtime_stats(self):
"""Print real-time sampling statistics."""
if len(self.sample_intervals) < 2:
@ -195,16 +185,16 @@ class SampleProfiler:
total = hits + partial + misses
if total > 0:
hit_pct = (hits + partial) / total * 100
cache_stats_str = f" {ANSIColors.MAGENTA}Cache: {hit_pct:.1f}% ({hits}+{partial}/{misses}){ANSIColors.RESET}"
cache_stats_str = f" {ANSIColors.MAGENTA}Cache: {fmt(hit_pct)}% ({hits}+{partial}/{misses}){ANSIColors.RESET}"
except RuntimeError:
pass
# Clear line and print stats
print(
f"\r\033[K{ANSIColors.BOLD_BLUE}Stats:{ANSIColors.RESET} "
f"{ANSIColors.YELLOW}{mean_hz:.1f}Hz ({mean_us_per_sample:.1f}µs){ANSIColors.RESET} "
f"{ANSIColors.GREEN}Min: {min_hz:.1f}Hz{ANSIColors.RESET} "
f"{ANSIColors.RED}Max: {max_hz:.1f}Hz{ANSIColors.RESET} "
f"{ANSIColors.YELLOW}{fmt(mean_hz)}Hz ({fmt(mean_us_per_sample)}µs){ANSIColors.RESET} "
f"{ANSIColors.GREEN}Min: {fmt(min_hz)}Hz{ANSIColors.RESET} "
f"{ANSIColors.RED}Max: {fmt(max_hz)}Hz{ANSIColors.RESET} "
f"{ANSIColors.CYAN}N={self.total_samples}{ANSIColors.RESET}"
f"{cache_stats_str}",
end="",
@ -234,10 +224,10 @@ class SampleProfiler:
misses_pct = (frame_cache_misses / total_lookups * 100) if total_lookups > 0 else 0
print(f" {ANSIColors.CYAN}Frame Cache:{ANSIColors.RESET}")
print(f" Total samples: {total_samples:,}")
print(f" Full hits: {frame_cache_hits:,} ({ANSIColors.GREEN}{hits_pct:.1f}%{ANSIColors.RESET})")
print(f" Partial hits: {frame_cache_partial_hits:,} ({ANSIColors.YELLOW}{partial_pct:.1f}%{ANSIColors.RESET})")
print(f" Misses: {frame_cache_misses:,} ({ANSIColors.RED}{misses_pct:.1f}%{ANSIColors.RESET})")
print(f" Total samples: {total_samples:n}")
print(f" Full hits: {frame_cache_hits:n} ({ANSIColors.GREEN}{fmt(hits_pct)}%{ANSIColors.RESET})")
print(f" Partial hits: {frame_cache_partial_hits:n} ({ANSIColors.YELLOW}{fmt(partial_pct)}%{ANSIColors.RESET})")
print(f" Misses: {frame_cache_misses:n} ({ANSIColors.RED}{fmt(misses_pct)}%{ANSIColors.RESET})")
# Frame read stats
frames_from_cache = stats.get('frames_read_from_cache', 0)
@ -247,8 +237,8 @@ class SampleProfiler:
memory_frame_pct = (frames_from_memory / total_frames * 100) if total_frames > 0 else 0
print(f" {ANSIColors.CYAN}Frame Reads:{ANSIColors.RESET}")
print(f" From cache: {frames_from_cache:,} ({ANSIColors.GREEN}{cache_frame_pct:.1f}%{ANSIColors.RESET})")
print(f" From memory: {frames_from_memory:,} ({ANSIColors.RED}{memory_frame_pct:.1f}%{ANSIColors.RESET})")
print(f" From cache: {frames_from_cache:n} ({ANSIColors.GREEN}{fmt(cache_frame_pct)}%{ANSIColors.RESET})")
print(f" From memory: {frames_from_memory:n} ({ANSIColors.RED}{fmt(memory_frame_pct)}%{ANSIColors.RESET})")
# Code object cache stats
code_hits = stats.get('code_object_cache_hits', 0)
@ -258,26 +248,95 @@ class SampleProfiler:
code_misses_pct = (code_misses / total_code * 100) if total_code > 0 else 0
print(f" {ANSIColors.CYAN}Code Object Cache:{ANSIColors.RESET}")
print(f" Hits: {code_hits:,} ({ANSIColors.GREEN}{code_hits_pct:.1f}%{ANSIColors.RESET})")
print(f" Misses: {code_misses:,} ({ANSIColors.RED}{code_misses_pct:.1f}%{ANSIColors.RESET})")
print(f" Hits: {code_hits:n} ({ANSIColors.GREEN}{fmt(code_hits_pct)}%{ANSIColors.RESET})")
print(f" Misses: {code_misses:n} ({ANSIColors.RED}{fmt(code_misses_pct)}%{ANSIColors.RESET})")
# Memory operations
memory_reads = stats.get('memory_reads', 0)
memory_bytes = stats.get('memory_bytes_read', 0)
if memory_bytes >= 1024 * 1024:
memory_str = f"{memory_bytes / (1024 * 1024):.1f} MB"
memory_str = f"{fmt(memory_bytes / (1024 * 1024))} MB"
elif memory_bytes >= 1024:
memory_str = f"{memory_bytes / 1024:.1f} KB"
memory_str = f"{fmt(memory_bytes / 1024)} KB"
else:
memory_str = f"{memory_bytes} B"
print(f" {ANSIColors.CYAN}Memory:{ANSIColors.RESET}")
print(f" Read operations: {memory_reads:,} ({memory_str})")
print(f" Read operations: {memory_reads:n} ({memory_str})")
# Stale invalidations
stale_invalidations = stats.get('stale_cache_invalidations', 0)
if stale_invalidations > 0:
print(f" {ANSIColors.YELLOW}Stale cache invalidations: {stale_invalidations}{ANSIColors.RESET}")
def _print_binary_stats(self, collector):
"""Print binary I/O encoding statistics."""
try:
stats = collector.get_stats()
except (ValueError, RuntimeError):
return # Collector closed or stats unavailable
print(f" {ANSIColors.CYAN}Binary Encoding:{ANSIColors.RESET}")
repeat_records = stats.get('repeat_records', 0)
repeat_samples = stats.get('repeat_samples', 0)
full_records = stats.get('full_records', 0)
suffix_records = stats.get('suffix_records', 0)
pop_push_records = stats.get('pop_push_records', 0)
total_records = stats.get('total_records', 0)
if total_records > 0:
repeat_pct = repeat_records / total_records * 100
full_pct = full_records / total_records * 100
suffix_pct = suffix_records / total_records * 100
pop_push_pct = pop_push_records / total_records * 100
else:
repeat_pct = full_pct = suffix_pct = pop_push_pct = 0
print(f" Records: {total_records:,}")
print(f" RLE repeat: {repeat_records:,} ({ANSIColors.GREEN}{repeat_pct:.1f}%{ANSIColors.RESET}) [{repeat_samples:,} samples]")
print(f" Full stack: {full_records:,} ({full_pct:.1f}%)")
print(f" Suffix match: {suffix_records:,} ({suffix_pct:.1f}%)")
print(f" Pop-push: {pop_push_records:,} ({pop_push_pct:.1f}%)")
frames_written = stats.get('total_frames_written', 0)
frames_saved = stats.get('frames_saved', 0)
compression_pct = stats.get('frame_compression_pct', 0)
print(f" {ANSIColors.CYAN}Frame Efficiency:{ANSIColors.RESET}")
print(f" Frames written: {frames_written:,}")
print(f" Frames saved: {frames_saved:,} ({ANSIColors.GREEN}{compression_pct:.1f}%{ANSIColors.RESET})")
bytes_written = stats.get('bytes_written', 0)
if bytes_written >= 1024 * 1024:
bytes_str = f"{bytes_written / (1024 * 1024):.1f} MB"
elif bytes_written >= 1024:
bytes_str = f"{bytes_written / 1024:.1f} KB"
else:
bytes_str = f"{bytes_written} B"
print(f" Bytes (pre-zstd): {bytes_str}")
def _is_process_running(pid):
if pid <= 0:
return False
if os.name == "posix":
try:
os.kill(pid, 0)
return True
except ProcessLookupError:
return False
except PermissionError:
# EPERM means process exists but we can't signal it
return True
elif sys.platform == "win32":
try:
_remote_debugging.RemoteUnwinder(pid)
except Exception:
return False
return True
else:
raise ValueError(f"Unsupported platform: {sys.platform}")
def sample(
pid,

View file

@ -18,21 +18,12 @@ class StackTraceCollector(Collector):
self.sample_interval_usec = sample_interval_usec
self.skip_idle = skip_idle
def collect(self, stack_frames, skip_idle=False):
if stack_frames and hasattr(stack_frames[0], "awaited_by"):
# Async-aware mode: process async task frames
for frames, thread_id, task_id in self._iter_async_frames(stack_frames):
if not frames:
continue
self.process_frames(frames, thread_id)
else:
# Sync-only mode
for frames, thread_id in self._iter_all_frames(stack_frames, skip_idle=skip_idle):
if not frames:
continue
self.process_frames(frames, thread_id)
def collect(self, stack_frames, timestamps_us=None, skip_idle=False):
weight = len(timestamps_us) if timestamps_us else 1
for frames, thread_id in self._iter_stacks(stack_frames, skip_idle=skip_idle):
self.process_frames(frames, thread_id, weight=weight)
def process_frames(self, frames, thread_id):
def process_frames(self, frames, thread_id, weight=1):
pass
@ -41,13 +32,13 @@ class CollapsedStackCollector(StackTraceCollector):
super().__init__(*args, **kwargs)
self.stack_counter = collections.Counter()
def process_frames(self, frames, thread_id):
def process_frames(self, frames, thread_id, weight=1):
# Extract only (filename, lineno, funcname) - opcode not needed for collapsed stacks
# frame is (filename, location, funcname, opcode)
call_tree = tuple(
(f[0], extract_lineno(f[1]), f[2]) for f in reversed(frames)
)
self.stack_counter[(call_tree, thread_id)] += 1
self.stack_counter[(call_tree, thread_id)] += weight
def export(self, filename):
lines = []
@ -96,23 +87,26 @@ class FlamegraphCollector(StackTraceCollector):
# Per-thread statistics
self.per_thread_stats = {} # {thread_id: {has_gil, on_cpu, gil_requested, unknown, has_exception, total, gc_samples}}
def collect(self, stack_frames, skip_idle=False):
def collect(self, stack_frames, timestamps_us=None, skip_idle=False):
"""Override to track thread status statistics before processing frames."""
# Increment sample count once per sample
self._sample_count += 1
# Weight is number of timestamps (samples with identical stack)
weight = len(timestamps_us) if timestamps_us else 1
# Increment sample count by weight
self._sample_count += weight
# Collect both aggregate and per-thread statistics using base method
status_counts, has_gc_frame, per_thread_stats = self._collect_thread_status_stats(stack_frames)
# Merge aggregate status counts
# Merge aggregate status counts (multiply by weight)
for key in status_counts:
self.thread_status_counts[key] += status_counts[key]
self.thread_status_counts[key] += status_counts[key] * weight
# Update aggregate GC frame count
if has_gc_frame:
self.samples_with_gc_frames += 1
self.samples_with_gc_frames += weight
# Merge per-thread statistics
# Merge per-thread statistics (multiply by weight)
for thread_id, stats in per_thread_stats.items():
if thread_id not in self.per_thread_stats:
self.per_thread_stats[thread_id] = {
@ -125,10 +119,10 @@ class FlamegraphCollector(StackTraceCollector):
"gc_samples": 0,
}
for key, value in stats.items():
self.per_thread_stats[thread_id][key] += value
self.per_thread_stats[thread_id][key] += value * weight
# Call parent collect to process frames
super().collect(stack_frames, skip_idle=skip_idle)
super().collect(stack_frames, timestamps_us, skip_idle=skip_idle)
def set_stats(self, sample_interval_usec, duration_sec, sample_rate,
error_rate=None, missed_samples=None, mode=None):
@ -311,7 +305,7 @@ class FlamegraphCollector(StackTraceCollector):
"opcode_mapping": opcode_mapping
}
def process_frames(self, frames, thread_id):
def process_frames(self, frames, thread_id, weight=1):
"""Process stack frames into flamegraph tree structure.
Args:
@ -319,10 +313,11 @@ class FlamegraphCollector(StackTraceCollector):
leaf-to-root order. location is (lineno, end_lineno, col_offset, end_col_offset).
opcode is None if not gathered.
thread_id: Thread ID for this stack trace
weight: Number of samples this stack represents (for batched RLE)
"""
# Reverse to root->leaf order for tree building
self._root["samples"] += 1
self._total_samples += 1
self._root["samples"] += weight
self._total_samples += weight
self._root["threads"].add(thread_id)
self._all_threads.add(thread_id)
@ -336,11 +331,11 @@ class FlamegraphCollector(StackTraceCollector):
if node is None:
node = {"samples": 0, "children": {}, "threads": set(), "opcodes": collections.Counter()}
current["children"][func] = node
node["samples"] += 1
node["samples"] += weight
node["threads"].add(thread_id)
if opcode is not None:
node["opcodes"][opcode] += 1
node["opcodes"][opcode] += weight
current = node

View file

@ -483,10 +483,20 @@ class Doc:
if (self._is_stdlib_module(object, basedir) and
object.__name__ not in ('xml.etree', 'test.test_pydoc.pydoc_mod')):
if docloc.startswith(("http://", "https://")):
docloc = "{}/{}.html".format(docloc.rstrip("/"), object.__name__.lower())
try:
from pydoc_data import module_docs
except ImportError:
module_docs = None
if module_docs and object.__name__ in module_docs.module_docs:
doc_name = module_docs.module_docs[object.__name__]
if docloc.startswith(("http://", "https://")):
docloc = "{}/{}".format(docloc.rstrip("/"), doc_name)
else:
docloc = os.path.join(docloc, doc_name)
else:
docloc = os.path.join(docloc, object.__name__.lower() + ".html")
docloc = None
else:
docloc = None
return docloc

321
Lib/pydoc_data/module_docs.py generated Normal file
View file

@ -0,0 +1,321 @@
# Autogenerated by Sphinx on Sun Oct 12 12:02:22 2025
# as part of the release process.
module_docs = {
'__future__': '__future__#module-__future__',
'__main__': '__main__#module-__main__',
'_thread': '_thread#module-_thread',
'_tkinter': 'tkinter#module-_tkinter',
'abc': 'abc#module-abc',
'aifc': 'aifc#module-aifc',
'annotationlib': 'annotationlib#module-annotationlib',
'argparse': 'argparse#module-argparse',
'array': 'array#module-array',
'ast': 'ast#module-ast',
'asynchat': 'asynchat#module-asynchat',
'asyncio': 'asyncio#module-asyncio',
'asyncore': 'asyncore#module-asyncore',
'atexit': 'atexit#module-atexit',
'audioop': 'audioop#module-audioop',
'base64': 'base64#module-base64',
'bdb': 'bdb#module-bdb',
'binascii': 'binascii#module-binascii',
'bisect': 'bisect#module-bisect',
'builtins': 'builtins#module-builtins',
'bz2': 'bz2#module-bz2',
'cProfile': 'profile#module-cProfile',
'calendar': 'calendar#module-calendar',
'cgi': 'cgi#module-cgi',
'cgitb': 'cgitb#module-cgitb',
'chunk': 'chunk#module-chunk',
'cmath': 'cmath#module-cmath',
'cmd': 'cmd#module-cmd',
'code': 'code#module-code',
'codecs': 'codecs#module-codecs',
'codeop': 'codeop#module-codeop',
'collections': 'collections#module-collections',
'collections.abc': 'collections.abc#module-collections.abc',
'colorsys': 'colorsys#module-colorsys',
'compileall': 'compileall#module-compileall',
'compression': 'compression#module-compression',
'compression.zstd': 'compression.zstd#module-compression.zstd',
'concurrent.futures': 'concurrent.futures#module-concurrent.futures',
'concurrent.interpreters': 'concurrent.interpreters#module-concurrent.interpreters',
'configparser': 'configparser#module-configparser',
'contextlib': 'contextlib#module-contextlib',
'contextvars': 'contextvars#module-contextvars',
'copy': 'copy#module-copy',
'copyreg': 'copyreg#module-copyreg',
'crypt': 'crypt#module-crypt',
'csv': 'csv#module-csv',
'ctypes': 'ctypes#module-ctypes',
'curses': 'curses#module-curses',
'curses.ascii': 'curses.ascii#module-curses.ascii',
'curses.panel': 'curses.panel#module-curses.panel',
'curses.textpad': 'curses#module-curses.textpad',
'dataclasses': 'dataclasses#module-dataclasses',
'datetime': 'datetime#module-datetime',
'dbm': 'dbm#module-dbm',
'dbm.dumb': 'dbm#module-dbm.dumb',
'dbm.gnu': 'dbm#module-dbm.gnu',
'dbm.ndbm': 'dbm#module-dbm.ndbm',
'dbm.sqlite3': 'dbm#module-dbm.sqlite3',
'decimal': 'decimal#module-decimal',
'difflib': 'difflib#module-difflib',
'dis': 'dis#module-dis',
'distutils': 'distutils#module-distutils',
'doctest': 'doctest#module-doctest',
'email': 'email#module-email',
'email.charset': 'email.charset#module-email.charset',
'email.contentmanager': 'email.contentmanager#module-email.contentmanager',
'email.encoders': 'email.encoders#module-email.encoders',
'email.errors': 'email.errors#module-email.errors',
'email.generator': 'email.generator#module-email.generator',
'email.header': 'email.header#module-email.header',
'email.headerregistry': 'email.headerregistry#module-email.headerregistry',
'email.iterators': 'email.iterators#module-email.iterators',
'email.message': 'email.message#module-email.message',
'email.mime': 'email.mime#module-email.mime',
'email.mime.application': 'email.mime#module-email.mime.application',
'email.mime.audio': 'email.mime#module-email.mime.audio',
'email.mime.base': 'email.mime#module-email.mime.base',
'email.mime.image': 'email.mime#module-email.mime.image',
'email.mime.message': 'email.mime#module-email.mime.message',
'email.mime.multipart': 'email.mime#module-email.mime.multipart',
'email.mime.nonmultipart': 'email.mime#module-email.mime.nonmultipart',
'email.mime.text': 'email.mime#module-email.mime.text',
'email.parser': 'email.parser#module-email.parser',
'email.policy': 'email.policy#module-email.policy',
'email.utils': 'email.utils#module-email.utils',
'encodings': 'codecs#module-encodings',
'encodings.idna': 'codecs#module-encodings.idna',
'encodings.mbcs': 'codecs#module-encodings.mbcs',
'encodings.utf_8_sig': 'codecs#module-encodings.utf_8_sig',
'ensurepip': 'ensurepip#module-ensurepip',
'enum': 'enum#module-enum',
'errno': 'errno#module-errno',
'faulthandler': 'faulthandler#module-faulthandler',
'fcntl': 'fcntl#module-fcntl',
'filecmp': 'filecmp#module-filecmp',
'fileinput': 'fileinput#module-fileinput',
'fnmatch': 'fnmatch#module-fnmatch',
'fractions': 'fractions#module-fractions',
'ftplib': 'ftplib#module-ftplib',
'functools': 'functools#module-functools',
'gc': 'gc#module-gc',
'getopt': 'getopt#module-getopt',
'getpass': 'getpass#module-getpass',
'gettext': 'gettext#module-gettext',
'glob': 'glob#module-glob',
'graphlib': 'graphlib#module-graphlib',
'grp': 'grp#module-grp',
'gzip': 'gzip#module-gzip',
'hashlib': 'hashlib#module-hashlib',
'heapq': 'heapq#module-heapq',
'hmac': 'hmac#module-hmac',
'html': 'html#module-html',
'html.entities': 'html.entities#module-html.entities',
'html.parser': 'html.parser#module-html.parser',
'http': 'http#module-http',
'http.client': 'http.client#module-http.client',
'http.cookiejar': 'http.cookiejar#module-http.cookiejar',
'http.cookies': 'http.cookies#module-http.cookies',
'http.server': 'http.server#module-http.server',
'idlelib': 'idle#module-idlelib',
'imaplib': 'imaplib#module-imaplib',
'imghdr': 'imghdr#module-imghdr',
'imp': 'imp#module-imp',
'importlib': 'importlib#module-importlib',
'importlib.abc': 'importlib#module-importlib.abc',
'importlib.machinery': 'importlib#module-importlib.machinery',
'importlib.metadata': 'importlib.metadata#module-importlib.metadata',
'importlib.resources': 'importlib.resources#module-importlib.resources',
'importlib.resources.abc': 'importlib.resources.abc#module-importlib.resources.abc',
'importlib.util': 'importlib#module-importlib.util',
'inspect': 'inspect#module-inspect',
'io': 'io#module-io',
'ipaddress': 'ipaddress#module-ipaddress',
'itertools': 'itertools#module-itertools',
'json': 'json#module-json',
'json.tool': 'json#module-json.tool',
'keyword': 'keyword#module-keyword',
'linecache': 'linecache#module-linecache',
'locale': 'locale#module-locale',
'logging': 'logging#module-logging',
'logging.config': 'logging.config#module-logging.config',
'logging.handlers': 'logging.handlers#module-logging.handlers',
'lzma': 'lzma#module-lzma',
'mailbox': 'mailbox#module-mailbox',
'mailcap': 'mailcap#module-mailcap',
'marshal': 'marshal#module-marshal',
'math': 'math#module-math',
'mimetypes': 'mimetypes#module-mimetypes',
'mmap': 'mmap#module-mmap',
'modulefinder': 'modulefinder#module-modulefinder',
'msilib': 'msilib#module-msilib',
'msvcrt': 'msvcrt#module-msvcrt',
'multiprocessing': 'multiprocessing#module-multiprocessing',
'multiprocessing.connection': 'multiprocessing#module-multiprocessing.connection',
'multiprocessing.dummy': 'multiprocessing#module-multiprocessing.dummy',
'multiprocessing.managers': 'multiprocessing#module-multiprocessing.managers',
'multiprocessing.pool': 'multiprocessing#module-multiprocessing.pool',
'multiprocessing.shared_memory': 'multiprocessing.shared_memory#module-multiprocessing.shared_memory',
'multiprocessing.sharedctypes': 'multiprocessing#module-multiprocessing.sharedctypes',
'netrc': 'netrc#module-netrc',
'nis': 'nis#module-nis',
'nntplib': 'nntplib#module-nntplib',
'numbers': 'numbers#module-numbers',
'operator': 'operator#module-operator',
'optparse': 'optparse#module-optparse',
'os': 'os#module-os',
'os.path': 'os.path#module-os.path',
'ossaudiodev': 'ossaudiodev#module-ossaudiodev',
'pathlib': 'pathlib#module-pathlib',
'pathlib.types': 'pathlib#module-pathlib.types',
'pdb': 'pdb#module-pdb',
'pickle': 'pickle#module-pickle',
'pickletools': 'pickletools#module-pickletools',
'pipes': 'pipes#module-pipes',
'pkgutil': 'pkgutil#module-pkgutil',
'platform': 'platform#module-platform',
'plistlib': 'plistlib#module-plistlib',
'poplib': 'poplib#module-poplib',
'posix': 'posix#module-posix',
'pprint': 'pprint#module-pprint',
'profile': 'profile#module-profile',
'profiling.sampling': 'profile#module-profiling.sampling',
'pstats': 'profile#module-pstats',
'pty': 'pty#module-pty',
'pwd': 'pwd#module-pwd',
'py_compile': 'py_compile#module-py_compile',
'pyclbr': 'pyclbr#module-pyclbr',
'pydoc': 'pydoc#module-pydoc',
'queue': 'queue#module-queue',
'quopri': 'quopri#module-quopri',
'random': 'random#module-random',
're': 're#module-re',
'readline': 'readline#module-readline',
'reprlib': 'reprlib#module-reprlib',
'resource': 'resource#module-resource',
'rlcompleter': 'rlcompleter#module-rlcompleter',
'runpy': 'runpy#module-runpy',
'sched': 'sched#module-sched',
'secrets': 'secrets#module-secrets',
'select': 'select#module-select',
'selectors': 'selectors#module-selectors',
'shelve': 'shelve#module-shelve',
'shlex': 'shlex#module-shlex',
'shutil': 'shutil#module-shutil',
'signal': 'signal#module-signal',
'site': 'site#module-site',
'sitecustomize': 'site#module-sitecustomize',
'smtpd': 'smtpd#module-smtpd',
'smtplib': 'smtplib#module-smtplib',
'sndhdr': 'sndhdr#module-sndhdr',
'socket': 'socket#module-socket',
'socketserver': 'socketserver#module-socketserver',
'spwd': 'spwd#module-spwd',
'sqlite3': 'sqlite3#module-sqlite3',
'ssl': 'ssl#module-ssl',
'stat': 'stat#module-stat',
'statistics': 'statistics#module-statistics',
'string': 'string#module-string',
'string.templatelib': 'string.templatelib#module-string.templatelib',
'stringprep': 'stringprep#module-stringprep',
'struct': 'struct#module-struct',
'subprocess': 'subprocess#module-subprocess',
'sunau': 'sunau#module-sunau',
'symtable': 'symtable#module-symtable',
'sys': 'sys#module-sys',
'sys.monitoring': 'sys.monitoring#module-sys.monitoring',
'sysconfig': 'sysconfig#module-sysconfig',
'syslog': 'syslog#module-syslog',
'tabnanny': 'tabnanny#module-tabnanny',
'tarfile': 'tarfile#module-tarfile',
'telnetlib': 'telnetlib#module-telnetlib',
'tempfile': 'tempfile#module-tempfile',
'termios': 'termios#module-termios',
'test': 'test#module-test',
'test.regrtest': 'test#module-test.regrtest',
'test.support': 'test#module-test.support',
'test.support.bytecode_helper': 'test#module-test.support.bytecode_helper',
'test.support.import_helper': 'test#module-test.support.import_helper',
'test.support.os_helper': 'test#module-test.support.os_helper',
'test.support.script_helper': 'test#module-test.support.script_helper',
'test.support.socket_helper': 'test#module-test.support.socket_helper',
'test.support.threading_helper': 'test#module-test.support.threading_helper',
'test.support.warnings_helper': 'test#module-test.support.warnings_helper',
'textwrap': 'textwrap#module-textwrap',
'threading': 'threading#module-threading',
'time': 'time#module-time',
'timeit': 'timeit#module-timeit',
'tkinter': 'tkinter#module-tkinter',
'tkinter.colorchooser': 'tkinter.colorchooser#module-tkinter.colorchooser',
'tkinter.commondialog': 'dialog#module-tkinter.commondialog',
'tkinter.dnd': 'tkinter.dnd#module-tkinter.dnd',
'tkinter.filedialog': 'dialog#module-tkinter.filedialog',
'tkinter.font': 'tkinter.font#module-tkinter.font',
'tkinter.messagebox': 'tkinter.messagebox#module-tkinter.messagebox',
'tkinter.scrolledtext': 'tkinter.scrolledtext#module-tkinter.scrolledtext',
'tkinter.simpledialog': 'dialog#module-tkinter.simpledialog',
'tkinter.ttk': 'tkinter.ttk#module-tkinter.ttk',
'token': 'token#module-token',
'tokenize': 'tokenize#module-tokenize',
'tomllib': 'tomllib#module-tomllib',
'trace': 'trace#module-trace',
'traceback': 'traceback#module-traceback',
'tracemalloc': 'tracemalloc#module-tracemalloc',
'tty': 'tty#module-tty',
'turtle': 'turtle#module-turtle',
'turtledemo': 'turtle#module-turtledemo',
'types': 'types#module-types',
'typing': 'typing#module-typing',
'unicodedata': 'unicodedata#module-unicodedata',
'unittest': 'unittest#module-unittest',
'unittest.mock': 'unittest.mock#module-unittest.mock',
'urllib': 'urllib#module-urllib',
'urllib.error': 'urllib.error#module-urllib.error',
'urllib.parse': 'urllib.parse#module-urllib.parse',
'urllib.request': 'urllib.request#module-urllib.request',
'urllib.response': 'urllib.request#module-urllib.response',
'urllib.robotparser': 'urllib.robotparser#module-urllib.robotparser',
'usercustomize': 'site#module-usercustomize',
'uu': 'uu#module-uu',
'uuid': 'uuid#module-uuid',
'venv': 'venv#module-venv',
'warnings': 'warnings#module-warnings',
'wave': 'wave#module-wave',
'weakref': 'weakref#module-weakref',
'webbrowser': 'webbrowser#module-webbrowser',
'winreg': 'winreg#module-winreg',
'winsound': 'winsound#module-winsound',
'wsgiref': 'wsgiref#module-wsgiref',
'wsgiref.handlers': 'wsgiref#module-wsgiref.handlers',
'wsgiref.headers': 'wsgiref#module-wsgiref.headers',
'wsgiref.simple_server': 'wsgiref#module-wsgiref.simple_server',
'wsgiref.types': 'wsgiref#module-wsgiref.types',
'wsgiref.util': 'wsgiref#module-wsgiref.util',
'wsgiref.validate': 'wsgiref#module-wsgiref.validate',
'xdrlib': 'xdrlib#module-xdrlib',
'xml': 'xml#module-xml',
'xml.dom': 'xml.dom#module-xml.dom',
'xml.dom.minidom': 'xml.dom.minidom#module-xml.dom.minidom',
'xml.dom.pulldom': 'xml.dom.pulldom#module-xml.dom.pulldom',
'xml.etree.ElementInclude': 'xml.etree.elementtree#module-xml.etree.ElementInclude',
'xml.etree.ElementTree': 'xml.etree.elementtree#module-xml.etree.ElementTree',
'xml.parsers.expat': 'pyexpat#module-xml.parsers.expat',
'xml.parsers.expat.errors': 'pyexpat#module-xml.parsers.expat.errors',
'xml.parsers.expat.model': 'pyexpat#module-xml.parsers.expat.model',
'xml.sax': 'xml.sax#module-xml.sax',
'xml.sax.handler': 'xml.sax.handler#module-xml.sax.handler',
'xml.sax.saxutils': 'xml.sax.utils#module-xml.sax.saxutils',
'xml.sax.xmlreader': 'xml.sax.reader#module-xml.sax.xmlreader',
'xmlrpc': 'xmlrpc#module-xmlrpc',
'xmlrpc.client': 'xmlrpc.client#module-xmlrpc.client',
'xmlrpc.server': 'xmlrpc.server#module-xmlrpc.server',
'zipapp': 'zipapp#module-zipapp',
'zipfile': 'zipfile#module-zipfile',
'zipimport': 'zipimport#module-zipimport',
'zlib': 'zlib#module-zlib',
'zoneinfo': 'zoneinfo#module-zoneinfo',
}

View file

@ -135,6 +135,19 @@ class GeneralTest(unittest.TestCase):
finally:
atexit.unregister(func)
def test_eq_unregister_clear(self):
# Issue #112127: callback's __eq__ may call unregister or _clear
class Evil:
def __eq__(self, other):
action(other)
return NotImplemented
for action in atexit.unregister, lambda o: atexit._clear():
with self.subTest(action=action):
atexit.register(lambda: None)
atexit.unregister(Evil())
atexit._clear()
if __name__ == "__main__":
unittest.main()

View file

@ -2060,6 +2060,37 @@ class ByteArrayTest(BaseBytesTest, unittest.TestCase):
self.assertEqual(instance.ba[0], ord("?"), "Assigned bytearray not altered")
self.assertEqual(instance.new_ba, bytearray(0x180), "Wrong object altered")
def test_search_methods_reentrancy_raises_buffererror(self):
# gh-142560: Raise BufferError if buffer mutates during search arg conversion.
class Evil:
def __init__(self, ba):
self.ba = ba
def __buffer__(self, flags):
self.ba.clear()
return memoryview(self.ba)
def __release_buffer__(self, view: memoryview) -> None:
view.release()
def __index__(self):
self.ba.clear()
return ord("A")
def make_case():
ba = bytearray(b"A")
return ba, Evil(ba)
for name in ("find", "count", "index", "rindex", "rfind"):
ba, evil = make_case()
with self.subTest(name):
with self.assertRaises(BufferError):
getattr(ba, name)(evil)
ba, evil = make_case()
with self.assertRaises(BufferError):
evil in ba
with self.assertRaises(BufferError):
ba.split(evil)
with self.assertRaises(BufferError):
ba.rsplit(evil)
class AssortedBytesTest(unittest.TestCase):
#

View file

@ -1877,6 +1877,26 @@ class TestUopsOptimization(unittest.TestCase):
self.assertNotIn("_GUARD_TOS_UNICODE", uops)
self.assertIn("_BINARY_OP_ADD_UNICODE", uops)
def test_binary_op_subscr_str_int(self):
def testfunc(n):
x = 0
s = "hello"
for _ in range(n):
c = s[1] # _BINARY_OP_SUBSCR_STR_INT
if c == 'e':
x += 1
return x
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
self.assertEqual(res, TIER2_THRESHOLD)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertIn("_BINARY_OP_SUBSCR_STR_INT", uops)
self.assertIn("_COMPARE_OP_STR", uops)
self.assertIn("_POP_TOP_NOP", uops)
self.assertNotIn("_POP_TOP", uops)
self.assertNotIn("_POP_TOP_INT", uops)
def test_call_type_1_guards_removed(self):
def testfunc(n):
x = 0
@ -2473,6 +2493,46 @@ class TestUopsOptimization(unittest.TestCase):
uops = get_opnames(ex)
self.assertIn("_POP_TOP_NOP", uops)
def test_load_attr_instance_value(self):
def testfunc(n):
class C():
pass
c = C()
c.x = n
x = 0
for _ in range(n):
x = c.x
return x
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
self.assertEqual(res, TIER2_THRESHOLD)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertIn("_LOAD_ATTR_INSTANCE_VALUE", uops)
self.assertNotIn("_POP_TOP", uops)
self.assertIn("_POP_TOP_NOP", uops)
def test_load_attr_with_hint(self):
def testfunc(n):
class C:
pass
c = C()
c.x = 42
for i in range(_testinternalcapi.SHARED_KEYS_MAX_SIZE - 1):
setattr(c, f"_{i}", None)
x = 0
for i in range(n):
x += c.x
return x
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
self.assertEqual(res, 42 * TIER2_THRESHOLD)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertIn("_LOAD_ATTR_WITH_HINT", uops)
self.assertNotIn("_POP_TOP", uops)
self.assertIn("_POP_TOP_NOP", uops)
def test_int_add_op_refcount_elimination(self):
def testfunc(n):
c = 1
@ -2533,6 +2593,22 @@ class TestUopsOptimization(unittest.TestCase):
self.assertIn("_POP_TOP_NOP", uops)
self.assertNotIn("_POP_TOP", uops)
def test_unicode_add_op_refcount_elimination(self):
def testfunc(n):
c = "a"
res = ""
for _ in range(n):
res = c + c
return res
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
self.assertEqual(res, "aa")
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertIn("_BINARY_OP_ADD_UNICODE", uops)
self.assertIn("_POP_TOP_NOP", uops)
self.assertNotIn("_POP_TOP", uops)
def test_remove_guard_for_slice_list(self):
def f(n):
for i in range(n):
@ -2593,6 +2669,26 @@ class TestUopsOptimization(unittest.TestCase):
self.assertNotIn("_POP_TOP", uops)
self.assertIn("_POP_TOP_NOP", uops)
def test_store_attr_with_hint(self):
def testfunc(n):
class C:
pass
c = C()
for i in range(_testinternalcapi.SHARED_KEYS_MAX_SIZE - 1):
setattr(c, f"_{i}", None)
for i in range(n):
c.x = i
return c.x
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
self.assertEqual(res, TIER2_THRESHOLD - 1)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertIn("_STORE_ATTR_WITH_HINT", uops)
self.assertNotIn("_POP_TOP", uops)
self.assertIn("_POP_TOP_NOP", uops)
def test_store_subscr_int(self):
def testfunc(n):
l = [0, 0, 0, 0]
@ -2933,6 +3029,74 @@ class TestUopsOptimization(unittest.TestCase):
for _ in range(TIER2_THRESHOLD+1):
obj.attr = EvilAttr(obj.__dict__)
def test_promoted_global_refcount_eliminated(self):
result = script_helper.run_python_until_end('-c', textwrap.dedent("""
import _testinternalcapi
import opcode
import _opcode
def get_first_executor(func):
code = func.__code__
co_code = code.co_code
for i in range(0, len(co_code), 2):
try:
return _opcode.get_executor(code, i)
except ValueError:
pass
return None
def get_opnames(ex):
return {item[0] for item in ex}
def testfunc(n):
y = []
for i in range(n):
x = tuple(y)
return x
testfunc(_testinternalcapi.TIER2_THRESHOLD)
ex = get_first_executor(testfunc)
assert ex is not None
uops = get_opnames(ex)
assert "_LOAD_GLOBAL_BUILTIN" not in uops
assert "_LOAD_CONST_INLINE_BORROW" in uops
assert "_POP_TOP_NOP" in uops
assert "_POP_TOP" not in uops
"""), PYTHON_JIT="1")
self.assertEqual(result[0].rc, 0, result)
def test_constant_fold_tuple(self):
def testfunc(n):
for _ in range(n):
t = (1,)
p = len(t)
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertNotIn("_CALL_LEN", uops)
def test_binary_subscr_list_int(self):
def testfunc(n):
l = [1]
x = 0
for _ in range(n):
y = l[0]
x += y
return x
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
self.assertEqual(res, TIER2_THRESHOLD)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertIn("_BINARY_OP_SUBSCR_LIST_INT", uops)
self.assertNotIn("_POP_TOP", uops)
self.assertNotIn("_POP_TOP_INT", uops)
self.assertIn("_POP_TOP_NOP", uops)
def global_identity(x):
return x

View file

@ -2215,6 +2215,16 @@ class SectionlessTestCase(unittest.TestCase):
cfg.add_section(configparser.UNNAMED_SECTION)
cfg.set(configparser.UNNAMED_SECTION, 'a', '1')
self.assertEqual('1', cfg[configparser.UNNAMED_SECTION]['a'])
output = io.StringIO()
cfg.write(output)
self.assertEqual(output.getvalue(), 'a = 1\n\n')
cfg = configparser.ConfigParser(allow_unnamed_section=True)
cfg[configparser.UNNAMED_SECTION] = {'a': '1'}
self.assertEqual('1', cfg[configparser.UNNAMED_SECTION]['a'])
output = io.StringIO()
cfg.write(output)
self.assertEqual(output.getvalue(), 'a = 1\n\n')
def test_disabled_error(self):
with self.assertRaises(configparser.MissingSectionHeaderError):
@ -2223,6 +2233,9 @@ class SectionlessTestCase(unittest.TestCase):
with self.assertRaises(configparser.UnnamedSectionDisabledError):
configparser.ConfigParser().add_section(configparser.UNNAMED_SECTION)
with self.assertRaises(configparser.UnnamedSectionDisabledError):
configparser.ConfigParser()[configparser.UNNAMED_SECTION] = {'a': '1'}
def test_multiple_configs(self):
cfg = configparser.ConfigParser(allow_unnamed_section=True)
cfg.read_string('a = 1')

View file

@ -186,5 +186,23 @@ class TestDefaultDict(unittest.TestCase):
with self.assertRaises(TypeError):
i |= None
def test_factory_conflict_with_set_value(self):
key = "conflict_test"
count = 0
def default_factory():
nonlocal count
count += 1
local_count = count
if count == 1:
test_dict[key]
return local_count
test_dict = defaultdict(default_factory)
self.assertEqual(count, 0)
self.assertEqual(test_dict[key], 2)
self.assertEqual(count, 2)
if __name__ == "__main__":
unittest.main()

View file

@ -49,3 +49,74 @@ class TestFTGenerators(TestCase):
self.concurrent_write_with_func(func=set_gen_name)
with self.subTest(func=set_gen_qualname):
self.concurrent_write_with_func(func=set_gen_qualname)
def test_concurrent_send(self):
def gen():
yield 1
yield 2
yield 3
yield 4
yield 5
def run_test(drive_generator):
g = gen()
values = []
threading_helper.run_concurrently(drive_generator, self.NUM_THREADS, args=(g, values,))
self.assertEqual(sorted(values), [1, 2, 3, 4, 5])
def call_next(g, values):
while True:
try:
values.append(next(g))
except ValueError:
continue
except StopIteration:
break
with self.subTest(method='next'):
run_test(call_next)
def call_send(g, values):
while True:
try:
values.append(g.send(None))
except ValueError:
continue
except StopIteration:
break
with self.subTest(method='send'):
run_test(call_send)
def for_iter_gen(g, values):
while True:
try:
for value in g:
values.append(value)
else:
break
except ValueError:
continue
with self.subTest(method='for'):
run_test(for_iter_gen)
def test_concurrent_close(self):
def gen():
for i in range(10):
yield i
time.sleep(0.001)
def drive_generator(g):
while True:
try:
for value in g:
if value == 5:
g.close()
else:
return
except ValueError as e:
self.assertEqual(e.args[0], "generator already executing")
g = gen()
threading_helper.run_concurrently(drive_generator, self.NUM_THREADS, args=(g,))

View file

@ -91,6 +91,64 @@ class TestList(TestCase):
with threading_helper.start_threads(threads):
pass
def test_reverse(self):
def reverse_list(b, l):
b.wait()
for _ in range(100):
l.reverse()
def reader_list(b, l):
b.wait()
for _ in range(100):
for i in range(10):
self.assertTrue(0 <= l[i] < 10)
l = list(range(10))
barrier = Barrier(2)
threads = [Thread(target=reverse_list, args=(barrier, l)),
Thread(target=reader_list, args=(barrier, l))]
with threading_helper.start_threads(threads):
pass
def test_slice_assignment1(self):
def assign_slice(b, l):
b.wait()
for _ in range(100):
l[2:5] = [7, 8, 9]
def reader_list(b, l):
b.wait()
for _ in range(100):
self.assertIn(l[2], (2, 7))
self.assertIn(l[3], (3, 8))
self.assertIn(l[4], (4, 9))
l = list(range(10))
barrier = Barrier(2)
threads = [Thread(target=assign_slice, args=(barrier, l)),
Thread(target=reader_list, args=(barrier, l))]
with threading_helper.start_threads(threads):
pass
def test_slice_assignment2(self):
def assign_slice(b, l):
b.wait()
for _ in range(100):
l[::2] = [10, 11, 12, 13, 14]
def reader_list(b, l):
b.wait()
for _ in range(100):
for i in range(0, 10, 2):
self.assertIn(l[i], (i, 10 + i // 2))
l = list(range(10))
barrier = Barrier(2)
threads = [Thread(target=assign_slice, args=(barrier, l)),
Thread(target=reader_list, args=(barrier, l))]
with threading_helper.start_threads(threads):
pass
if __name__ == "__main__":
unittest.main()

View file

@ -134,6 +134,18 @@ class FinalizationTest(unittest.TestCase):
self.assertEqual(len(resurrected), 1)
self.assertIsInstance(resurrected[0].gi_code, types.CodeType)
def test_exhausted_generator_frame_cycle(self):
def g():
yield
generator = g()
frame = generator.gi_frame
self.assertIsNone(frame.f_back)
next(generator)
self.assertIsNone(frame.f_back)
next(generator, None)
self.assertIsNone(frame.f_back)
class GeneratorTest(unittest.TestCase):
@ -290,6 +302,33 @@ class GeneratorTest(unittest.TestCase):
self.assertEqual([1,2], list(i for i in C()))
def test_close_clears_frame(self):
# gh-142766: Test that closing a generator clears its frame
class DetectDelete:
def __init__(self):
DetectDelete.deleted = False
def __del__(self):
DetectDelete.deleted = True
def generator(arg):
yield
# Test a freshly created generator (not suspended)
g = generator(DetectDelete())
g.close()
self.assertTrue(DetectDelete.deleted)
# Test a suspended generator
g = generator(DetectDelete())
next(g)
g.close()
self.assertTrue(DetectDelete.deleted)
# Clear via gi_frame.clear()
g = generator(DetectDelete())
g.gi_frame.clear()
self.assertTrue(DetectDelete.deleted)
class ModifyUnderlyingIterableTest(unittest.TestCase):
iterables = [

View file

@ -174,6 +174,7 @@ class MinidomTest(unittest.TestCase):
self.assertEqual(dom.documentElement.childNodes[-1].data, "Hello")
dom.unlink()
@support.requires_resource('cpu')
def testAppendChildNoQuadraticComplexity(self):
impl = getDOMImplementation()
@ -182,14 +183,18 @@ class MinidomTest(unittest.TestCase):
children = [newdoc.createElement(f"child-{i}") for i in range(1, 2 ** 15 + 1)]
element = top_element
start = time.time()
start = time.monotonic()
for child in children:
element.appendChild(child)
element = child
end = time.time()
end = time.monotonic()
# This example used to take at least 30 seconds.
self.assertLess(end - start, 1)
# Conservative assertion due to the wide variety of systems and
# build configs timing based tests wind up run under.
# A --with-address-sanitizer --with-pydebug build on a rpi5 still
# completes this loop in <0.5 seconds.
self.assertLess(end - start, 4)
def testSetAttributeNodeWithoutOwnerDocument(self):
# regression test for gh-142754

View file

@ -6,6 +6,7 @@ from test.support import (
from test.support.import_helper import import_module
from test.support.os_helper import TESTFN, unlink
from test.support.script_helper import assert_python_ok
import errno
import unittest
import os
import re
@ -1165,6 +1166,46 @@ class MmapTests(unittest.TestCase):
m.flush(PAGESIZE)
m.flush(PAGESIZE, PAGESIZE)
@unittest.skipUnless(sys.platform == 'linux', 'Linux only')
@support.requires_linux_version(5, 17, 0)
def test_set_name(self):
# Test setting name on anonymous mmap
m = mmap.mmap(-1, PAGESIZE)
self.addCleanup(m.close)
try:
result = m.set_name('test_mapping')
except OSError as exc:
if exc.errno == errno.EINVAL:
# gh-142419: On Fedora, prctl(PR_SET_VMA_ANON_NAME) fails with
# EINVAL because the kernel option CONFIG_ANON_VMA_NAME is
# disabled.
# See: https://bugzilla.redhat.com/show_bug.cgi?id=2302746
self.skipTest("prctl() failed with EINVAL")
else:
raise
self.assertIsNone(result)
# Test name length limit (80 chars including prefix "cpython:mmap:" and '\0')
# Prefix is 13 chars, so max name is 66 chars
long_name = 'x' * 66
result = m.set_name(long_name)
self.assertIsNone(result)
# Test name too long
too_long_name = 'x' * 67
with self.assertRaises(ValueError):
m.set_name(too_long_name)
# Test that file-backed mmap raises error
with open(TESTFN, 'wb+') as f:
f.write(b'x' * PAGESIZE)
f.flush()
m2 = mmap.mmap(f.fileno(), PAGESIZE)
self.addCleanup(m2.close)
with self.assertRaises(ValueError):
m2.set_name('should_fail')
class LargeMmapTests(unittest.TestCase):

View file

@ -3478,6 +3478,49 @@ def test_pdb_issue_gh_65052():
(Pdb) continue
"""
def test_pdb_commands_last_breakpoint():
"""See GH-142834
>>> def test_function():
... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
... foo = 1
... bar = 2
>>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE
... 'break 4',
... 'break 3',
... 'clear 2',
... 'commands',
... 'p "success"',
... 'end',
... 'continue',
... 'clear 1',
... 'commands',
... 'continue',
... ]):
... test_function()
> <doctest test.test_pdb.test_pdb_commands_last_breakpoint[0]>(2)test_function()
-> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
(Pdb) break 4
Breakpoint 1 at <doctest test.test_pdb.test_pdb_commands_last_breakpoint[0]>:4
(Pdb) break 3
Breakpoint 2 at <doctest test.test_pdb.test_pdb_commands_last_breakpoint[0]>:3
(Pdb) clear 2
Deleted breakpoint 2 at <doctest test.test_pdb.test_pdb_commands_last_breakpoint[0]>:3
(Pdb) commands
(com) p "success"
(com) end
(Pdb) continue
'success'
> <doctest test.test_pdb.test_pdb_commands_last_breakpoint[0]>(4)test_function()
-> bar = 2
(Pdb) clear 1
Deleted breakpoint 1 at <doctest test.test_pdb.test_pdb_commands_last_breakpoint[0]>:4
(Pdb) commands
*** cannot set commands: no existing breakpoint
(Pdb) continue
"""
@support.force_not_colorized_test_class
@support.requires_subprocess()
@ -3563,10 +3606,22 @@ class PdbTestCase(unittest.TestCase):
def _fd_dir_for_pipe_targets(self):
"""Return a directory exposing live file descriptors, if any."""
return self._proc_fd_dir() or self._dev_fd_dir()
def _proc_fd_dir(self):
"""Return /proc-backed fd dir when it can be used for pipes."""
# GH-142836: Opening /proc/self/fd entries for pipes raises EACCES on
# Solaris, so prefer other mechanisms there.
if sys.platform.startswith("sunos"):
return None
proc_fd = "/proc/self/fd"
if os.path.isdir(proc_fd) and os.path.exists(os.path.join(proc_fd, '0')):
return proc_fd
return None
def _dev_fd_dir(self):
"""Return /dev-backed fd dir when usable."""
dev_fd = "/dev/fd"
if os.path.isdir(dev_fd) and os.path.exists(os.path.join(dev_fd, '0')):
if sys.platform.startswith("freebsd"):
@ -3576,7 +3631,6 @@ class PdbTestCase(unittest.TestCase):
except FileNotFoundError:
return None
return dev_fd
return None
def test_find_function_empty_file(self):
@ -4039,6 +4093,23 @@ def bœr():
f.write("invalid")
self.assertEqual(pdb.Pdb().rcLines[0], "invalid")
def test_readrc_current_dir(self):
with os_helper.temp_cwd() as cwd:
rc_path = os.path.join(cwd, ".pdbrc")
with open(rc_path, "w") as f:
f.write("invalid")
self.assertEqual(pdb.Pdb().rcLines[-1], "invalid")
def test_readrc_cwd_is_home(self):
with os_helper.EnvironmentVarGuard() as env:
env.unset("HOME")
with os_helper.temp_cwd() as cwd, patch("os.path.expanduser"):
rc_path = os.path.join(cwd, ".pdbrc")
os.path.expanduser.return_value = rc_path
with open(rc_path, "w") as f:
f.write("invalid")
self.assertEqual(pdb.Pdb().rcLines, ["invalid"])
def test_header(self):
stdout = StringIO()
header = 'Nobody expects... blah, blah, blah'

File diff suppressed because it is too large Load diff

View file

@ -16,6 +16,7 @@ except ImportError:
from test.support import is_emscripten, requires_remote_subprocess_debugging
from profiling.sampling.cli import main
from profiling.sampling.errors import SamplingScriptNotFoundError, SamplingModuleNotFoundError, SamplingUnknownProcessError
class TestSampleProfilerCLI(unittest.TestCase):
@ -203,12 +204,12 @@ class TestSampleProfilerCLI(unittest.TestCase):
with (
mock.patch("sys.argv", test_args),
mock.patch("sys.stderr", io.StringIO()) as mock_stderr,
self.assertRaises(SystemExit) as cm,
self.assertRaises(SamplingScriptNotFoundError) as cm,
):
main()
# Verify the error is about the non-existent script
self.assertIn("12345", str(cm.exception.code))
self.assertIn("12345", str(cm.exception))
def test_cli_no_target_specified(self):
# In new CLI, must specify a subcommand
@ -436,6 +437,7 @@ class TestSampleProfilerCLI(unittest.TestCase):
with (
mock.patch("sys.argv", test_args),
mock.patch("profiling.sampling.cli._is_process_running", return_value=True),
mock.patch("profiling.sampling.cli.sample") as mock_sample,
):
main()
@ -475,6 +477,7 @@ class TestSampleProfilerCLI(unittest.TestCase):
for test_args, expected_filename, expected_format in test_cases:
with (
mock.patch("sys.argv", test_args),
mock.patch("profiling.sampling.cli._is_process_running", return_value=True),
mock.patch("profiling.sampling.cli.sample") as mock_sample,
):
main()
@ -513,6 +516,7 @@ class TestSampleProfilerCLI(unittest.TestCase):
with (
mock.patch("sys.argv", test_args),
mock.patch("profiling.sampling.cli._is_process_running", return_value=True),
mock.patch("profiling.sampling.cli.sample") as mock_sample,
):
main()
@ -534,6 +538,7 @@ class TestSampleProfilerCLI(unittest.TestCase):
with (
mock.patch("sys.argv", test_args),
mock.patch("profiling.sampling.cli._is_process_running", return_value=True),
mock.patch("profiling.sampling.cli.sample") as mock_sample,
):
main()
@ -547,6 +552,7 @@ class TestSampleProfilerCLI(unittest.TestCase):
with (
mock.patch("sys.argv", test_args),
mock.patch("profiling.sampling.cli._is_process_running", return_value=True),
mock.patch("profiling.sampling.cli.sample") as mock_sample,
):
main()
@ -562,6 +568,7 @@ class TestSampleProfilerCLI(unittest.TestCase):
with (
mock.patch("sys.argv", test_args),
mock.patch("profiling.sampling.cli._is_process_running", return_value=True),
mock.patch("profiling.sampling.cli.sample") as mock_sample,
):
main()
@ -576,6 +583,7 @@ class TestSampleProfilerCLI(unittest.TestCase):
with (
mock.patch("sys.argv", test_args),
mock.patch("profiling.sampling.cli._is_process_running", return_value=True),
mock.patch("profiling.sampling.cli.sample") as mock_sample,
):
main()
@ -697,14 +705,20 @@ class TestSampleProfilerCLI(unittest.TestCase):
def test_run_nonexistent_script_exits_cleanly(self):
"""Test that running a non-existent script exits with a clean error."""
with mock.patch("sys.argv", ["profiling.sampling.cli", "run", "/nonexistent/script.py"]):
with self.assertRaises(SystemExit) as cm:
with self.assertRaisesRegex(SamplingScriptNotFoundError, "Script '[\\w/.]+' not found."):
main()
self.assertIn("Script not found", str(cm.exception.code))
@unittest.skipIf(is_emscripten, "subprocess not available")
def test_run_nonexistent_module_exits_cleanly(self):
"""Test that running a non-existent module exits with a clean error."""
with mock.patch("sys.argv", ["profiling.sampling.cli", "run", "-m", "nonexistent_module_xyz"]):
with self.assertRaises(SystemExit) as cm:
with self.assertRaisesRegex(SamplingModuleNotFoundError, "Module '[\\w/.]+' not found."):
main()
self.assertIn("Module not found", str(cm.exception.code))
def test_cli_attach_nonexistent_pid(self):
fake_pid = "99999"
with mock.patch("sys.argv", ["profiling.sampling.cli", "attach", fake_pid]):
with self.assertRaises(SamplingUnknownProcessError) as cm:
main()
self.assertIn(fake_pid, str(cm.exception))

View file

@ -17,7 +17,7 @@ try:
import profiling.sampling.sample
from profiling.sampling.pstats_collector import PstatsCollector
from profiling.sampling.stack_collector import CollapsedStackCollector
from profiling.sampling.sample import SampleProfiler
from profiling.sampling.sample import SampleProfiler, _is_process_running
except ImportError:
raise unittest.SkipTest(
"Test only runs when _remote_debugging is available"
@ -602,7 +602,7 @@ do_work()
@requires_remote_subprocess_debugging()
class TestSampleProfilerErrorHandling(unittest.TestCase):
def test_invalid_pid(self):
with self.assertRaises((OSError, RuntimeError)):
with self.assertRaises((SystemExit, PermissionError)):
collector = PstatsCollector(sample_interval_usec=100, skip_idle=False)
profiling.sampling.sample.sample(-1, collector, duration_sec=1)
@ -638,7 +638,7 @@ class TestSampleProfilerErrorHandling(unittest.TestCase):
sample_interval_usec=1000,
all_threads=False,
)
self.assertTrue(profiler._is_process_running())
self.assertTrue(_is_process_running(profiler.pid))
self.assertIsNotNone(profiler.unwinder.get_stack_trace())
subproc.process.kill()
subproc.process.wait()
@ -647,7 +647,7 @@ class TestSampleProfilerErrorHandling(unittest.TestCase):
)
# Exit the context manager to ensure the process is terminated
self.assertFalse(profiler._is_process_running())
self.assertFalse(_is_process_running(profiler.pid))
self.assertRaises(
ProcessLookupError, profiler.unwinder.get_stack_trace
)
@ -863,3 +863,98 @@ asyncio.run(supervisor())
self.assertGreater(cpu_percentage, 90.0,
f"cpu_leaf should dominate samples in 'running' mode, "
f"got {cpu_percentage:.1f}% ({cpu_leaf_samples}/{total})")
def _generate_deep_generators_script(chain_depth=20, recurse_depth=150):
"""Generate a script with deep nested generators for stress testing."""
lines = [
'import sys',
'sys.setrecursionlimit(5000)',
'',
]
# Generate chain of yield-from functions
for i in range(chain_depth - 1):
lines.extend([
f'def deep_yield_chain_{i}(n):',
f' yield ("L{i}", n)',
f' yield from deep_yield_chain_{i + 1}(n)',
'',
])
# Last chain function calls recursive_diver
lines.extend([
f'def deep_yield_chain_{chain_depth - 1}(n):',
f' yield ("L{chain_depth - 1}", n)',
f' yield from recursive_diver(n, {chain_depth})',
'',
'def recursive_diver(n, depth):',
' yield (f"DIVE_{depth}", n)',
f' if depth < {recurse_depth}:',
' yield from recursive_diver(n, depth + 1)',
' else:',
' for i in range(5):',
' yield (f"BOTTOM_{depth}", i)',
'',
'def oscillating_generator(iterations=1000):',
' for i in range(iterations):',
' yield ("OSCILLATE", i)',
' yield from deep_yield_chain_0(i)',
'',
'def run_forever():',
' while True:',
' for _ in oscillating_generator(10):',
' pass',
'',
'_test_sock.sendall(b"working")',
'run_forever()',
])
return '\n'.join(lines)
@requires_remote_subprocess_debugging()
class TestDeepGeneratorFrameCache(unittest.TestCase):
"""Test frame cache consistency with deep oscillating generator stacks."""
def test_all_stacks_share_same_base_frame(self):
"""Verify all sampled stacks reach the entry point function.
When profiling deep generators that oscillate up and down the call
stack, every sample should include the entry point function
(run_forever) in its call chain. If the frame cache stores
incomplete stacks, some samples will be missing this base function,
causing broken flamegraphs.
"""
script = _generate_deep_generators_script()
with test_subprocess(script, wait_for_working=True) as subproc:
collector = CollapsedStackCollector(sample_interval_usec=1, skip_idle=False)
with (
io.StringIO() as captured_output,
mock.patch("sys.stdout", captured_output),
):
profiling.sampling.sample.sample(
subproc.process.pid,
collector,
duration_sec=2,
)
samples_with_entry_point = 0
samples_without_entry_point = 0
total_samples = 0
for (call_tree, _thread_id), count in collector.stack_counter.items():
total_samples += count
if call_tree:
has_entry_point = call_tree and call_tree[0][2] == "<module>"
if has_entry_point:
samples_with_entry_point += count
else:
samples_without_entry_point += count
self.assertGreater(total_samples, 100,
f"Expected at least 100 samples, got {total_samples}")
self.assertEqual(samples_without_entry_point, 0,
f"Found {samples_without_entry_point}/{total_samples} samples "
f"missing the entry point function 'run_forever'. This indicates "
f"incomplete stacks are being returned, likely due to frame cache "
f"storing partial stack traces.")

View file

@ -252,6 +252,7 @@ class TestGilModeFiltering(unittest.TestCase):
with (
mock.patch("sys.argv", test_args),
mock.patch("profiling.sampling.cli._is_process_running", return_value=True),
mock.patch("profiling.sampling.cli.sample") as mock_sample,
):
try:
@ -313,6 +314,7 @@ class TestGilModeFiltering(unittest.TestCase):
with (
mock.patch("sys.argv", test_args),
mock.patch("profiling.sampling.cli._is_process_running", return_value=True),
mock.patch("profiling.sampling.cli.sample") as mock_sample,
):
try:
@ -432,6 +434,7 @@ class TestExceptionModeFiltering(unittest.TestCase):
with (
mock.patch("sys.argv", test_args),
mock.patch("profiling.sampling.cli._is_process_running", return_value=True),
mock.patch("profiling.sampling.cli.sample") as mock_sample,
):
try:
@ -493,6 +496,7 @@ class TestExceptionModeFiltering(unittest.TestCase):
with (
mock.patch("sys.argv", test_args),
mock.patch("profiling.sampling.cli._is_process_running", return_value=True),
mock.patch("profiling.sampling.cli.sample") as mock_sample,
):
try:

View file

@ -473,6 +473,32 @@ class PydocDocTest(unittest.TestCase):
result, doc_loc = get_pydoc_text(xml.etree)
self.assertEqual(doc_loc, "", "MODULE DOCS incorrectly includes a link")
def test_online_docs_link(self):
import encodings.idna
import importlib._bootstrap
module_docs = {
'encodings': 'codecs#module-encodings',
'encodings.idna': 'codecs#module-encodings.idna',
}
with unittest.mock.patch('pydoc_data.module_docs.module_docs', module_docs):
doc = pydoc.TextDoc()
basedir = os.path.dirname(encodings.__file__)
doc_link = doc.getdocloc(encodings, basedir=basedir)
self.assertIsNotNone(doc_link)
self.assertIn('codecs#module-encodings', doc_link)
self.assertNotIn('encodings.html', doc_link)
doc_link = doc.getdocloc(encodings.idna, basedir=basedir)
self.assertIsNotNone(doc_link)
self.assertIn('codecs#module-encodings.idna', doc_link)
self.assertNotIn('encodings.idna.html', doc_link)
doc_link = doc.getdocloc(importlib._bootstrap, basedir=basedir)
self.assertIsNone(doc_link)
def test_getpager_with_stdin_none(self):
previous_stdin = sys.stdin
try:

View file

@ -409,6 +409,12 @@ class TestAsyncioREPL(unittest.TestCase):
expected = "toplevel contextvar test: ok"
self.assertIn(expected, output, expected)
def test_quiet_mode(self):
p = spawn_repl("-q", "-m", "asyncio", custom=True)
output = kill_python(p)
self.assertEqual(p.returncode, 0)
self.assertEqual(output[:3], ">>>")
if __name__ == "__main__":
unittest.main()

View file

@ -1573,5 +1573,17 @@ class TestModuleAll(unittest.TestCase):
check__all__(self, sax, extra=extra)
class TestModule(unittest.TestCase):
def test_deprecated__version__and__date__(self):
for module in (sax.expatreader, sax.handler):
with self.subTest(module=module):
with self.assertWarnsRegex(
DeprecationWarning,
"'version' is deprecated and slated for removal in Python 3.20",
) as cm:
getattr(module, "version")
self.assertEqual(cm.filename, __file__)
if __name__ == "__main__":
unittest.main()

View file

@ -6,9 +6,14 @@ import_helper.import_module('_sqlite3')
import os
import sqlite3
# make sure only print once
_printed_version = False
# Implement the unittest "load tests" protocol.
def load_tests(*args):
if verbose:
def load_tests(loader, tests, pattern):
global _printed_version
if verbose and not _printed_version:
print(f"test_sqlite3: testing with SQLite version {sqlite3.sqlite_version}")
_printed_version = True
pkg_dir = os.path.dirname(__file__)
return load_package_tests(pkg_dir, *args)
return load_package_tests(pkg_dir, loader, tests, pattern)

View file

@ -759,7 +759,7 @@ class NormalizationTest(unittest.TestCase):
@requires_resource('cpu')
def test_normalization_3_2_0(self):
testdatafile = findfile('NormalizationTest-3.2.0.txt', 'data')
testdatafile = findfile('NormalizationTest-3.2.0.txt')
with open(testdatafile, encoding='utf-8') as testdata:
self.run_normalization_tests(testdata, unicodedata.ucd_3_2_0)

View file

@ -219,5 +219,81 @@ class TestThreadingMock(unittest.TestCase):
self.assertEqual(m.call_count, LOOPS * THREADS)
def test_call_args_thread_safe(self):
m = ThreadingMock()
LOOPS = 100
THREADS = 10
def test_function(thread_id):
for i in range(LOOPS):
m(thread_id, i)
oldswitchinterval = sys.getswitchinterval()
setswitchinterval(1e-6)
try:
threads = [
threading.Thread(target=test_function, args=(thread_id,))
for thread_id in range(THREADS)
]
with threading_helper.start_threads(threads):
pass
finally:
sys.setswitchinterval(oldswitchinterval)
expected_calls = {
(thread_id, i)
for thread_id in range(THREADS)
for i in range(LOOPS)
}
self.assertSetEqual({call.args for call in m.call_args_list}, expected_calls)
def test_method_calls_thread_safe(self):
m = ThreadingMock()
LOOPS = 100
THREADS = 10
def test_function(thread_id):
for i in range(LOOPS):
getattr(m, f"method_{thread_id}")(i)
oldswitchinterval = sys.getswitchinterval()
setswitchinterval(1e-6)
try:
threads = [
threading.Thread(target=test_function, args=(thread_id,))
for thread_id in range(THREADS)
]
with threading_helper.start_threads(threads):
pass
finally:
sys.setswitchinterval(oldswitchinterval)
for thread_id in range(THREADS):
self.assertEqual(getattr(m, f"method_{thread_id}").call_count, LOOPS)
self.assertEqual({call.args for call in getattr(m, f"method_{thread_id}").call_args_list},
{(i,) for i in range(LOOPS)})
def test_mock_calls_thread_safe(self):
m = ThreadingMock()
LOOPS = 100
THREADS = 10
def test_function(thread_id):
for i in range(LOOPS):
m(thread_id, i)
oldswitchinterval = sys.getswitchinterval()
setswitchinterval(1e-6)
try:
threads = [
threading.Thread(target=test_function, args=(thread_id,))
for thread_id in range(THREADS)
]
with threading_helper.start_threads(threads):
pass
finally:
sys.setswitchinterval(oldswitchinterval)
expected_calls = {
(thread_id, i)
for thread_id in range(THREADS)
for i in range(LOOPS)
}
self.assertSetEqual({call.args for call in m.mock_calls}, expected_calls)
if __name__ == "__main__":
unittest.main()

View file

@ -577,6 +577,23 @@ class OpenerDirectorTests(unittest.TestCase):
self.assertRaises(TypeError,
OpenerDirector().add_handler, NonHandler())
def test_no_protocol_methods(self):
# test the case that methods starts with handler type without the protocol
# like open*() or _open*().
# These methods should be ignored
o = OpenerDirector()
meth_spec = [
["open"],
["_open"],
["error"]
]
add_ordered_mock_handlers(o, meth_spec)
self.assertEqual(len(o.handle_open), 0)
self.assertEqual(len(o.handle_error), 0)
def test_badly_named_methods(self):
# test work-around for three methods that accidentally follow the
# naming conventions for handler methods

View file

@ -4705,6 +4705,19 @@ class C14NTest(unittest.TestCase):
# --------------------------------------------------------------------
class TestModule(unittest.TestCase):
def test_deprecated_version(self):
with self.assertWarnsRegex(
DeprecationWarning,
"'VERSION' is deprecated and slated for removal in Python 3.20",
) as cm:
getattr(ET, "VERSION")
self.assertEqual(cm.filename, __file__)
# --------------------------------------------------------------------
def setUpModule(module=None):
# When invoked without a module, runs the Python ET tests by loading pyET.
# Otherwise, uses the given module as the ET.

View file

@ -1551,6 +1551,26 @@ class ZoneInfoCacheTest(TzPathUserMixin, ZoneInfoTestBase):
except CustomError:
pass
def test_weak_cache_descriptor_use_after_free(self):
class BombDescriptor:
def __get__(self, obj, owner):
return {}
class EvilZoneInfo(self.klass):
pass
# Must be set after the class creation.
EvilZoneInfo._weak_cache = BombDescriptor()
key = "America/Los_Angeles"
zone1 = EvilZoneInfo(key)
self.assertEqual(str(zone1), key)
EvilZoneInfo.clear_cache()
zone2 = EvilZoneInfo(key)
self.assertEqual(str(zone2), key)
self.assertIsNot(zone2, zone1)
class CZoneInfoCacheTest(ZoneInfoCacheTest):
module = c_zoneinfo

View file

@ -415,6 +415,8 @@ class OpenerDirector:
continue
i = meth.find("_")
if i < 1:
continue
protocol = meth[:i]
condition = meth[i+1:]

View file

@ -83,15 +83,12 @@ __all__ = [
"SubElement",
"tostring", "tostringlist",
"TreeBuilder",
"VERSION",
"XML", "XMLID",
"XMLParser", "XMLPullParser",
"register_namespace",
"canonicalize", "C14NWriterTarget",
]
VERSION = "1.3.0"
import sys
import re
import warnings
@ -2104,3 +2101,14 @@ except ImportError:
pass
else:
_set_factories(Comment, ProcessingInstruction)
# --------------------------------------------------------------------
def __getattr__(name):
if name == "VERSION":
from warnings import _deprecated
_deprecated("VERSION", remove=(3, 20))
return "1.3.0" # Do not change
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

Some files were not shown because too many files have changed in this diff Show more