diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst
index ef09639189a6c2..400a05758c9517 100644
--- a/Doc/c-api/init_config.rst
+++ b/Doc/c-api/init_config.rst
@@ -116,15 +116,14 @@ Error Handling
Get the *config* exit code.
- * Set *\*exitcode* and return ``1`` if *config* has an exit code set.
- * Return ``0`` if *config* has no exit code set.
+ Return ``0`` and leave *\*exitcode* unchanged.
- Only the ``Py_InitializeFromInitConfig()`` function can set an exit
- code if the ``parse_argv`` option is non-zero.
+ In Python 3.15, :c:func:`Py_InitializeFromInitConfig` sets an exit code if a
+ command line option wants to exit Python. This is no longer the case in
+ Python 3.16. Instead, the option is now processed in :c:func:`Py_RunMain`.
+ This function became useless.
- An exit code can be set when parsing the command line failed (exit
- code ``2``) or when a command line option asks to display the command
- line help (exit code ``0``).
+ .. deprecated:: next
Get Options
@@ -247,10 +246,11 @@ Initialize Python
* Return ``0`` on success.
* Set an error in *config* and return ``-1`` on error.
- * Set an exit code in *config* and return ``-1`` if Python wants to
- exit.
- See ``PyInitConfig_GetExitcode()`` for the exit code case.
+ .. versionchanged:: next
+ The function no longer sets an exit code if a command line option wants
+ to exit Python. Instead, the option is now processed in
+ :c:func:`Py_RunMain`.
.. _pyinitconfig-opts:
@@ -690,9 +690,6 @@ Example of customized Python always running in isolated mode::
exception:
PyConfig_Clear(&config);
- if (PyStatus_IsExit(status)) {
- return status.exitcode;
- }
/* Display the error message and exit the process with
non-zero exit code */
Py_ExitStatusException(status);
@@ -758,6 +755,8 @@ PyStatus
Exit code. Argument passed to ``exit()``.
+ .. deprecated:: next
+
.. c:member:: const char *err_msg
Error message.
@@ -788,6 +787,11 @@ PyStatus
Exit Python with the specified exit code.
+ .. deprecated:: next
+ :c:func:`Py_InitializeFromConfig` no longer sets an exit code if a
+ command line option wants to exit Python. Instead, the option is
+ now processed in :c:func:`Py_RunMain`.
+
Functions to handle a status:
.. c:function:: int PyStatus_Exception(PyStatus status)
@@ -803,6 +807,11 @@ PyStatus
Is the result an exit?
+ .. deprecated:: next
+ :c:func:`Py_InitializeFromConfig` no longer sets an exit code if a
+ command line option wants to exit Python. Instead, the option is
+ now processed in :c:func:`Py_RunMain`.
+
.. c:function:: void Py_ExitStatusException(PyStatus status)
Call ``exit(exitcode)`` if *status* is an exit. Print the error
diff --git a/Doc/c-api/interp-lifecycle.rst b/Doc/c-api/interp-lifecycle.rst
index bd4125cacdd0e6..efd208263bdf33 100644
--- a/Doc/c-api/interp-lifecycle.rst
+++ b/Doc/c-api/interp-lifecycle.rst
@@ -127,6 +127,11 @@ Initializing and finalizing the interpreter
interpreter, populating the runtime configuration structure, and querying
the returned status structure.
+ .. versionchanged:: next
+ The function no longer returns an exit code if a command line option
+ wants to exit Python. Instead, the option is processed in
+ :c:func:`Py_RunMain`.
+
.. c:function:: int Py_IsInitialized()
diff --git a/Doc/c-api/slice.rst b/Doc/c-api/slice.rst
index c6d761fe7fd1c9..ce2b64a8aca853 100644
--- a/Doc/c-api/slice.rst
+++ b/Doc/c-api/slice.rst
@@ -53,6 +53,7 @@ Slice Objects
length *length*, and store the length of the slice in *slicelength*. Out
of bounds indices are clipped in a manner consistent with the handling of
normal slices.
+ *length* must not be negative.
Return ``0`` on success and ``-1`` on error with an exception set.
@@ -108,6 +109,10 @@ Slice Objects
Out of bounds indices are clipped in a manner consistent with the handling
of normal slices.
+ *length* must not be negative.
+ *step* must not be zero and must not be less than ``-PY_SSIZE_T_MAX``,
+ as guaranteed by :c:func:`PySlice_Unpack`.
+
Return the length of the slice. Always successful. Doesn't call Python
code.
diff --git a/Doc/howto/free-threading-python.rst b/Doc/howto/free-threading-python.rst
index 53bea1db191d76..676fffac9d33ee 100644
--- a/Doc/howto/free-threading-python.rst
+++ b/Doc/howto/free-threading-python.rst
@@ -79,10 +79,7 @@ The free-threaded build of CPython aims to provide similar thread-safety
behavior at the Python level to the default GIL-enabled build. Built-in
types like :class:`dict`, :class:`list`, and :class:`set` use internal locks
to protect against concurrent modifications in ways that behave similarly to
-the GIL. However, Python has not historically guaranteed specific behavior for
-concurrent modifications to these built-in types, so this should be treated
-as a description of the current implementation, not a guarantee of current or
-future behavior.
+the GIL. See :ref:`threadsafety` for the guarantees provided by built-in types.
.. note::
diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst
index fc5302d875fc1f..c88c10030fb369 100644
--- a/Doc/library/argparse.rst
+++ b/Doc/library/argparse.rst
@@ -2099,7 +2099,7 @@ Mutual exclusion
>>> group.add_argument('--bar', action='store_false')
>>> parser.parse_args([])
usage: PROG [-h] (--foo | --bar)
- PROG: error: one of the arguments --foo --bar is required
+ PROG: error: one of the following arguments is required: --foo, --bar
Note that currently mutually exclusive argument groups do not support the
*title* and *description* arguments of
diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst
index 3d3125ad17b126..7a1f9410c87fc7 100644
--- a/Doc/whatsnew/3.16.rst
+++ b/Doc/whatsnew/3.16.rst
@@ -1128,6 +1128,14 @@ Deprecated C APIs
and :c:func:`PyAsyncGen_New` are deprecated.
They are scheduled for removal in 3.18.
+* Deprecate :c:func:`PyInitConfig_GetExitCode`, :c:func:`PyStatus_Exit`,
+ and :c:func:`PyStatus_IsExit` functions, and :c:member:`PyStatus.exitcode`
+ member. :c:func:`Py_InitializeFromInitConfig` and
+ :c:func:`Py_InitializeFromConfig` can no longer return an exit code.
+ Instead, if a command line option wants to exit Python, the option is
+ now processed in :c:func:`Py_RunMain`.
+ (Contributed by Victor Stinner in :gh:`158080`.)
+
* :c:func:`PyModule_GetFilename` is no longer deprecated, but using
:c:func:`PyModule_GetFilenameObject` instead is still recommended.
(Contributed by Victor Stinner in :gh:`154757`.)
diff --git a/Include/cpython/initconfig.h b/Include/cpython/initconfig.h
index 1ccc496c63ac78..846e05fb26a4d8 100644
--- a/Include/cpython/initconfig.h
+++ b/Include/cpython/initconfig.h
@@ -11,19 +11,19 @@ typedef struct {
enum {
_PyStatus_TYPE_OK=0,
_PyStatus_TYPE_ERROR=1,
- _PyStatus_TYPE_EXIT=2
+ _PyStatus_TYPE_EXIT=2 // deprecated
} _type;
const char *func;
const char *err_msg;
- int exitcode;
+ Py_DEPRECATED(3.16) int exitcode;
} PyStatus;
PyAPI_FUNC(PyStatus) PyStatus_Ok(void);
PyAPI_FUNC(PyStatus) PyStatus_Error(const char *err_msg);
PyAPI_FUNC(PyStatus) PyStatus_NoMemory(void);
-PyAPI_FUNC(PyStatus) PyStatus_Exit(int exitcode);
+Py_DEPRECATED(3.16) PyAPI_FUNC(PyStatus) PyStatus_Exit(int exitcode);
PyAPI_FUNC(int) PyStatus_IsError(PyStatus err);
-PyAPI_FUNC(int) PyStatus_IsExit(PyStatus err);
+Py_DEPRECATED(3.16) PyAPI_FUNC(int) PyStatus_IsExit(PyStatus err);
PyAPI_FUNC(int) PyStatus_Exception(PyStatus err);
/* --- PyWideStringList ------------------------------------------------ */
@@ -242,6 +242,11 @@ typedef struct PyConfig {
// PYTHON_PRESITE=package.module or -X presite=package.module
wchar_t *run_presite;
#endif
+
+ // If a command line option wants to exit Python, store it in this member
+ // and only process the option in Py_RunMain() instead of PyConfig_Read().
+ // If equals to 0, there is no option.
+ int _deferred_cmdline_option;
} PyConfig;
PyAPI_FUNC(void) PyConfig_InitPythonConfig(PyConfig *config);
@@ -293,7 +298,7 @@ PyAPI_FUNC(void) PyInitConfig_Free(PyInitConfig *config);
PyAPI_FUNC(int) PyInitConfig_GetError(PyInitConfig* config,
const char **err_msg);
-PyAPI_FUNC(int) PyInitConfig_GetExitCode(PyInitConfig* config,
+Py_DEPRECATED(3.16) PyAPI_FUNC(int) PyInitConfig_GetExitCode(PyInitConfig* config,
int *exitcode);
PyAPI_FUNC(int) PyInitConfig_HasOption(PyInitConfig *config,
diff --git a/Include/cpython/object.h b/Include/cpython/object.h
index 0ef52d4d2bc7b4..c0fcfc363ef90c 100644
--- a/Include/cpython/object.h
+++ b/Include/cpython/object.h
@@ -523,7 +523,8 @@ _Py_ThreadId(void)
#elif defined(__MINGW32__) && defined(_M_IX86)
tid = __readfsdword(24);
#elif defined(__MINGW32__) && defined(_M_ARM64)
- tid = __getReg(18);
+ // x18 is the Windows ARM64 platform register and points to the TEB.
+ __asm__ ("mov %0, x18" : "=r" (tid));
#elif defined(__i386__)
__asm__("{movl %%gs:0, %0|mov %0, dword ptr gs:[0]}" : "=r" (tid)); // 32-bit always uses GS
#elif defined(__MACH__) && defined(__x86_64__)
diff --git a/Include/internal/pycore_initconfig.h b/Include/internal/pycore_initconfig.h
index 183b2d45c5ede1..50df2efdd4e526 100644
--- a/Include/internal/pycore_initconfig.h
+++ b/Include/internal/pycore_initconfig.h
@@ -182,6 +182,8 @@ extern PyObject* _PyConfig_CreateXOptionsDict(const PyConfig *config);
extern void _Py_DumpPathConfig(PyThreadState *tstate);
+extern int _PyConfig_ProcessDeferredCmdlineOption(PyConfig *config);
+
/* --- Function used for testing ---------------------------------- */
diff --git a/Lib/argparse.py b/Lib/argparse.py
index 38e1c0d0ed78fd..4cf5dca148c969 100644
--- a/Lib/argparse.py
+++ b/Lib/argparse.py
@@ -2499,8 +2499,8 @@ def consume_positionals(start_index):
names = [_get_action_name(action)
for action in group._group_actions
if action.help is not SUPPRESS]
- msg = _('one of the arguments %s is required')
- raise ArgumentError(None, msg % ' '.join(names))
+ msg = _('one of the following arguments is required: %s')
+ raise ArgumentError(None, msg % ', '.join(names))
# return the updated namespace and the extra arguments
return namespace, extras
diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py
index 75beb5ede13fef..3abdb3244570a5 100644
--- a/Lib/test/test_argparse.py
+++ b/Lib/test/test_argparse.py
@@ -6866,7 +6866,7 @@ def test_required_exclusive(self):
args = parser.parse_intermixed_args('1 --foo 2'.split())
self.assertEqual(NS(badger=['1', '2'], foo=True, spam=None), args)
self.assertRaisesRegex(argparse.ArgumentError,
- 'one of the arguments --foo --spam is required',
+ 'one of the following arguments is required: --foo, --spam',
parser.parse_intermixed_args, '1 2'.split())
self.assertEqual(group.required, True)
@@ -6882,7 +6882,7 @@ def test_required_exclusive_with_positional(self):
args = parser.parse_intermixed_args(['a', 'b'])
self.assertEqual(NS(foo=False, spam=None, badger=['a', 'b']), args)
self.assertRaisesRegex(argparse.ArgumentError,
- 'one of the arguments --foo --spam badger is required',
+ 'one of the following arguments is required: --foo, --spam, badger',
parser.parse_intermixed_args, [])
self.assertRaisesRegex(argparse.ArgumentError,
'argument badger: not allowed with argument --foo',
@@ -7258,7 +7258,7 @@ def test_required_mutually_exclusive_args(self):
group.add_argument('--bar')
group.add_argument('--baz')
self.assertRaisesRegex(argparse.ArgumentError,
- 'one of the arguments --bar --baz is required',
+ 'one of the following arguments is required: --bar, --baz',
self.parser.parse_args, [])
def test_conflicting_mutually_exclusive_args_optional_with_metavar(self):
diff --git a/Lib/test/test_capi/test_slice.py b/Lib/test/test_capi/test_slice.py
new file mode 100644
index 00000000000000..3f41b55a609f48
--- /dev/null
+++ b/Lib/test/test_capi/test_slice.py
@@ -0,0 +1,307 @@
+import sys
+import unittest
+from test.support import import_helper
+
+_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
+
+NULL = None
+SSIZE_MAX = sys.maxsize
+SSIZE_MIN = -sys.maxsize - 1
+
+VALUES = [None, 0, 1, 3, 7, -1, -3, -7]
+STEPS = [None, 1, 3, 5, -1, -3, -5]
+LENGTHS = [0, 1, 3, 10]
+
+
+class Index:
+ def __init__(self, value):
+ self.value = value
+
+ def __index__(self):
+ return self.value
+
+
+class BadIndex:
+ def __index__(self):
+ raise RuntimeError('bad index')
+
+
+class PopIndex:
+ # __index__() removes the last item of the list.
+ def __init__(self, value, seq):
+ self.value = value
+ self.seq = seq
+
+ def __index__(self):
+ self.seq.pop()
+ return self.value
+
+
+class SliceTest(unittest.TestCase):
+
+ def test_check(self):
+ # Test PySlice_Check()
+ check = _testlimitedcapi.slice_check
+ self.assertTrue(check(slice(1, 7, 2)))
+ self.assertFalse(check(object()))
+
+ # CRASHES check(NULL)
+
+ def test_new(self):
+ # Test PySlice_New()
+ new = _testlimitedcapi.slice_new
+ self.assertEqual(new(1, 7, 2), slice(1, 7, 2))
+ self.assertEqual(new(7, 1, -2), slice(7, 1, -2))
+ self.assertEqual(new('a', 'b', 'c'), slice('a', 'b', 'c'))
+ self.assertEqual(new(NULL, NULL, NULL), slice(None, None, None))
+
+ def test_getindices(self):
+ # Test PySlice_GetIndices()
+ getindices = _testlimitedcapi.slice_getindices
+ self.assertEqual(getindices(slice(1, 7, 2), 10), (1, 7, 2))
+ self.assertEqual(getindices(slice(None), 10), (0, 10, 1))
+ self.assertEqual(getindices(slice(None, None, -1), 10), (9, -1, -1))
+ self.assertEqual(getindices(slice(-3, -1), 10), (7, 9, 1))
+ self.assertEqual(getindices(slice(-1, -3, -1), 10), (9, 7, -1))
+ self.assertEqual(getindices(slice(None, None, -2), 0), (-1, -1, -2))
+ self.assertEqual(getindices(slice(-3, -5, 1), 0), (-3, -5, 1))
+
+ # It fails without setting an exception for out of bounds indices,
+ # a zero step and non-integer indices.
+ self.assertIsNone(getindices(slice(1, 11), 10))
+ self.assertIsNone(getindices(slice(10, 1), 10))
+ self.assertIsNone(getindices(slice(1, 7, 0), 10))
+ self.assertIsNone(getindices(slice(Index(1)), 10))
+ self.assertIsNone(getindices(slice('a'), 10))
+ self.assertIsNone(getindices(slice(1, 'a'), 10))
+ self.assertIsNone(getindices(slice(1, 7, 'a'), 10))
+
+ # Negative length is not supported, but does not fail.
+ self.assertIsNone(getindices(slice(None), -3))
+ self.assertIsNone(getindices(slice(1, 7, 2), -3))
+ self.assertEqual(getindices(slice(-10, -5, 1), -3), (-13, -8, 1))
+ self.assertEqual(getindices(slice(-5, -10, -2), -3), (-8, -13, -2))
+
+ # CRASHES getindices(NULL, 10)
+ # CRASHES getindices(object(), 10)
+
+ def test_unpack(self):
+ # Test PySlice_Unpack()
+ unpack = _testlimitedcapi.slice_unpack
+ self.assertEqual(unpack(slice(1, 7)), (1, 7, 1))
+ self.assertEqual(unpack(slice(1, 7, 2)), (1, 7, 2))
+ self.assertEqual(unpack(slice(7, 1, -2)), (7, 1, -2))
+ self.assertEqual(unpack(slice(None, 7, 2)), (0, 7, 2))
+ self.assertEqual(unpack(slice(None, 7, -2)), (SSIZE_MAX, 7, -2))
+ self.assertEqual(unpack(slice(1, None, 2)), (1, SSIZE_MAX, 2))
+ self.assertEqual(unpack(slice(1, None, -2)), (1, SSIZE_MIN, -2))
+ self.assertEqual(unpack(slice(None)), (0, SSIZE_MAX, 1))
+ self.assertEqual(unpack(slice(None, None, -1)),
+ (SSIZE_MAX, SSIZE_MIN, -1))
+ # Negative indices are not adjusted.
+ self.assertEqual(unpack(slice(-3, -1)), (-3, -1, 1))
+ self.assertEqual(unpack(slice(Index(1), Index(7), Index(2))),
+ (1, 7, 2))
+
+ # Values which do not fit in Py_ssize_t are silently clipped.
+ self.assertEqual(unpack(slice(1, 2**1000)), (1, SSIZE_MAX, 1))
+ self.assertEqual(unpack(slice(1, -2**1000)), (1, SSIZE_MIN, 1))
+ self.assertEqual(unpack(slice(2**1000, 7)), (SSIZE_MAX, 7, 1))
+ self.assertEqual(unpack(slice(-2**1000, 7)), (SSIZE_MIN, 7, 1))
+ self.assertEqual(unpack(slice(1, 7, 2**1000)), (1, 7, SSIZE_MAX))
+ # The step is boosted to -PY_SSIZE_T_MAX, not PY_SSIZE_T_MIN, so
+ # that negating it is safe.
+ self.assertEqual(unpack(slice(7, 1, -2**1000)), (7, 1, -SSIZE_MAX))
+ self.assertEqual(unpack(slice(7, 1, SSIZE_MIN)), (7, 1, -SSIZE_MAX))
+
+ with self.assertRaisesRegex(ValueError, 'slice step cannot be zero'):
+ unpack(slice(1, 1, 0))
+ with self.assertRaisesRegex(TypeError,
+ 'slice indices must be integers'):
+ unpack(slice('a', 7))
+ with self.assertRaisesRegex(TypeError,
+ 'slice indices must be integers'):
+ unpack(slice(1, 'a'))
+ with self.assertRaisesRegex(TypeError,
+ 'slice indices must be integers'):
+ unpack(slice(1, 7, 'a'))
+ with self.assertRaisesRegex(RuntimeError, 'bad index'):
+ unpack(slice(BadIndex(), 7))
+ with self.assertRaisesRegex(RuntimeError, 'bad index'):
+ unpack(slice(1, BadIndex()))
+ with self.assertRaisesRegex(RuntimeError, 'bad index'):
+ unpack(slice(1, 7, BadIndex()))
+
+ # CRASHES unpack(NULL)
+ # CRASHES unpack(object())
+
+ def test_adjustindices(self):
+ # Test PySlice_AdjustIndices()
+ adjust = _testlimitedcapi.slice_adjustindices
+ self.assertEqual(adjust(10, 1, 7, 1), (6, 1, 7))
+ self.assertEqual(adjust(10, 1, 7, 2), (3, 1, 7))
+ self.assertEqual(adjust(10, 7, 1, -1), (6, 7, 1))
+ self.assertEqual(adjust(10, 7, 1, -2), (3, 7, 1))
+ # An empty slice keeps the adjusted indices.
+ self.assertEqual(adjust(10, 7, 1, 1), (0, 7, 1))
+ self.assertEqual(adjust(10, 1, 7, -1), (0, 1, 7))
+
+ # Negative indices are added to the length.
+ self.assertEqual(adjust(10, -9, -3, 1), (6, 1, 7))
+ self.assertEqual(adjust(10, -3, -9, -1), (6, 7, 1))
+
+ # Out of bounds indices are clipped.
+ self.assertEqual(adjust(10, -100, 100, 1), (10, 0, 10))
+ self.assertEqual(adjust(10, 100, -100, -1), (10, 9, -1))
+ self.assertEqual(adjust(10, SSIZE_MIN, SSIZE_MAX, 1), (10, 0, 10))
+ self.assertEqual(adjust(10, SSIZE_MAX, SSIZE_MIN, -1), (10, 9, -1))
+ self.assertEqual(adjust(0, 1, 7, 1), (0, 0, 0))
+ self.assertEqual(adjust(0, 7, 1, -1), (0, -1, -1))
+
+ # The returned length is the length of the corresponding range.
+ for length in LENGTHS:
+ for start in VALUES[1:]:
+ for stop in VALUES[1:]:
+ for step in STEPS[1:]:
+ with self.subTest(length=length, start=start,
+ stop=stop, step=step):
+ slicelength, start2, stop2 = adjust(length, start,
+ stop, step)
+ self.assertEqual(slicelength,
+ len(range(start2, stop2, step)))
+
+ # Negative length is not supported, but does not fail.
+ self.assertEqual(adjust(-3, 1, 7, 1), (0, -3, -3))
+ self.assertEqual(adjust(-3, 7, 1, -1), (0, -4, -4))
+ self.assertEqual(adjust(-3, -10, -5, 1), (0, 0, 0))
+
+ # The step is asserted to be neither zero nor less than
+ # -PY_SSIZE_T_MAX.
+ # CRASHES adjust(10, 0, 10, 0)
+ # CRASHES adjust(10, 0, 10, SSIZE_MIN)
+
+
+class GetIndicesExMacroTest(unittest.TestCase):
+ # PySlice_GetIndicesEx() is a macro using PySlice_Unpack() and
+ # PySlice_AdjustIndices(). It is also a deprecated function, exported
+ # for the stable ABI.
+ getindicesex = staticmethod(_testlimitedcapi.slice_getindicesex_macro)
+ getindicesex_seq = staticmethod(
+ _testlimitedcapi.slice_getindicesex_seq_macro)
+ # The macro evaluates the length after calling PySlice_Unpack(), so the
+ # size of the list after removing an item is used.
+ resized = (6, 8, 1, 2)
+
+ def test_getindicesex(self):
+ # Test PySlice_GetIndicesEx()
+ getindicesex = self.getindicesex
+ self.assertEqual(getindicesex(slice(1, 7, 2), 10), (1, 7, 2, 3))
+ self.assertEqual(getindicesex(slice(7, 1, -2), 10), (7, 1, -2, 3))
+ self.assertEqual(getindicesex(slice(Index(1), Index(7), Index(2)), 10),
+ (1, 7, 2, 3))
+
+ # The result agrees with slice.indices() and the slice length is
+ # the length of the corresponding range.
+ for length in LENGTHS:
+ for start in VALUES:
+ for stop in VALUES:
+ for step in STEPS:
+ s = slice(start, stop, step)
+ with self.subTest(slice=s, length=length):
+ indices = s.indices(length)
+ self.assertEqual(getindicesex(s, length),
+ indices + (len(range(*indices)),))
+
+ # Negative indices are added to the length.
+ self.assertEqual(getindicesex(slice(-9, -3), 10), (1, 7, 1, 6))
+ self.assertEqual(getindicesex(slice(-3, -9, -1), 10), (7, 1, -1, 6))
+
+ # Out of bounds indices are clipped.
+ self.assertEqual(getindicesex(slice(-100, 100), 10), (0, 10, 1, 10))
+ self.assertEqual(getindicesex(slice(100, -100, -1), 10),
+ (9, -1, -1, 10))
+ self.assertEqual(getindicesex(slice(None), 0), (0, 0, 1, 0))
+ self.assertEqual(getindicesex(slice(1, 7, 2), 0), (0, 0, 2, 0))
+ self.assertEqual(getindicesex(slice(None, None, -1), 0),
+ (-1, -1, -1, 0))
+
+ # Indices which do not fit in Py_ssize_t are clipped, not rejected.
+ # Note that slice.indices() does not clip the step.
+ self.assertEqual(getindicesex(slice(1, 2**1000), 10), (1, 10, 1, 9))
+ self.assertEqual(getindicesex(slice(2**1000, 7), 10), (10, 7, 1, 0))
+ self.assertEqual(getindicesex(slice(1, 7, 2**1000), 10),
+ (1, 7, SSIZE_MAX, 1))
+ # -PY_SSIZE_T_MAX-1 is replaced with -PY_SSIZE_T_MAX.
+ self.assertEqual(getindicesex(slice(7, 1, -2**1000), 10),
+ (7, 1, -SSIZE_MAX, 1))
+ self.assertEqual(getindicesex(slice(7, 1, SSIZE_MIN), 10),
+ (7, 1, -SSIZE_MAX, 1))
+
+ with self.assertRaisesRegex(ValueError, 'slice step cannot be zero'):
+ getindicesex(slice(1, 7, 0), 10)
+ with self.assertRaisesRegex(TypeError,
+ 'slice indices must be integers'):
+ getindicesex(slice('a', 7), 10)
+ with self.assertRaisesRegex(TypeError,
+ 'slice indices must be integers'):
+ getindicesex(slice(1, 'a'), 10)
+ with self.assertRaisesRegex(TypeError,
+ 'slice indices must be integers'):
+ getindicesex(slice(1, 7, 'a'), 10)
+ with self.assertRaisesRegex(RuntimeError, 'bad index'):
+ getindicesex(slice(BadIndex(), 7), 10)
+ with self.assertRaisesRegex(RuntimeError, 'bad index'):
+ getindicesex(slice(1, BadIndex()), 10)
+ with self.assertRaisesRegex(RuntimeError, 'bad index'):
+ getindicesex(slice(1, 7, BadIndex()), 10)
+
+ # Negative length is not supported, but does not fail.
+ self.assertEqual(getindicesex(slice(None), -3), (-3, -3, 1, 0))
+ self.assertEqual(getindicesex(slice(1, 7, 2), -3), (-3, -3, 2, 0))
+ self.assertEqual(getindicesex(slice(7, 1, -2), -3), (-4, -4, -2, 0))
+
+ # CRASHES getindicesex(NULL, 10)
+ # CRASHES getindicesex(object(), 10)
+
+ def test_getindicesex_seq(self):
+ # The length is the size of a sequence.
+ getindicesex_seq = self.getindicesex_seq
+ seq = list(range(10))
+ self.assertEqual(getindicesex_seq(slice(-3, -1), seq), (7, 9, 1, 2))
+ self.assertEqual(getindicesex_seq(slice(-3, -1), []), (0, 0, 1, 0))
+
+ # gh-72054: __index__() can resize the sequence. Negative indices
+ # are adjusted by the length, so the result depends on when it is
+ # evaluated.
+ seq = list(range(10))
+ self.assertEqual(getindicesex_seq(slice(PopIndex(-3, seq), -1), seq),
+ self.resized)
+ self.assertEqual(len(seq), 9, seq)
+
+ seq = list(range(10))
+ self.assertEqual(getindicesex_seq(slice(-3, PopIndex(-1, seq)), seq),
+ self.resized)
+ self.assertEqual(len(seq), 9, seq)
+
+ seq = list(range(10))
+ self.assertEqual(
+ getindicesex_seq(slice(-3, -1, PopIndex(1, seq)), seq),
+ self.resized)
+ self.assertEqual(len(seq), 9, seq)
+
+ # CRASHES getindicesex_seq(slice(None), NULL)
+ # CRASHES getindicesex_seq(slice(None), object())
+
+
+class GetIndicesExFuncTest(GetIndicesExMacroTest):
+ # The deprecated function is equivalent to the macro, except that the
+ # length is evaluated before the call.
+ getindicesex = staticmethod(_testlimitedcapi.slice_getindicesex_func)
+ getindicesex_seq = staticmethod(
+ _testlimitedcapi.slice_getindicesex_seq_func)
+ # The size of the list before removing an item is used.
+ resized = (7, 9, 1, 2)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py
index 4c1abb15c0cb14..476a481a0ba54b 100644
--- a/Lib/test/test_cmd_line.py
+++ b/Lib/test/test_cmd_line.py
@@ -133,11 +133,16 @@ def test_site_flag(self):
@support.cpython_only
def test_version(self):
- version = ('Python %d.%d' % sys.version_info[:2]).encode("ascii")
- for switch in '-V', '--version', '-VV':
- rc, out, err = assert_python_ok(switch)
- self.assertNotStartsWith(err, version)
- self.assertStartsWith(out, version)
+ short_version = ('Python %d.%d' % sys.version_info[:2])
+ for switch in ('-V', '--version'):
+ with self.subTest(switch=switch):
+ rc, out, err = assert_python_ok(switch)
+ self.assertStartsWith(out, short_version.encode())
+ self.assertEqual(err, b'')
+
+ rc, out, err = assert_python_ok('-VV')
+ self.assertEqual(out.rstrip(), f"Python {sys.version}".encode())
+ self.assertEqual(err, b'')
def test_verbose(self):
# -v causes imports to write to stderr. If the write to
diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py
index 77b231323cc275..39485afcdb0f57 100644
--- a/Lib/test/test_embed.py
+++ b/Lib/test/test_embed.py
@@ -783,6 +783,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'use_frozen_modules': not support.Py_DEBUG,
'safe_path': False,
'_is_python_build': IGNORE_CONFIG,
+ '_deferred_cmdline_option': 0,
}
if Py_STATS:
CONFIG_COMPAT['_pystats'] = False
diff --git a/Lib/test/test_multibytecodec.py b/Lib/test/test_multibytecodec.py
index 6b032fc8604eef..0cd822fef87400 100644
--- a/Lib/test/test_multibytecodec.py
+++ b/Lib/test/test_multibytecodec.py
@@ -265,6 +265,15 @@ def test_iso2022(self):
self.assertRaises(UnicodeDecodeError, decoder.decode, b'', True)
self.assertEqual(decoder.decode(b'B@$'), '\u4e16')
+ def test_hz_keep_buffer(self):
+ # A trailing '~' shouldn't read past the end of the input.
+ decoder = codecs.getincrementaldecoder('hz')()
+ self.assertEqual(decoder.decode(b'~'), '')
+ self.assertRaises(UnicodeDecodeError, decoder.decode, b'', True)
+ self.assertEqual(decoder.decode(b'~'), '~')
+ self.assertEqual(decoder.decode(b'~'), '')
+ self.assertEqual(decoder.decode(b'\n', True), '')
+
def test_decode_unicode(self):
# Trying to decode a unicode string should raise a TypeError
for enc in ALL_CJKENCODINGS:
diff --git a/Lib/test/translationdata/argparse/msgids.txt b/Lib/test/translationdata/argparse/msgids.txt
index ae89ac74726ecf..8a2305b89a5667 100644
--- a/Lib/test/translationdata/argparse/msgids.txt
+++ b/Lib/test/translationdata/argparse/msgids.txt
@@ -18,7 +18,7 @@ invalid %(type)s value: %(value)r
invalid choice: %(value)r (choose from %(choices)s)
invalid choice: %(value)r, maybe you meant %(closest)r? (choose from %(choices)s)
not allowed with argument %s
-one of the arguments %s is required
+one of the following arguments is required: %s
option '%(option)s' is deprecated
options
positional arguments
diff --git a/Misc/NEWS.d/next/C_API/2026-09-24-15-27-35.gh-issue-158080.YIqypf.rst b/Misc/NEWS.d/next/C_API/2026-09-24-15-27-35.gh-issue-158080.YIqypf.rst
new file mode 100644
index 00000000000000..4413b328d474e4
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2026-09-24-15-27-35.gh-issue-158080.YIqypf.rst
@@ -0,0 +1,6 @@
+Deprecate :c:func:`PyInitConfig_GetExitCode`, :c:func:`PyStatus_Exit`, and
+:c:func:`PyStatus_IsExit` functions, and :c:member:`PyStatus.exitcode`
+member. :c:func:`Py_InitializeFromInitConfig` and
+:c:func:`Py_InitializeFromConfig` can no longer return an exit code.
+Instead, if a command line option wants to exit Python, the option is
+now processed in :c:func:`Py_RunMain`. Patch by Victor Stinner.
diff --git a/Misc/NEWS.d/next/Library/2026-09-08-00-31-56.gh-issue-157127.gduPd0.rst b/Misc/NEWS.d/next/Library/2026-09-08-00-31-56.gh-issue-157127.gduPd0.rst
new file mode 100644
index 00000000000000..614c85fafa264a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-09-08-00-31-56.gh-issue-157127.gduPd0.rst
@@ -0,0 +1,3 @@
+Reword the :mod:`argparse` error message reported for a missing required
+mutually exclusive group to ``one of the following arguments is required:
+--foo, --bar``, so that the argument names are separated by commas.
diff --git a/Misc/NEWS.d/next/Library/2026-09-11-16-50-11.gh-issue-157325.hzTild.rst b/Misc/NEWS.d/next/Library/2026-09-11-16-50-11.gh-issue-157325.hzTild.rst
new file mode 100644
index 00000000000000..a5adf101c9dad3
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-09-11-16-50-11.gh-issue-157325.hzTild.rst
@@ -0,0 +1,2 @@
+Fix an out-of-bounds read in the ``hz`` incremental decoder when the input
+ends with ``~``.
diff --git a/Misc/NEWS.d/next/Windows/2026-09-10-02-00-00.gh-issue-157212.x18TEB.rst b/Misc/NEWS.d/next/Windows/2026-09-10-02-00-00.gh-issue-157212.x18TEB.rst
new file mode 100644
index 00000000000000..8d0970516242f9
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2026-09-10-02-00-00.gh-issue-157212.x18TEB.rst
@@ -0,0 +1,3 @@
+Fix ``_Py_ThreadId()`` compilation on Windows ARM64 with MinGW toolchains,
+where ``__getReg()`` is unavailable. The TEB address is now read directly
+from ``x18``. Patch by Christian Aurich.
diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
index 696fec1290e5f3..0ca1d90ac4f30b 100644
--- a/Modules/Setup.stdlib.in
+++ b/Modules/Setup.stdlib.in
@@ -174,7 +174,7 @@
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tokenizer.c _testinternalcapi/tuple.c _testinternalcapi/typecache.c
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c _testcapi/weakref.c _testcapi/marshal.c
-@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/capsule.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c _testlimitedcapi/run.c _testlimitedcapi/type.c _testlimitedcapi/hash.c
+@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/capsule.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slice.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c _testlimitedcapi/run.c _testlimitedcapi/type.c _testlimitedcapi/hash.c
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
diff --git a/Modules/_testlimitedcapi.c b/Modules/_testlimitedcapi.c
index b30e32c56c5704..0d290eb5ef5b9e 100644
--- a/Modules/_testlimitedcapi.c
+++ b/Modules/_testlimitedcapi.c
@@ -58,6 +58,9 @@ module_exec(PyObject *mod)
if (_PyTestLimitedCAPI_Init_Set(mod) < 0) {
return -1;
}
+ if (_PyTestLimitedCAPI_Init_Slice(mod) < 0) {
+ return -1;
+ }
if (_PyTestLimitedCAPI_Init_Slots(mod) < 0) {
return -1;
}
diff --git a/Modules/_testlimitedcapi/parts.h b/Modules/_testlimitedcapi/parts.h
index e8a2d82fb94aea..ab6f76f3d06699 100644
--- a/Modules/_testlimitedcapi/parts.h
+++ b/Modules/_testlimitedcapi/parts.h
@@ -40,6 +40,7 @@ int _PyTestLimitedCAPI_Init_List(PyObject *module);
int _PyTestLimitedCAPI_Init_Long(PyObject *module);
int _PyTestLimitedCAPI_Init_PyOS(PyObject *module);
int _PyTestLimitedCAPI_Init_Set(PyObject *module);
+int _PyTestLimitedCAPI_Init_Slice(PyObject *module);
int _PyTestLimitedCAPI_Init_Slots(PyObject *module);
int _PyTestLimitedCAPI_Init_Sys(PyObject *module);
int _PyTestLimitedCAPI_Init_ThreadState(PyObject *module);
diff --git a/Modules/_testlimitedcapi/slice.c b/Modules/_testlimitedcapi/slice.c
new file mode 100644
index 00000000000000..febb3d0de3cfa6
--- /dev/null
+++ b/Modules/_testlimitedcapi/slice.c
@@ -0,0 +1,240 @@
+#include "pyconfig.h" // Py_GIL_DISABLED
+#ifdef Py_GIL_DISABLED
+# define Py_TARGET_ABI3T 0x030f0000
+#else
+ // Need limited C API 3.6.1 for PySlice_Unpack() and PySlice_AdjustIndices()
+ // and for PySlice_GetIndicesEx() implemented as a macro.
+# define Py_LIMITED_API 0x03060100
+#endif
+
+#include "parts.h"
+#include "util.h"
+
+
+static PyObject *
+slice_check(PyObject *Py_UNUSED(module), PyObject *obj)
+{
+ NULLABLE(obj);
+ return PyLong_FromLong(PySlice_Check(obj));
+}
+
+static PyObject *
+slice_new(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ PyObject *start, *stop, *step;
+
+ if (!PyArg_ParseTuple(args, "OOO", &start, &stop, &step)) {
+ return NULL;
+ }
+ NULLABLE(start);
+ NULLABLE(stop);
+ NULLABLE(step);
+ return PySlice_New(start, stop, step);
+}
+
+/* Returns the (start, stop, step) triple on success. If PySlice_GetIndices()
+ * fails without setting an exception, returns None. */
+static PyObject *
+slice_getindices(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ PyObject *slice;
+ Py_ssize_t length;
+ Py_ssize_t start = UNINITIALIZED_SIZE;
+ Py_ssize_t stop = UNINITIALIZED_SIZE;
+ Py_ssize_t step = UNINITIALIZED_SIZE;
+
+ if (!PyArg_ParseTuple(args, "On", &slice, &length)) {
+ return NULL;
+ }
+ NULLABLE(slice);
+ if (PySlice_GetIndices(slice, length, &start, &stop, &step) < 0) {
+ if (PyErr_Occurred()) {
+ return NULL;
+ }
+ Py_RETURN_NONE;
+ }
+ assert(!PyErr_Occurred());
+ assert(start != UNINITIALIZED_SIZE);
+ assert(stop != UNINITIALIZED_SIZE);
+ assert(step != UNINITIALIZED_SIZE);
+ return Py_BuildValue("nnn", start, stop, step);
+}
+
+/* Test PySlice_GetIndicesEx() implemented as a macro using PySlice_Unpack()
+ * and PySlice_AdjustIndices(). */
+static PyObject *
+slice_getindicesex_macro(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ PyObject *slice;
+ Py_ssize_t length = UNINITIALIZED_SIZE;
+ Py_ssize_t start = UNINITIALIZED_SIZE;
+ Py_ssize_t stop = UNINITIALIZED_SIZE;
+ Py_ssize_t step = UNINITIALIZED_SIZE;
+ Py_ssize_t slicelength = UNINITIALIZED_SIZE;
+
+ if (!PyArg_ParseTuple(args, "On", &slice, &length)) {
+ return NULL;
+ }
+ NULLABLE(slice);
+ if (PySlice_GetIndicesEx(slice, length,
+ &start, &stop, &step, &slicelength) < 0) {
+ assert(PyErr_Occurred());
+ /* The macro sets the slice length to 0 on error. */
+ assert(slicelength == 0);
+ return NULL;
+ }
+ assert(!PyErr_Occurred());
+ assert(start != UNINITIALIZED_SIZE);
+ assert(stop != UNINITIALIZED_SIZE);
+ assert(step != UNINITIALIZED_SIZE);
+ assert(slicelength != UNINITIALIZED_SIZE);
+ return Py_BuildValue("nnnn", start, stop, step, slicelength);
+}
+
+/* Same as slice_getindicesex_macro(), but the length is the size of a sequence.
+ * The macro evaluates it after calling PySlice_Unpack(), which can execute
+ * arbitrary Python code and resize the sequence. */
+static PyObject *
+slice_getindicesex_seq_macro(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ PyObject *slice, *seq;
+ Py_ssize_t start = UNINITIALIZED_SIZE;
+ Py_ssize_t stop = UNINITIALIZED_SIZE;
+ Py_ssize_t step = UNINITIALIZED_SIZE;
+ Py_ssize_t slicelength = UNINITIALIZED_SIZE;
+
+ if (!PyArg_ParseTuple(args, "OO", &slice, &seq)) {
+ return NULL;
+ }
+ NULLABLE(slice);
+ NULLABLE(seq);
+ if (PySlice_GetIndicesEx(slice, Py_SIZE(seq),
+ &start, &stop, &step, &slicelength) < 0) {
+ assert(PyErr_Occurred());
+ assert(slicelength == 0);
+ return NULL;
+ }
+ assert(!PyErr_Occurred());
+ return Py_BuildValue("nnnn", start, stop, step, slicelength);
+}
+
+static PyObject *
+slice_unpack(PyObject *Py_UNUSED(module), PyObject *slice)
+{
+ Py_ssize_t start = UNINITIALIZED_SIZE;
+ Py_ssize_t stop = UNINITIALIZED_SIZE;
+ Py_ssize_t step = UNINITIALIZED_SIZE;
+
+ NULLABLE(slice);
+ if (PySlice_Unpack(slice, &start, &stop, &step) < 0) {
+ assert(PyErr_Occurred());
+ return NULL;
+ }
+ assert(!PyErr_Occurred());
+ assert(start != UNINITIALIZED_SIZE);
+ assert(stop != UNINITIALIZED_SIZE);
+ assert(step != UNINITIALIZED_SIZE);
+ return Py_BuildValue("nnn", start, stop, step);
+}
+
+static PyObject *
+slice_adjustindices(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ Py_ssize_t length, start, stop, step;
+
+ if (!PyArg_ParseTuple(args, "nnnn", &length, &start, &stop, &step)) {
+ return NULL;
+ }
+ Py_ssize_t slicelength = PySlice_AdjustIndices(length, &start, &stop, step);
+ assert(!PyErr_Occurred());
+ return Py_BuildValue("nnn", slicelength, start, stop);
+}
+
+#undef PySlice_GetIndicesEx
+
+/* Test the deprecated PySlice_GetIndicesEx() function. It is still exported
+ * for the stable ABI and used if Py_LIMITED_API is older than 3.5.4. */
+static PyObject *
+slice_getindicesex_func(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ PyObject *slice;
+ Py_ssize_t length;
+ Py_ssize_t start = UNINITIALIZED_SIZE;
+ Py_ssize_t stop = UNINITIALIZED_SIZE;
+ Py_ssize_t step = UNINITIALIZED_SIZE;
+ Py_ssize_t slicelength = UNINITIALIZED_SIZE;
+
+ if (!PyArg_ParseTuple(args, "On", &slice, &length)) {
+ return NULL;
+ }
+ NULLABLE(slice);
+// Ignore deprecation warnings
+_Py_COMP_DIAG_PUSH
+_Py_COMP_DIAG_IGNORE_DEPR_DECLS
+ int res = PySlice_GetIndicesEx(slice, length,
+ &start, &stop, &step, &slicelength);
+_Py_COMP_DIAG_POP
+ if (res < 0) {
+ assert(PyErr_Occurred());
+ return NULL;
+ }
+ assert(!PyErr_Occurred());
+ assert(start != UNINITIALIZED_SIZE);
+ assert(stop != UNINITIALIZED_SIZE);
+ assert(step != UNINITIALIZED_SIZE);
+ assert(slicelength != UNINITIALIZED_SIZE);
+ return Py_BuildValue("nnnn", start, stop, step, slicelength);
+}
+
+
+/* Same as slice_getindicesex_seq_macro(), but using the deprecated function.
+ * The length is evaluated before the call. */
+static PyObject *
+slice_getindicesex_seq_func(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ PyObject *slice, *seq;
+ Py_ssize_t start = UNINITIALIZED_SIZE;
+ Py_ssize_t stop = UNINITIALIZED_SIZE;
+ Py_ssize_t step = UNINITIALIZED_SIZE;
+ Py_ssize_t slicelength = UNINITIALIZED_SIZE;
+
+ if (!PyArg_ParseTuple(args, "OO", &slice, &seq)) {
+ return NULL;
+ }
+ NULLABLE(slice);
+ NULLABLE(seq);
+// Ignore deprecation warnings
+_Py_COMP_DIAG_PUSH
+_Py_COMP_DIAG_IGNORE_DEPR_DECLS
+ int res = PySlice_GetIndicesEx(slice, Py_SIZE(seq),
+ &start, &stop, &step, &slicelength);
+_Py_COMP_DIAG_POP
+ if (res < 0) {
+ assert(PyErr_Occurred());
+ return NULL;
+ }
+ assert(!PyErr_Occurred());
+ return Py_BuildValue("nnnn", start, stop, step, slicelength);
+}
+
+
+static PyMethodDef test_methods[] = {
+ {"slice_check", slice_check, METH_O},
+ {"slice_new", slice_new, METH_VARARGS},
+ {"slice_getindices", slice_getindices, METH_VARARGS},
+ {"slice_getindicesex_macro", slice_getindicesex_macro, METH_VARARGS},
+ {"slice_getindicesex_seq_macro", slice_getindicesex_seq_macro, METH_VARARGS},
+ {"slice_getindicesex_func", slice_getindicesex_func,
+ METH_VARARGS},
+ {"slice_getindicesex_seq_func", slice_getindicesex_seq_func,
+ METH_VARARGS},
+ {"slice_unpack", slice_unpack, METH_O},
+ {"slice_adjustindices", slice_adjustindices, METH_VARARGS},
+ {NULL},
+};
+
+int
+_PyTestLimitedCAPI_Init_Slice(PyObject *m)
+{
+ return PyModule_AddFunctions(m, test_methods);
+}
diff --git a/Modules/cjkcodecs/_codecs_cn.c b/Modules/cjkcodecs/_codecs_cn.c
index e2c7908c9bb275..dc89f1d899e36b 100644
--- a/Modules/cjkcodecs/_codecs_cn.c
+++ b/Modules/cjkcodecs/_codecs_cn.c
@@ -414,9 +414,9 @@ DECODER(hz)
Py_UCS4 decoded;
if (c == '~') {
+ REQUIRE_INBUF(2);
unsigned char c2 = INBYTE2;
- REQUIRE_INBUF(2);
if (c2 == '~' && state->c[CN_STATE_OFFSET] == 0)
OUTCHAR('~');
else if (c2 == '{' && state->c[CN_STATE_OFFSET] == 0)
diff --git a/Modules/main.c b/Modules/main.c
index ef22331760906c..44a025d500e35e 100644
--- a/Modules/main.c
+++ b/Modules/main.c
@@ -749,13 +749,21 @@ pymain_set_path0(PyObject *main_importer_path)
static void
pymain_run_python(int *exitcode)
{
- int set_running_main = 0;
-
- PyObject *main_importer_path = NULL;
PyInterpreterState *interp = _PyInterpreterState_GET();
/* pymain_repl() and pymain_run_stdin() modify the config */
PyConfig *config = (PyConfig*)_PyInterpreterState_GetConfig(interp);
+ // Process command line options which want to exit Python
+ int cmdline_exitcode = _PyConfig_ProcessDeferredCmdlineOption(config);
+ if (cmdline_exitcode >= 0) {
+ *exitcode = cmdline_exitcode;
+ return;
+ }
+
+ int set_running_main = 0;
+
+ PyObject *main_importer_path = NULL;
+
/* ensure path config is written into global variables */
PyStatus status = _PyPathConfig_UpdateGlobal(config);
if (_PyStatus_EXCEPTION(status)) {
@@ -910,10 +918,7 @@ static int
pymain_main(_PyArgv *args)
{
PyStatus status = pymain_init(args);
- if (_PyStatus_IS_EXIT(status)) {
- pymain_free();
- return status.exitcode;
- }
+ assert(!_PyStatus_IS_EXIT(status));
if (_PyStatus_EXCEPTION(status)) {
pymain_exit_error(status);
}
diff --git a/PCbuild/_testlimitedcapi.vcxproj b/PCbuild/_testlimitedcapi.vcxproj
index 4218e0ed3945d2..2995dbb469ed3d 100644
--- a/PCbuild/_testlimitedcapi.vcxproj
+++ b/PCbuild/_testlimitedcapi.vcxproj
@@ -110,6 +110,7 @@
+
diff --git a/PCbuild/_testlimitedcapi.vcxproj.filters b/PCbuild/_testlimitedcapi.vcxproj.filters
index ddef60e599d485..92f1c5dedbd62e 100644
--- a/PCbuild/_testlimitedcapi.vcxproj.filters
+++ b/PCbuild/_testlimitedcapi.vcxproj.filters
@@ -25,6 +25,7 @@
+
diff --git a/Programs/_bootstrap_python.c b/Programs/_bootstrap_python.c
index d30ef8c879d815..c65d616afbd501 100644
--- a/Programs/_bootstrap_python.c
+++ b/Programs/_bootstrap_python.c
@@ -104,9 +104,6 @@ main(int argc, char **argv)
error:
PyConfig_Clear(&config);
- if (PyStatus_IsExit(status)) {
- return status.exitcode;
- }
Py_ExitStatusException(status);
}
diff --git a/Programs/_testembed.c b/Programs/_testembed.c
index 79e817829c1594..592c23756201f0 100644
--- a/Programs/_testembed.c
+++ b/Programs/_testembed.c
@@ -1722,7 +1722,7 @@ static int test_initconfig_api(void)
goto error;
}
- // Set a list of UTF-8 strings (argv)
+ // Set a list of UTF-8 strings (xoptions)
char* xoptions[] = {"faulthandler"};
if (PyInitConfig_SetStrList(config, "xoptions",
Py_ARRAY_LENGTH(xoptions), xoptions) < 0) {
@@ -1814,29 +1814,25 @@ static int test_initconfig_get_api(void)
static int test_initconfig_exit(void)
{
+ // -h command line option is stored as PyConfig._deferred_cmdline_option
PyInitConfig *config = PyInitConfig_Create();
if (config == NULL) {
printf("Init allocation error\n");
return 1;
}
- char *argv[] = {PROGRAM_NAME_UTF8, "--help"};
+ char *argv[] = {PROGRAM_NAME_UTF8, "-h"};
assert(PyInitConfig_SetStrList(config, "argv",
Py_ARRAY_LENGTH(argv), argv) == 0);
-
assert(PyInitConfig_SetInt(config, "parse_argv", 1) == 0);
- assert(Py_InitializeFromInitConfig(config) < 0);
-
- int exitcode;
- assert(PyInitConfig_GetExitCode(config, &exitcode) == 1);
- assert(exitcode == 0);
+ assert(Py_InitializeFromInitConfig(config) == 0);
+ PyInitConfig_Free(config);
- const char *err_msg;
- assert(PyInitConfig_GetError(config, &err_msg) == 1);
- assert(strcmp(err_msg, "exit code 0") == 0);
+ const PyConfig *rt_config = _Py_GetConfig();
+ assert(rt_config->_deferred_cmdline_option == 'h');
- PyInitConfig_Free(config);
+ Py_Finalize();
return 0;
}
diff --git a/Python/getopt.c b/Python/getopt.c
index 79bea2359ffffc..7e918189c716a9 100644
--- a/Python/getopt.c
+++ b/Python/getopt.c
@@ -41,10 +41,10 @@ static const wchar_t *opt_ptr = L"";
static const _PyOS_LongOption longopts[] = {
/* name, has_arg, val (used in switch in initconfig.c) */
- {L"check-hash-based-pycs", 1, 0},
- {L"help-all", 0, 1},
- {L"help-env", 0, 2},
- {L"help-xoptions", 0, 3},
+ {L"check-hash-based-pycs", 1, 1},
+ {L"help-all", 0, 2},
+ {L"help-env", 0, 3},
+ {L"help-xoptions", 0, 4},
{NULL, 0, -1}, /* sentinel */
};
diff --git a/Python/initconfig.c b/Python/initconfig.c
index d683fdd6abc6e1..6de12db9d600ec 100644
--- a/Python/initconfig.c
+++ b/Python/initconfig.c
@@ -204,6 +204,7 @@ static const PyConfigSpec PYCONFIG_SPEC[] = {
SPEC(module_search_paths_set, BOOL, INIT_ONLY, NO_SYS),
SPEC(pythonpath_env, WSTR_OPT, INIT_ONLY, NO_SYS),
SPEC(sys_path_0, WSTR_OPT, INIT_ONLY, NO_SYS),
+ SPEC(_deferred_cmdline_option, INT, INIT_ONLY, NO_SYS),
// Array terminator
{NULL, 0, 0, 0, NO_SYS},
@@ -2996,13 +2997,17 @@ static PyStatus
config_parse_cmdline(PyConfig *config, PyWideStringList *warnoptions,
Py_ssize_t *opt_index)
{
+ // Only store the first option
+#define DEFER_OPTION(OPTION) \
+ do { \
+ if (config->_deferred_cmdline_option == 0) { \
+ config->_deferred_cmdline_option = (OPTION); \
+ } \
+ } while (0)
+
PyStatus status;
const PyWideStringList *argv = &config->argv;
int print_version = 0;
- const wchar_t* program = config->program_name;
- if (!program && argv->length >= 1) {
- program = argv->items[0];
- }
_PyOS_ResetGetOpt();
do {
@@ -3045,7 +3050,7 @@ config_parse_cmdline(PyConfig *config, PyWideStringList *warnoptions,
switch (c) {
// Integers represent long options, see Python/getopt.c
- case 0:
+ case 1:
// check-hash-based-pycs
if (wcscmp(_PyOS_optarg, L"always") == 0
|| wcscmp(_PyOS_optarg, L"never") == 0
@@ -3057,27 +3062,24 @@ config_parse_cmdline(PyConfig *config, PyWideStringList *warnoptions,
return status;
}
} else {
- fprintf(stderr, "--check-hash-based-pycs must be one of "
- "'default', 'always', or 'never'\n");
- config_usage(1, program);
- return _PyStatus_EXIT(2);
+ DEFER_OPTION(c);
}
break;
- case 1:
+ case 2:
// help-all
- config_complete_usage(program);
- return _PyStatus_EXIT(0);
+ DEFER_OPTION(c);
+ break;
- case 2:
+ case 3:
// help-env
- config_envvars_usage();
- return _PyStatus_EXIT(0);
+ DEFER_OPTION(c);
+ break;
- case 3:
+ case 4:
// help-xoptions
- config_xoptions_usage();
- return _PyStatus_EXIT(0);
+ DEFER_OPTION(c);
+ break;
case 'b':
config->bytes_warning++;
@@ -3136,8 +3138,8 @@ config_parse_cmdline(PyConfig *config, PyWideStringList *warnoptions,
case 'h':
case '?':
- config_usage(0, program);
- return _PyStatus_EXIT(0);
+ DEFER_OPTION(c);
+ break;
case 'V':
print_version++;
@@ -3162,15 +3164,16 @@ config_parse_cmdline(PyConfig *config, PyWideStringList *warnoptions,
default:
/* unknown argument: parsing failed */
- config_usage(1, program);
- return _PyStatus_EXIT(2);
+ DEFER_OPTION(c);
+ break;
}
- } while (1);
+ } while (config->_deferred_cmdline_option == 0);
- if (print_version) {
- printf("Python %s\n",
- (print_version >= 2) ? Py_GetVersion() : PY_VERSION);
- return _PyStatus_EXIT(0);
+ if (print_version >= 2) {
+ DEFER_OPTION('W');
+ }
+ else if (print_version >= 1) {
+ DEFER_OPTION('V');
}
if (config->run_command == NULL && config->run_module == NULL
@@ -3192,6 +3195,72 @@ config_parse_cmdline(PyConfig *config, PyWideStringList *warnoptions,
*opt_index = _PyOS_optind;
return _PyStatus_OK();
+
+#undef DEFER_OPTION
+}
+
+
+int
+_PyConfig_ProcessDeferredCmdlineOption(PyConfig *config)
+{
+ int c = config->_deferred_cmdline_option;
+ config->_deferred_cmdline_option = 0;
+ if (c == 0) {
+ // There is no deferred option
+ return -1;
+ }
+
+ // Select the program name
+ const PyWideStringList *argv = &config->argv;
+ const wchar_t* program = config->program_name;
+ if (!program && argv->length >= 1) {
+ program = argv->items[0];
+ }
+ if (!program) {
+ program = L"python";
+ }
+
+ switch (c) {
+ case 'h':
+ case '?':
+ config_usage(0, program);
+ return 0;
+
+ case 'V':
+ printf("Python %s\n", PY_VERSION);
+ return 0;
+
+ case 'W': // -VV or more -V options
+ printf("Python %s\n", Py_GetVersion());
+ return 0;
+
+ // Integers represent long options, see Python/getopt.c
+ case 1:
+ // check-hash-based-pycs
+ fprintf(stderr, "--check-hash-based-pycs must be one of "
+ "'default', 'always', or 'never'\n");
+ config_usage(1, program);
+ return 2;
+
+ case 2:
+ // help-all
+ config_complete_usage(program);
+ return 0;
+
+ case 3:
+ // help-env
+ config_envvars_usage();
+ return 0;
+
+ case 4:
+ // help-xoptions
+ config_xoptions_usage();
+ return 0;
+
+ default:
+ config_usage(1, program);
+ return 2;
+ }
}
@@ -3634,7 +3703,6 @@ config_argv0_add_exe(PyConfig *config)
* Command line arguments
* Environment variables
- * Py_xxx global configuration variables
The only side effects are to modify config and to call _Py_SetArgcArgv(). */
PyStatus
@@ -3966,22 +4034,7 @@ PyInitConfig_Free(PyInitConfig *config)
int
PyInitConfig_GetError(PyInitConfig* config, const char **perr_msg)
{
- if (_PyStatus_IS_EXIT(config->status)) {
- char buffer[22]; // len("exit code -2147483648\0")
- PyOS_snprintf(buffer, sizeof(buffer),
- "exit code %i",
- config->status.exitcode);
-
- if (config->err_msg != NULL) {
- free(config->err_msg);
- }
- config->err_msg = strdup(buffer);
- if (config->err_msg != NULL) {
- *perr_msg = config->err_msg;
- return 1;
- }
- config->status = _PyStatus_NO_MEMORY();
- }
+ assert(!_PyStatus_IS_EXIT(config->status));
if (_PyStatus_IS_ERROR(config->status) && config->status.err_msg != NULL) {
*perr_msg = config->status.err_msg;
@@ -3997,13 +4050,8 @@ PyInitConfig_GetError(PyInitConfig* config, const char **perr_msg)
int
PyInitConfig_GetExitCode(PyInitConfig* config, int *exitcode)
{
- if (_PyStatus_IS_EXIT(config->status)) {
- *exitcode = config->status.exitcode;
- return 1;
- }
- else {
- return 0;
- }
+ assert(!_PyStatus_IS_EXIT(config->status));
+ return 0;
}
diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
index a9c98d73fd0f72..8888f523559123 100644
--- a/Python/pylifecycle.c
+++ b/Python/pylifecycle.c
@@ -3830,10 +3830,9 @@ _Py_FatalRefcountErrorFunc(const char *func, const char *msg)
void _Py_NO_RETURN
Py_ExitStatusException(PyStatus status)
{
- if (_PyStatus_IS_EXIT(status)) {
- exit(status.exitcode);
- }
- else if (_PyStatus_IS_ERROR(status)) {
+ assert(!_PyStatus_IS_EXIT(status));
+
+ if (_PyStatus_IS_ERROR(status)) {
fatal_error(fileno(stderr), 1, status.func, status.err_msg, 1);
}
else {