From 5a31024da45eb00f5450269cad4481131d77c81f Mon Sep 17 00:00:00 2001 From: Jeffrey Bosboom Date: Thu, 16 Oct 2025 04:40:47 -0700 Subject: [PATCH 1/4] gh-83714: Check for struct statx.stx_atomic_write_unit_max_opt in configure (#140185) stx_atomic_write_unit_max_opt was added in Linux 6.16, but is controlled by the STATX_WRITE_ATOMIC mask bit added in Linux 6.11. That's safe at runtime because all kernels clear the reserved space in struct statx and zero is a valid value for stx_atomic_write_unit_max_opt, and it avoids allocating another mask bit, which are a limited resource. But it also means the kernel headers don't provide a way to check whether stx_atomic_write_unit_max_opt exists, so add a configure check. --- Doc/library/os.rst | 2 +- Modules/posixmodule.c | 2 +- configure | 15 +++++++++++++++ configure.ac | 7 +++++++ pyconfig.h.in | 4 ++++ 5 files changed, 28 insertions(+), 2 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 1ac87b32badd78..c3ed6a976926fd 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -3496,7 +3496,7 @@ features: Maximum optimized size for direct I/O with torn-write protection. .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel - userspace API headers >= 6.11. + userspace API headers >= 6.16. .. attribute:: stx_atomic_write_segments_max diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 5c1c508578a31e..1338be22e18678 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -3369,7 +3369,7 @@ static PyMemberDef pystatx_result_members[] = { MM(stx_atomic_write_segments_max, Py_T_UINT, atomic_write_segments_max, "maximum iovecs for direct I/O with torn-write protection"), #endif -#if 0 +#ifdef HAVE_STRUCT_STATX_STX_ATOMIC_WRITE_UNIT_MAX_OPT MM(stx_atomic_write_unit_max_opt, Py_T_UINT, atomic_write_unit_max_opt, "maximum optimized size for direct I/O with torn-write protection"), #endif diff --git a/configure b/configure index 28005cd1924be7..267981250cd7e2 100755 --- a/configure +++ b/configure @@ -25133,6 +25133,21 @@ printf "%s\n" "#define HAVE_SIGINFO_T_SI_BAND 1" >>confdefs.h fi +if test "$ac_cv_func_statx" = yes; then + # stx_atomic_write_unit_max_opt was added in Linux 6.16, but is controlled by + # the STATX_WRITE_ATOMIC mask bit added in Linux 6.11, so having the mask bit + # doesn't imply having the member. + ac_fn_c_check_member "$LINENO" "struct statx" "stx_atomic_write_unit_max_opt" "ac_cv_member_struct_statx_stx_atomic_write_unit_max_opt" "$ac_includes_default" +if test "x$ac_cv_member_struct_statx_stx_atomic_write_unit_max_opt" = xyes +then : + +printf "%s\n" "#define HAVE_STRUCT_STATX_STX_ATOMIC_WRITE_UNIT_MAX_OPT 1" >>confdefs.h + + +fi + +fi + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for time.h that defines altzone" >&5 printf %s "checking for time.h that defines altzone... " >&6; } if test ${ac_cv_header_time_altzone+y} diff --git a/configure.ac b/configure.ac index d20f6f8c40abeb..382591952ef68f 100644 --- a/configure.ac +++ b/configure.ac @@ -5819,6 +5819,13 @@ AC_CHECK_MEMBERS([struct passwd.pw_gecos, struct passwd.pw_passwd], [], [], [[ # Issue #21085: In Cygwin, siginfo_t does not have si_band field. AC_CHECK_MEMBERS([siginfo_t.si_band], [], [], [[@%:@include ]]) +if test "$ac_cv_func_statx" = yes; then + # stx_atomic_write_unit_max_opt was added in Linux 6.16, but is controlled by + # the STATX_WRITE_ATOMIC mask bit added in Linux 6.11, so having the mask bit + # doesn't imply having the member. + AC_CHECK_MEMBERS([struct statx.stx_atomic_write_unit_max_opt]) +fi + AC_CACHE_CHECK([for time.h that defines altzone], [ac_cv_header_time_altzone], [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include ]], [[return altzone;]])], [ac_cv_header_time_altzone=yes], diff --git a/pyconfig.h.in b/pyconfig.h.in index 611408d88f05b5..092f96e7152e11 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -1330,6 +1330,10 @@ /* Define to 1 if 'pw_passwd' is a member of 'struct passwd'. */ #undef HAVE_STRUCT_PASSWD_PW_PASSWD +/* Define to 1 if 'stx_atomic_write_unit_max_opt' is a member of 'struct + statx'. */ +#undef HAVE_STRUCT_STATX_STX_ATOMIC_WRITE_UNIT_MAX_OPT + /* Define to 1 if 'st_birthtime' is a member of 'struct stat'. */ #undef HAVE_STRUCT_STAT_ST_BIRTHTIME From 379fd020a0116754f22b04fa2f7f27a8f7b372b0 Mon Sep 17 00:00:00 2001 From: bzoracler <50305397+bzoracler@users.noreply.github.com> Date: Fri, 17 Oct 2025 01:30:36 +1300 Subject: [PATCH 2/4] =?UTF-8?q?gh-138859:=20Account=20for=20`ParamSpec`=20?= =?UTF-8?q?defaults=20that=20are=20not=20lists=20=E2=80=A6=20(#138868)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Lib/test/test_typing.py | 10 ++++++++++ Lib/typing.py | 2 +- .../2025-09-13-12-19-17.gh-issue-138859.PxjIoN.rst | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2025-09-13-12-19-17.gh-issue-138859.PxjIoN.rst diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index bc7f14f90a7cf9..db0501d70e3442 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -762,6 +762,16 @@ class A(Generic[T, P, U]): ... self.assertEqual(A[float, [range]].__args__, (float, (range,), float)) self.assertEqual(A[float, [range], int].__args__, (float, (range,), int)) + def test_paramspec_and_typevar_specialization_2(self): + T = TypeVar("T") + P = ParamSpec('P', default=...) + U = TypeVar("U", default=float) + self.assertEqual(P.__default__, ...) + class A(Generic[T, P, U]): ... + self.assertEqual(A[float].__args__, (float, ..., float)) + self.assertEqual(A[float, [range]].__args__, (float, (range,), float)) + self.assertEqual(A[float, [range], int].__args__, (float, (range,), int)) + def test_typevartuple_none(self): U = TypeVarTuple('U') U_None = TypeVarTuple('U_None', default=None) diff --git a/Lib/typing.py b/Lib/typing.py index 03d5357d4cf51a..25234d2d707dd2 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1113,7 +1113,7 @@ def _paramspec_prepare_subst(self, alias, args): params = alias.__parameters__ i = params.index(self) if i == len(args) and self.has_default(): - args = [*args, self.__default__] + args = (*args, self.__default__) if i >= len(args): raise TypeError(f"Too few arguments for {alias}") # Special case where Z[[int, str, bool]] == Z[int, str, bool] in PEP 612. diff --git a/Misc/NEWS.d/next/Library/2025-09-13-12-19-17.gh-issue-138859.PxjIoN.rst b/Misc/NEWS.d/next/Library/2025-09-13-12-19-17.gh-issue-138859.PxjIoN.rst new file mode 100644 index 00000000000000..a5d4dd042fcd5b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-13-12-19-17.gh-issue-138859.PxjIoN.rst @@ -0,0 +1 @@ +Fix generic type parameterization raising a :exc:`TypeError` when omitting a :class:`ParamSpec` that has a default which is not a list of types. From 5f357f3b0de0d937afb0154d0df7e0298c2523cf Mon Sep 17 00:00:00 2001 From: wangxiaolei Date: Thu, 16 Oct 2025 23:02:38 +0800 Subject: [PATCH 3/4] gh-140078: fix typo in tkinter docs (#140081) Remove extraneous word. --- Doc/library/tkinter.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/tkinter.rst b/Doc/library/tkinter.rst index f284988daf2d4e..be26af02bcee0d 100644 --- a/Doc/library/tkinter.rst +++ b/Doc/library/tkinter.rst @@ -392,7 +392,7 @@ by spaces. Without getting into too many details, notice the following: * Operations which are implemented as separate *commands* in Tcl (like ``grid`` or ``destroy``) are represented as *methods* on Tkinter widget objects. As you'll see shortly, at other times Tcl uses what appear to be - method calls on widget objects, which more closely mirror what would is + method calls on widget objects, which more closely mirror what is used in Tkinter. From 459d493ce3288cda7dcebb868970b199764502f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maurycy=20Paw=C5=82owski-Wiero=C5=84ski?= <5383+maurycy@users.noreply.github.com> Date: Thu, 16 Oct 2025 19:24:34 +0200 Subject: [PATCH 4/4] gh-140149: Use PyBytesWriter in _build_concatenated_bytes() (#140150) Use PyBytesWriter in action_helpers.c _build_concatenated_bytes(). 3x faster bytes concat in the parser. Co-authored-by: Victor Stinner --- ...-10-15-17-12-32.gh-issue-140149.cy1m3d.rst | 2 + Parser/action_helpers.c | 39 ++++++++++++++++--- 2 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-10-15-17-12-32.gh-issue-140149.cy1m3d.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-15-17-12-32.gh-issue-140149.cy1m3d.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-15-17-12-32.gh-issue-140149.cy1m3d.rst new file mode 100644 index 00000000000000..e98e28802cfee9 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-15-17-12-32.gh-issue-140149.cy1m3d.rst @@ -0,0 +1,2 @@ +Speed up parsing bytes literals concatenation by using PyBytesWriter API and +a single memory allocation (about 3x faster). diff --git a/Parser/action_helpers.c b/Parser/action_helpers.c index 57e46b4399c66d..b7a5b9d5e307b1 100644 --- a/Parser/action_helpers.c +++ b/Parser/action_helpers.c @@ -1612,19 +1612,46 @@ _build_concatenated_bytes(Parser *p, asdl_expr_seq *strings, int lineno, Py_ssize_t len = asdl_seq_LEN(strings); assert(len > 0); - PyObject* res = Py_GetConstant(Py_CONSTANT_EMPTY_BYTES); - /* Bytes literals never get a kind, but just for consistency since they are represented as Constant nodes, we'll mirror the same behavior as unicode strings for determining the kind. */ - PyObject* kind = asdl_seq_GET(strings, 0)->v.Constant.kind; + PyObject *kind = asdl_seq_GET(strings, 0)->v.Constant.kind; + + Py_ssize_t total = 0; + for (Py_ssize_t i = 0; i < len; i++) { + expr_ty elem = asdl_seq_GET(strings, i); + PyObject *bytes = elem->v.Constant.value; + Py_ssize_t part = PyBytes_GET_SIZE(bytes); + if (part > PY_SSIZE_T_MAX - total) { + PyErr_NoMemory(); + return NULL; + } + total += part; + } + + PyBytesWriter *writer = PyBytesWriter_Create(total); + if (writer == NULL) { + return NULL; + } + char *out = PyBytesWriter_GetData(writer); + for (Py_ssize_t i = 0; i < len; i++) { expr_ty elem = asdl_seq_GET(strings, i); - PyBytes_Concat(&res, elem->v.Constant.value); + PyObject *bytes = elem->v.Constant.value; + Py_ssize_t part = PyBytes_GET_SIZE(bytes); + if (part > 0) { + memcpy(out, PyBytes_AS_STRING(bytes), part); + out += part; + } } - if (!res || _PyArena_AddPyObject(arena, res) < 0) { - Py_XDECREF(res); + + PyObject *res = PyBytesWriter_Finish(writer); + if (res == NULL) { + return NULL; + } + if (_PyArena_AddPyObject(arena, res) < 0) { + Py_DECREF(res); return NULL; } return _PyAST_Constant(res, kind, lineno, col_offset, end_lineno, end_col_offset, p->arena);