`.
- The *html* argument no longer supported.
+ The *html* argument is no longer supported.
.. method:: close()
diff --git a/Doc/reference/import.rst b/Doc/reference/import.rst
index 7de995b12702ec..0b9d1c233d182a 100644
--- a/Doc/reference/import.rst
+++ b/Doc/reference/import.rst
@@ -544,7 +544,7 @@ the module.
It is **strongly** recommended that you rely on :attr:`__spec__` and
its attributes instead of any of the other individual attributes
-listed below.
+listed below, except :attr:`__name__`.
.. attribute:: __name__
@@ -596,6 +596,10 @@ listed below.
Raise :exc:`DeprecationWarning` instead of :exc:`ImportWarning`
when falling back to ``__package__``.
+ .. deprecated-removed:: 3.13 3.15
+ ``__package__`` will cease to be set or taken into consideration
+ by the import system or standard library.
+
.. attribute:: __spec__
@@ -653,6 +657,10 @@ listed below.
It is **strongly** recommended that you rely on :attr:`__spec__`
instead of ``__cached__``.
+ .. deprecated-removed:: 3.13 3.15
+ ``__cached__`` will cease to be set or taken into consideration
+ by the import system or standard library.
+
.. _package-path-rules:
module.__path__
diff --git a/Doc/tools/extensions/c_annotations.py b/Doc/tools/extensions/c_annotations.py
index a65cf71e4affe3..50065d34a2c27a 100644
--- a/Doc/tools/extensions/c_annotations.py
+++ b/Doc/tools/extensions/c_annotations.py
@@ -124,10 +124,7 @@ def add_annotations(app: Sphinx, doctree: nodes.document) -> None:
continue
if not par[0].get("ids", None):
continue
- name = par[0]["ids"][0]
- if name.startswith("c."):
- name = name[2:]
-
+ name = par[0]["ids"][0].removeprefix("c.")
objtype = par["objtype"]
# Stable ABI annotation.
diff --git a/Doc/tools/extensions/patchlevel.py b/Doc/tools/extensions/patchlevel.py
index f2df6db47a2227..53ea1bf47b8fd3 100644
--- a/Doc/tools/extensions/patchlevel.py
+++ b/Doc/tools/extensions/patchlevel.py
@@ -74,4 +74,4 @@ def get_version_info():
if __name__ == "__main__":
- print(format_version_info(get_header_version_info())[1])
+ print(format_version_info(get_header_version_info())[0])
diff --git a/Doc/tools/templates/download.html b/Doc/tools/templates/download.html
index f69adc71f937c4..c978e61b16a49e 100644
--- a/Doc/tools/templates/download.html
+++ b/Doc/tools/templates/download.html
@@ -1,13 +1,15 @@
{% extends "layout.html" %}
{% set title = _('Download') %}
{% if daily is defined %}
- {% set dlbase = pathto('archives', 1) %}
+ {% set dl_base = pathto('archives', resource=True) %}
+ {% set dl_version = version %}
{% else %}
{#
The link below returns HTTP 404 until the first related alpha release.
This is expected; use daily documentation builds for CPython development.
#}
- {% set dlbase = 'https://docs.python.org/ftp/python/doc/' + release %}
+ {% set dl_base = 'https://www.python.org/ftp/python/doc/' + release %}
+ {% set dl_version = release %}
{% endif %}
{% block body %}
@@ -26,27 +28,27 @@ {% trans %}Download Python {{ release }} Documentation{% endtrans %}
{% trans %}PDF{% endtrans %} |
- {% trans download_size="17" %}Download (ca. {{ download_size }} MiB){% endtrans %} |
- {% trans download_size="17" %}Download (ca. {{ download_size }} MiB){% endtrans %} |
+ {% trans download_size="17" %}Download (ca. {{ download_size }} MiB){% endtrans %} |
+ {% trans download_size="17" %}Download (ca. {{ download_size }} MiB){% endtrans %} |
{% trans %}HTML{% endtrans %} |
- {% trans download_size="13" %}Download (ca. {{ download_size }} MiB){% endtrans %} |
- {% trans download_size="8" %}Download (ca. {{ download_size }} MiB){% endtrans %} |
+ {% trans download_size="13" %}Download (ca. {{ download_size }} MiB){% endtrans %} |
+ {% trans download_size="8" %}Download (ca. {{ download_size }} MiB){% endtrans %} |
{% trans %}Plain text{% endtrans %} |
- {% trans download_size="4" %}Download (ca. {{ download_size }} MiB){% endtrans %} |
- {% trans download_size="3" %}Download (ca. {{ download_size }} MiB){% endtrans %} |
+ {% trans download_size="4" %}Download (ca. {{ download_size }} MiB){% endtrans %} |
+ {% trans download_size="3" %}Download (ca. {{ download_size }} MiB){% endtrans %} |
{% trans %}Texinfo{% endtrans %} |
- {% trans download_size="9" %}Download (ca. {{ download_size }} MiB){% endtrans %} |
- {% trans download_size="7" %}Download (ca. {{ download_size }} MiB){% endtrans %} |
+ {% trans download_size="9" %}Download (ca. {{ download_size }} MiB){% endtrans %} |
+ {% trans download_size="7" %}Download (ca. {{ download_size }} MiB){% endtrans %} |
{% trans %}EPUB{% endtrans %} |
- {% trans download_size="6" %}Download (ca. {{ download_size }} MiB){% endtrans %} |
+ {% trans download_size="6" %}Download (ca. {{ download_size }} MiB){% endtrans %} |
|
diff --git a/Doc/using/android.rst b/Doc/using/android.rst
new file mode 100644
index 00000000000000..957705f7f5e189
--- /dev/null
+++ b/Doc/using/android.rst
@@ -0,0 +1,65 @@
+.. _using-android:
+
+=======================
+Using Python on Android
+=======================
+
+Python on Android is unlike Python on desktop platforms. On a desktop platform,
+Python is generally installed as a system resource that can be used by any user
+of that computer. Users then interact with Python by running a :program:`python`
+executable and entering commands at an interactive prompt, or by running a
+Python script.
+
+On Android, there is no concept of installing as a system resource. The only unit
+of software distribution is an "app". There is also no console where you could
+run a :program:`python` executable, or interact with a Python REPL.
+
+As a result, the only way you can use Python on Android is in embedded mode – that
+is, by writing a native Android application, embedding a Python interpreter
+using ``libpython``, and invoking Python code using the :ref:`Python embedding
+API `. The full Python interpreter, the standard library, and all
+your Python code is then packaged into your app for its own private use.
+
+The Python standard library has some notable omissions and restrictions on
+Android. See the :ref:`API availability guide ` for
+details.
+
+Adding Python to an Android app
+-------------------------------
+
+These instructions are only needed if you're planning to compile Python for
+Android yourself. Most users should *not* need to do this. Instead, use one of
+the following tools, which will provide a much easier experience:
+
+* `Briefcase `__, from the BeeWare project
+* `Buildozer `__, from the Kivy project
+* `Chaquopy `__
+* `pyqtdeploy `__
+* `Termux `__
+
+If you're sure you want to do all of this manually, read on. You can use the
+:source:`testbed app ` as a guide; each step below contains a
+link to the relevant file.
+
+* Build Python by following the instructions in :source:`Android/README.md`.
+
+* Add code to your :source:`build.gradle `
+ file to copy the following items into your project. All except your own Python
+ code can be copied from ``cross-build/HOST/prefix/lib``:
+
+ * In your JNI libraries:
+
+ * ``libpython*.*.so``
+ * ``lib*_python.so`` (external libraries such as OpenSSL)
+
+ * In your assets:
+
+ * ``python*.*`` (the Python standard library)
+ * ``python*.*/site-packages`` (your own Python code)
+
+* Add code to your app to :source:`extract the assets to the filesystem
+ `.
+
+* Add code to your app to :source:`start Python in embedded mode
+ `. This will need to be C code
+ called via JNI.
diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst
index 10adf744c7ff52..4976418ba33cf8 100644
--- a/Doc/using/configure.rst
+++ b/Doc/using/configure.rst
@@ -183,12 +183,6 @@ General Options
See :envvar:`PYTHONCOERCECLOCALE` and the :pep:`538`.
-.. option:: --without-freelists
-
- Disable all freelists except the empty tuple singleton.
-
- .. versionadded:: 3.11
-
.. option:: --with-platlibdir=DIRNAME
Python library directory name (default is ``lib``).
diff --git a/Doc/using/index.rst b/Doc/using/index.rst
index f55a12f1ab8a0d..90fdfc0bec0583 100644
--- a/Doc/using/index.rst
+++ b/Doc/using/index.rst
@@ -12,11 +12,13 @@ interpreter and things that make working with Python easier.
.. toctree::
:numbered:
+ :maxdepth: 2
cmdline.rst
unix.rst
configure.rst
windows.rst
mac.rst
+ android.rst
ios.rst
editors.rst
diff --git a/Doc/using/ios.rst b/Doc/using/ios.rst
index 455221c8c3180e..4d4eb2031ee980 100644
--- a/Doc/using/ios.rst
+++ b/Doc/using/ios.rst
@@ -63,7 +63,7 @@ Standard library availability
-----------------------------
The Python standard library has some notable omissions and restrictions on
-iOS. See the :ref:`API availability guide for iOS ` for
+iOS. See the :ref:`API availability guide for iOS ` for
details.
Binary extension modules
diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst
index d59f24406c9483..e5c6d7cd308504 100644
--- a/Doc/whatsnew/3.11.rst
+++ b/Doc/whatsnew/3.11.rst
@@ -2168,7 +2168,7 @@ Build Changes
(Contributed by Donghee Na and Brett Holman in :issue:`44340`.)
* Freelists for object structs can now be disabled. A new :program:`configure`
- option :option:`--without-freelists` can be used to disable all freelists
+ option ``--without-freelists`` can be used to disable all freelists
except empty tuple singleton.
(Contributed by Christian Heimes in :issue:`45522`.)
diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst
index 4d1a10155c4617..5640759e79b734 100644
--- a/Doc/whatsnew/3.13.rst
+++ b/Doc/whatsnew/3.13.rst
@@ -3,7 +3,7 @@
What's New In Python 3.13
****************************
-:Editor: Thomas Wouters
+:Editors: Adam Turner and Thomas Wouters
.. Rules for maintenance:
@@ -53,12 +53,6 @@ For full details, see the :ref:`changelog `.
:pep:`719` -- Python 3.13 Release Schedule
-.. note::
-
- Prerelease users should be aware that this document is currently in draft
- form. It will be updated substantially as Python 3.13 moves towards release,
- so it's worth checking back even after reading earlier versions.
-
Summary -- Release Highlights
=============================
@@ -142,7 +136,7 @@ Significant improvements in the standard library:
* The :mod:`copy` module now has a :func:`copy.replace` function,
with support for many builtin types and any class defining
the :func:`~object.__replace__` method.
-* The :mod:`dbm.sqlite3` module is now the default :mod:`dbm` backend.
+* The new :mod:`dbm.sqlite3` module is now the default :mod:`dbm` backend.
* The :mod:`os` module has a :ref:`suite of new functions `
for working with Linux's timer notification file descriptors.
* The :mod:`random` module now has a :ref:`command-line interface `.
@@ -159,14 +153,15 @@ C API improvements:
* The :doc:`PyTime C API ` has been added,
providing access to system clocks.
* :c:type:`PyMutex` is a new lightweight mutex that occupies a single byte.
-* :doc:`C-API support ` was added for generating :pep:`669` monitoring events.
+* There is a new :ref:`suite of functions `
+ for generating :pep:`669` monitoring events in the C API.
New typing features:
* :pep:`696`: Type parameters (:data:`typing.TypeVar`, :data:`typing.ParamSpec`,
and :data:`typing.TypeVarTuple`) now support defaults.
* :pep:`702`: The new :func:`warnings.deprecated` decorator adds support
- for marking deprecations in the type system.
+ for marking deprecations in the type system and at runtime.
* :pep:`705`: :data:`typing.ReadOnly` can be used to mark an item of a
:class:`typing.TypedDict` as read-only for type checkers.
* :pep:`742`: :data:`typing.TypeIs` provides more intuitive
@@ -176,14 +171,15 @@ Platform support:
* :pep:`730`: Apple's iOS is now an :ref:`officially supported platform
`, at :pep:`tier 3 <11#tier-3>`.
- Official Android support (:pep:`738`) is in the works as well.
+* :pep:`738`: Android is now an :ref:`officially supported platform
+ `, at :pep:`tier 3 <11#tier-3>`.
* ``wasm32-wasi`` is now supported as a :pep:`tier 2 <11#tier-2>` platform.
* ``wasm32-emscripten`` is no longer an officially supported platform.
Important removals:
* :ref:`PEP 594 `: The remaining 19 "dead batteries"
- have been removed from the standard library:
+ (legacy stdlib modules) have been removed from the standard library:
:mod:`!aifc`, :mod:`!audioop`, :mod:`!cgi`, :mod:`!cgitb`, :mod:`!chunk`,
:mod:`!crypt`, :mod:`!imghdr`, :mod:`!mailcap`, :mod:`!msilib`, :mod:`!nis`,
:mod:`!nntplib`, :mod:`!ossaudiodev`, :mod:`!pipes`, :mod:`!sndhdr`,
@@ -191,8 +187,8 @@ Important removals:
* Remove the :program:`2to3` tool and :mod:`!lib2to3` module
(deprecated in Python 3.11).
* Remove the :mod:`!tkinter.tix` module (deprecated in Python 3.6).
-* Remove :func:`!locale.resetlocale`.
-* Remove :mod:`!typing.io` and :mod:`!typing.re`.
+* Remove the :func:`!locale.resetlocale` function.
+* Remove the :mod:`!typing.io` and :mod:`!typing.re` namespaces.
* Remove chained :class:`classmethod` descriptors.
Release schedule changes:
@@ -337,7 +333,7 @@ designed with threading in mind will run faster on multi-core hardware.
expect some bugs and a substantial single-threaded performance hit.
Free-threaded builds of CPython support optionally running with the GIL
enabled at runtime using the environment variable :envvar:`PYTHON_GIL` or
-the command-line option :option:`-X gil`.
+the command-line option :option:`-X gil=1`.
To check if the current interpreter supports free-threading, :option:`python -VV <-V>`
and :attr:`sys.version` contain "experimental free-threading build".
@@ -438,14 +434,14 @@ Defined mutation semantics for :py:func:`locals`
Historically, the expected result of mutating the return value of
:func:`locals` has been left to individual Python implementations to define.
Starting from Python 3.13, :pep:`667` standardises
-the historical behaviour of CPython for most code execution scopes,
+the historical behavior of CPython for most code execution scopes,
but changes :term:`optimized scopes `
(functions, generators, coroutines, comprehensions, and generator expressions)
to explicitly return independent snapshots of the currently assigned local
variables, including locally referenced nonlocal variables captured in closures.
This change to the semantics of :func:`locals` in optimized scopes also
-affects the default behaviour of code execution functions that implicitly
+affects the default behavior of code execution functions that implicitly
target :func:`!locals` if no explicit namespace is provided
(such as :func:`exec` and :func:`eval`).
In previous versions, whether or not changes could be accessed by calling
@@ -494,8 +490,10 @@ is not a tier 3 supported platform, but will have best-effort support.
(PEP written and implementation contributed by Russell Keith-Magee in
:gh:`114099`.)
-:pep:`738`: Android support is being actively worked on,
-but the platform is not yet officially supported.
+:pep:`738`: Android is now a :pep:`11` supported platform, with the
+``aarch64-linux-android`` and ``x86_64-linux-android`` targets at tier 3.
+The 32-bit targets ``arm-linux-androideabi`` and ``i686-linux-android``
+are not tier 3 supported platforms, but will have best-effort support.
(PEP written and implementation contributed by Malcolm Smith in
:gh:`116622`.)
@@ -519,7 +517,7 @@ old generation, instead of collecting one or more generations.
The behavior of :func:`!gc.collect` changes slightly:
-* ``gc.collect(1)``: Performs an increment of GC,
+* ``gc.collect(1)``: Performs an increment of garbage collection,
rather than collecting generation 1.
* Other calls to :func:`!gc.collect` are unchanged.
@@ -609,6 +607,9 @@ Other Language Changes
the value of the *optimize* argument.
(Contributed by Irit Katriel in :gh:`108113`).
+* Add a :attr:`~property.__name__` attribute on :class:`property` objects.
+ (Contributed by Eugene Toder in :gh:`101860`.)
+
* Add :exc:`PythonFinalizationError`, a new exception derived from
:exc:`RuntimeError` and used to signal when operations are blocked
during :term:`finalization `.
@@ -635,9 +636,6 @@ Other Language Changes
the :mod:`bz2`, :mod:`lzma`, :mod:`tarfile`, and :mod:`zipfile` modules.
(Contributed by Serhiy Storchaka in :gh:`115961`.)
-* Add a :attr:`~property.__name__` attribute on :class:`property` objects.
- (Contributed by Eugene Toder in :gh:`101860`.)
-
New Modules
===========
@@ -677,7 +675,7 @@ ast
* The constructors of node types in the :mod:`ast` module are now
stricter in the arguments they accept,
- with more intuitive behaviour when arguments are omitted.
+ with more intuitive behavior when arguments are omitted.
If an optional field on an AST node is not included as an argument when
constructing an instance, the field will now be set to ``None``. Similarly,
@@ -888,7 +886,7 @@ email
return ``('', '')`` pairs in more situations where invalid email addresses
are encountered instead of potentially inaccurate values.
The two functions have a new optional *strict* parameter (default ``True``).
- To get the old behaviour (accepting malformed input), use ``strict=False``.
+ To get the old behavior (accepting malformed input), use ``strict=False``.
``getattr(email.utils, 'supports_strict_parsing', False)`` can be used
to check if the *strict* parameter is available.
(Contributed by Thomas Dwyer and Victor Stinner for :gh:`102988` to improve
@@ -2008,257 +2006,524 @@ C API Changes
New Features
------------
-* You no longer have to define the ``PY_SSIZE_T_CLEAN`` macro before including
- :file:`Python.h` when using ``#`` formats in
- :ref:`format codes `.
- APIs accepting the format codes always use ``Py_ssize_t`` for ``#`` formats.
- (Contributed by Inada Naoki in :gh:`104922`.)
+* Add the :ref:`PyMonitoring C API `
+ for generating :pep:`669` monitoring events:
+
+ * :c:type:`PyMonitoringState`
+ * :c:func:`PyMonitoring_FirePyStartEvent`
+ * :c:func:`PyMonitoring_FirePyResumeEvent`
+ * :c:func:`PyMonitoring_FirePyReturnEvent`
+ * :c:func:`PyMonitoring_FirePyYieldEvent`
+ * :c:func:`PyMonitoring_FireCallEvent`
+ * :c:func:`PyMonitoring_FireLineEvent`
+ * :c:func:`PyMonitoring_FireJumpEvent`
+ * :c:func:`PyMonitoring_FireBranchEvent`
+ * :c:func:`PyMonitoring_FireCReturnEvent`
+ * :c:func:`PyMonitoring_FirePyThrowEvent`
+ * :c:func:`PyMonitoring_FireRaiseEvent`
+ * :c:func:`PyMonitoring_FireCRaiseEvent`
+ * :c:func:`PyMonitoring_FireReraiseEvent`
+ * :c:func:`PyMonitoring_FireExceptionHandledEvent`
+ * :c:func:`PyMonitoring_FirePyUnwindEvent`
+ * :c:func:`PyMonitoring_FireStopIterationEvent`
+ * :c:func:`PyMonitoring_EnterScope`
+ * :c:func:`PyMonitoring_ExitScope`
-* The *keywords* parameter of :c:func:`PyArg_ParseTupleAndKeywords` and
- :c:func:`PyArg_VaParseTupleAndKeywords` now has type :c:expr:`char * const *`
- in C and :c:expr:`const char * const *` in C++, instead of :c:expr:`char **`.
- It makes these functions compatible with arguments of type
- :c:expr:`const char * const *`, :c:expr:`const char **` or
- :c:expr:`char * const *` in C++ and :c:expr:`char * const *` in C
- without an explicit type cast.
- This can be overridden with the :c:macro:`PY_CXX_CONST` macro.
- (Contributed by Serhiy Storchaka in :gh:`65210`.)
+ (Contributed by Irit Katriel in :gh:`111997`).
-* Add :c:func:`PyImport_AddModuleRef`: similar to
- :c:func:`PyImport_AddModule`, but return a :term:`strong reference` instead
- of a :term:`borrowed reference`.
- (Contributed by Victor Stinner in :gh:`105922`.)
+* Add :c:type:`PyMutex`, a lightweight mutex that occupies a single byte,
+ and the new :c:func:`PyMutex_Lock` and :c:func:`PyMutex_Unlock` functions.
+ :c:func:`!PyMutex_Lock` will release the :term:`GIL` (if currently held)
+ if the operation needs to block.
+ (Contributed by Sam Gross in :gh:`108724`.)
-* Add :c:func:`PyWeakref_GetRef` function: similar to
- :c:func:`PyWeakref_GetObject` but returns a :term:`strong reference`, or
- ``NULL`` if the referent is no longer live.
- (Contributed by Victor Stinner in :gh:`105927`.)
+* Add the :ref:`PyTime C API ` to provide access to system clocks:
-* Add :c:func:`PyObject_GetOptionalAttr` and
- :c:func:`PyObject_GetOptionalAttrString`, variants of
- :c:func:`PyObject_GetAttr` and :c:func:`PyObject_GetAttrString` which
- don't raise :exc:`AttributeError` if the attribute is not found.
- These variants are more convenient and faster if the missing attribute
- should not be treated as a failure.
- (Contributed by Serhiy Storchaka in :gh:`106521`.)
+ * :c:type:`PyTime_t`.
+ * :c:var:`PyTime_MIN` and :c:var:`PyTime_MAX`.
+ * :c:func:`PyTime_AsSecondsDouble`.
+ * :c:func:`PyTime_Monotonic`.
+ * :c:func:`PyTime_MonotonicRaw`.
+ * :c:func:`PyTime_PerfCounter`.
+ * :c:func:`PyTime_PerfCounterRaw`.
+ * :c:func:`PyTime_Time`.
+ * :c:func:`PyTime_TimeRaw`.
-* Add :c:func:`PyMapping_GetOptionalItem` and
- :c:func:`PyMapping_GetOptionalItemString`: variants of
- :c:func:`PyObject_GetItem` and :c:func:`PyMapping_GetItemString` which don't
- raise :exc:`KeyError` if the key is not found.
- These variants are more convenient and faster if the missing key should not
- be treated as a failure.
- (Contributed by Serhiy Storchaka in :gh:`106307`.)
+ (Contributed by Victor Stinner and Petr Viktorin in :gh:`110850`.)
-* Add fixed variants of functions which silently ignore errors:
+* Add the :c:func:`PyDict_ContainsString` function
+ with the same behavior as :c:func:`PyDict_Contains`,
+ but *key* is specified as a :c:expr:`const char*` UTF-8 encoded bytes string,
+ rather than a :c:expr:`PyObject*`.
+ (Contributed by Victor Stinner in :gh:`108314`.)
- - :c:func:`PyObject_HasAttrWithError` replaces :c:func:`PyObject_HasAttr`.
- - :c:func:`PyObject_HasAttrStringWithError` replaces :c:func:`PyObject_HasAttrString`.
- - :c:func:`PyMapping_HasKeyWithError` replaces :c:func:`PyMapping_HasKey`.
- - :c:func:`PyMapping_HasKeyStringWithError` replaces :c:func:`PyMapping_HasKeyString`.
+* Add the :c:func:`PyDict_GetItemRef` and :c:func:`PyDict_GetItemStringRef`
+ functions,
+ which behave similarly to :c:func:`PyDict_GetItemWithError`,
+ but return a :term:`strong reference` instead of a :term:`borrowed reference`.
+ Moreover, these functions return ``-1`` on error,
+ removing the need to check :c:func:`!PyErr_Occurred`.
+ (Contributed by Victor Stinner in :gh:`106004`.)
- New functions return not only ``1`` for true and ``0`` for false, but also
- ``-1`` for error.
+* Add the :c:func:`PyDict_SetDefaultRef` function,
+ which behaves similarly to :c:func:`PyDict_SetDefault`,
+ but returns a :term:`strong reference` instead of a :term:`borrowed reference`.
+ This function returns ``-1`` on error,
+ ``0`` on insertion,
+ and ``1`` if the key was already present in the dictionary.
+ (Contributed by Sam Gross in :gh:`112066`.)
- (Contributed by Serhiy Storchaka in :gh:`108511`.)
+* Add the :c:func:`PyDict_Pop` and :c:func:`PyDict_PopString` functions
+ to remove a key from a dictionary and optionally return the removed value.
+ This is similar to :meth:`dict.pop`,
+ though there is no default value,
+ and :exc:`KeyError` is not raised for missing keys.
+ (Contributed by Stefan Behnel and Victor Stinner in :gh:`111262`.)
-* If Python is built in :ref:`debug mode ` or :option:`with
- assertions <--with-assertions>`, :c:func:`PyTuple_SET_ITEM` and
- :c:func:`PyList_SET_ITEM` now check the index argument with an assertion.
- (Contributed by Victor Stinner in :gh:`106168`.)
+* Add the :c:func:`PyMapping_GetOptionalItem`
+ and :c:func:`PyMapping_GetOptionalItemString` functions
+ as alternatives to :c:func:`PyObject_GetItem`
+ and :c:func:`PyMapping_GetItemString` respectively.
+ The new functions do not raise :exc:`KeyError`
+ if the requested key is missing from the mapping.
+ These variants are more convenient and faster
+ if a missing key should not be treated as a failure.
+ (Contributed by Serhiy Storchaka in :gh:`106307`.)
-* Add :c:func:`PyModule_Add` function: similar to
- :c:func:`PyModule_AddObjectRef` and :c:func:`PyModule_AddObject` but
- always steals a reference to the value.
- (Contributed by Serhiy Storchaka in :gh:`86493`.)
+* Add the :c:func:`PyObject_GetOptionalAttr`
+ and :c:func:`PyObject_GetOptionalAttrString` functions
+ as alternatives to :c:func:`PyObject_GetAttr`
+ and :c:func:`PyObject_GetAttrString` respectively.
+ The new functions do not raise :exc:`AttributeError`
+ if the requested attribute is not found on the object.
+ These variants are more convenient and faster
+ if the missing attribute should not be treated as a failure.
+ (Contributed by Serhiy Storchaka in :gh:`106521`.)
-* Add :c:func:`PyDict_GetItemRef` and :c:func:`PyDict_GetItemStringRef`
- functions: similar to :c:func:`PyDict_GetItemWithError` but returning a
- :term:`strong reference` instead of a :term:`borrowed reference`. Moreover,
- these functions return -1 on error and so checking ``PyErr_Occurred()`` is
- not needed.
- (Contributed by Victor Stinner in :gh:`106004`.)
+* Add the :c:func:`PyErr_FormatUnraisable` function
+ as an extension to :c:func:`PyErr_WriteUnraisable`
+ that allows customizing the warning message.
+ (Contributed by Serhiy Storchaka in :gh:`108082`.)
-* Added :c:func:`PyDict_SetDefaultRef`, which is similar to
- :c:func:`PyDict_SetDefault` but returns a :term:`strong reference` instead of
- a :term:`borrowed reference`. This function returns ``-1`` on error, ``0`` on
- insertion, and ``1`` if the key was already present in the dictionary.
- (Contributed by Sam Gross in :gh:`112066`.)
+* Add new functions that return a :term:`strong reference` instead of
+ a :term:`borrowed reference` for frame locals, globals, and builtins,
+ as part of :ref:`PEP 667 `:
-* Add :c:func:`PyDict_ContainsString` function: same as
- :c:func:`PyDict_Contains`, but *key* is specified as a :c:expr:`const char*`
- UTF-8 encoded bytes string, rather than a :c:expr:`PyObject*`.
- (Contributed by Victor Stinner in :gh:`108314`.)
+ * :c:func:`PyEval_GetFrameBuiltins` replaces :c:func:`PyEval_GetBuiltins`
+ * :c:func:`PyEval_GetFrameGlobals` replaces :c:func:`PyEval_GetGlobals`
+ * :c:func:`PyEval_GetFrameLocals` replaces :c:func:`PyEval_GetLocals`
+
+ (Contributed by Mark Shannon and Tian Gao in :gh:`74929`.)
-* Added :c:func:`PyList_GetItemRef` function: similar to
- :c:func:`PyList_GetItem` but returns a :term:`strong reference` instead of
- a :term:`borrowed reference`.
+* Add the :c:func:`Py_GetConstant` and :c:func:`Py_GetConstantBorrowed`
+ functions to get :term:`strong `
+ or :term:`borrowed ` references to constants.
+ For example, ``Py_GetConstant(Py_CONSTANT_ZERO)`` returns a strong reference
+ to the constant zero.
+ (Contributed by Victor Stinner in :gh:`115754`.)
-* Add :c:func:`Py_IsFinalizing` function: check if the main Python interpreter is
+* Add the :c:func:`PyImport_AddModuleRef` function
+ as a replacement for :c:func:`PyImport_AddModule`
+ that returns a :term:`strong reference` instead of a :term:`borrowed reference`.
+ (Contributed by Victor Stinner in :gh:`105922`.)
+
+* Add the :c:func:`Py_IsFinalizing` function to check
+ whether the main Python interpreter is
:term:`shutting down `.
(Contributed by Victor Stinner in :gh:`108014`.)
-* Add :c:func:`PyLong_AsInt` function: similar to :c:func:`PyLong_AsLong`, but
- store the result in a C :c:expr:`int` instead of a C :c:expr:`long`.
- Previously, it was known as the private function :c:func:`!_PyLong_AsInt`
- (with an underscore prefix).
+* Add the :c:func:`PyList_GetItemRef` function
+ as a replacement for :c:func:`PyList_GetItem`
+ that returns a :term:`strong reference` instead of a :term:`borrowed reference`.
+ (Contributed by Sam Gross in :gh:`114329`.)
+
+* Add the :c:func:`PyList_Extend` and :c:func:`PyList_Clear` functions,
+ mirroring the Python :meth:`!list.extend` and :meth:`!list.clear` methods.
+ (Contributed by Victor Stinner in :gh:`111138`.)
+
+* Add the :c:func:`PyLong_AsInt` function.
+ It behaves similarly to :c:func:`PyLong_AsLong`,
+ but stores the result in a C :c:expr:`int` instead of a C :c:expr:`long`.
(Contributed by Victor Stinner in :gh:`108014`.)
-* Python built with :file:`configure` :option:`--with-trace-refs` (tracing
- references) now supports the :ref:`Limited API `.
- (Contributed by Victor Stinner in :gh:`108634`.)
+* Add the :c:func:`PyLong_AsNativeBytes`, :c:func:`PyLong_FromNativeBytes`,
+ and :c:func:`PyLong_FromUnsignedNativeBytes` functions
+ to simplify converting between native integer types
+ and Python :class:`int` objects.
+ (Contributed by Steve Dower in :gh:`111140`.)
+
+* Add :c:func:`PyModule_Add` function, which is similar to
+ :c:func:`PyModule_AddObjectRef` and :c:func:`PyModule_AddObject`,
+ but always steals a reference to the value.
+ (Contributed by Serhiy Storchaka in :gh:`86493`.)
+
+* Add the :c:func:`PyObject_GenericHash` function
+ that implements the default hashing function of a Python object.
+ (Contributed by Serhiy Storchaka in :gh:`113024`.)
-* Add :c:func:`PyObject_VisitManagedDict` and
- :c:func:`PyObject_ClearManagedDict` functions which must be called by the
- traverse and clear functions of a type using
- :c:macro:`Py_TPFLAGS_MANAGED_DICT` flag. The `pythoncapi-compat project
- `__ can be used to get these
- functions on Python 3.11 and 3.12.
+* Add the :c:func:`Py_HashPointer` function to hash a raw pointer.
+ (Contributed by Victor Stinner in :gh:`111545`.)
+
+* Add the :c:func:`PyObject_VisitManagedDict` and
+ :c:func:`PyObject_ClearManagedDict` functions.
+ which must be called by the traverse and clear functions of a type using
+ the :c:macro:`Py_TPFLAGS_MANAGED_DICT` flag.
+ The `pythoncapi-compat project`_ can be used to
+ use these functions with Python 3.11 and 3.12.
(Contributed by Victor Stinner in :gh:`107073`.)
-* Add :c:func:`PyUnicode_EqualToUTF8AndSize` and :c:func:`PyUnicode_EqualToUTF8`
- functions: compare Unicode object with a :c:expr:`const char*` UTF-8 encoded
- string and return true (``1``) if they are equal, or false (``0``) otherwise.
+* Add the :c:func:`PyRefTracer_SetTracer`
+ and :c:func:`PyRefTracer_GetTracer` functions,
+ which enable tracking object creation and destruction
+ in the same way that the :mod:`tracemalloc` module does.
+ (Contributed by Pablo Galindo in :gh:`93502`.)
+
+* Add the :c:func:`PySys_AuditTuple` function
+ as an alternative to :c:func:`PySys_Audit`
+ that takes event arguments as a Python :class:`tuple` object.
+ (Contributed by Victor Stinner in :gh:`85283`.)
+
+* Add the :c:func:`PyThreadState_GetUnchecked()` function
+ as an alternative to :c:func:`PyThreadState_Get()`
+ that doesn't kill the process with a fatal error if it is ``NULL``.
+ The caller is responsible for checking if the result is ``NULL``.
+ (Contributed by Victor Stinner in :gh:`108867`.)
+
+* Add the :c:func:`PyType_GetFullyQualifiedName` function
+ to get the type's fully qualified name.
+ The module name is prepended if ``type.__module__`` is a string
+ and is not equal to either ``'builtins'`` or ``'__main__'``.
+ (Contributed by Victor Stinner in :gh:`111696`.)
+
+* Add the :c:func:`PyType_GetModuleName` function
+ to get the type's module name.
+ This is equivalent to getting the ``type.__module__`` attribute.
+ (Contributed by Eric Snow and Victor Stinner in :gh:`111696`.)
+
+* Add the :c:func:`PyUnicode_EqualToUTF8AndSize`
+ and :c:func:`PyUnicode_EqualToUTF8` functions
+ to compare a Unicode object with a :c:expr:`const char*` UTF-8 encoded string
+ and ``1`` if they are equal or ``0`` otherwise.
These functions do not raise exceptions.
(Contributed by Serhiy Storchaka in :gh:`110289`.)
-* Add :c:func:`PyThreadState_GetUnchecked()` function: similar to
- :c:func:`PyThreadState_Get()`, but don't kill the process with a fatal error
- if it is NULL. The caller is responsible to check if the result is NULL.
- Previously, the function was private and known as
- ``_PyThreadState_UncheckedGet()``.
- (Contributed by Victor Stinner in :gh:`108867`.)
+* Add the :c:func:`PyWeakref_GetRef` function
+ as an alternative to :c:func:`PyWeakref_GetObject`
+ that returns a :term:`strong reference`
+ or ``NULL`` if the referent is no longer live.
+ (Contributed by Victor Stinner in :gh:`105927`.)
-* Add :c:func:`PySys_AuditTuple` function: similar to :c:func:`PySys_Audit`,
- but pass event arguments as a Python :class:`tuple` object.
- (Contributed by Victor Stinner in :gh:`85283`.)
+* Add fixed variants of functions which silently ignore errors:
-* :c:func:`PyArg_ParseTupleAndKeywords` now supports non-ASCII keyword
- parameter names.
+ * :c:func:`PyObject_HasAttrWithError` replaces :c:func:`PyObject_HasAttr`.
+ * :c:func:`PyObject_HasAttrStringWithError`
+ replaces :c:func:`PyObject_HasAttrString`.
+ * :c:func:`PyMapping_HasKeyWithError` replaces :c:func:`PyMapping_HasKey`.
+ * :c:func:`PyMapping_HasKeyStringWithError`
+ replaces :c:func:`PyMapping_HasKeyString`.
+
+ The new functions return ``-1`` for errors
+ and the standard ``1`` for true and ``0`` for false.
+
+ (Contributed by Serhiy Storchaka in :gh:`108511`.)
+
+
+Changed C APIs
+--------------
+
+* The *keywords* parameter of :c:func:`PyArg_ParseTupleAndKeywords`
+ and :c:func:`PyArg_VaParseTupleAndKeywords`
+ now has type :c:expr:`char * const *` in C
+ and :c:expr:`const char * const *` in C++,
+ instead of :c:expr:`char **`.
+ In C++, this makes these functions compatible with arguments
+ of type :c:expr:`const char * const *`, :c:expr:`const char **`,
+ or :c:expr:`char * const *` without an explicit type cast.
+ In C, the functions only support arguments of type :c:expr:`char * const *`.
+ This can be overridden with the :c:macro:`PY_CXX_CONST` macro.
+ (Contributed by Serhiy Storchaka in :gh:`65210`.)
+
+* :c:func:`PyArg_ParseTupleAndKeywords` now supports
+ non-ASCII keyword parameter names.
(Contributed by Serhiy Storchaka in :gh:`110815`.)
-* Add :c:func:`PyMem_RawMalloc`, :c:func:`PyMem_RawCalloc`,
- :c:func:`PyMem_RawRealloc` and :c:func:`PyMem_RawFree` to the limited C API
- (version 3.13).
- (Contributed by Victor Stinner in :gh:`85283`.)
+* The :c:func:`!PyCode_GetFirstFree` function is now unstable API
+ and is now named :c:func:`PyUnstable_Code_GetFirstFree`.
+ (Contributed by Bogdan Romanyuk in :gh:`115781`.)
-* Add :c:func:`PySys_Audit` and :c:func:`PySys_AuditTuple` functions to the
- limited C API.
- (Contributed by Victor Stinner in :gh:`85283`.)
+* The :c:func:`PyDict_GetItem`, :c:func:`PyDict_GetItemString`,
+ :c:func:`PyMapping_HasKey`, :c:func:`PyMapping_HasKeyString`,
+ :c:func:`PyObject_HasAttr`, :c:func:`PyObject_HasAttrString`,
+ and :c:func:`PySys_GetObject` functions,
+ each of which clears all errors which occurred when calling them
+ now reports these errors using :func:`sys.unraisablehook`.
+ You may replace them with other functions as recommended in the documentation.
+ (Contributed by Serhiy Storchaka in :gh:`106672`.)
-* Add :c:func:`PyErr_FormatUnraisable` function: similar to
- :c:func:`PyErr_WriteUnraisable`, but allow customizing the warning message.
- (Contributed by Serhiy Storchaka in :gh:`108082`.)
+* Add support for the ``%T``, ``%#T``, ``%N`` and ``%#N`` formats
+ to :c:func:`PyUnicode_FromFormat`:
-* Add :c:func:`PyList_Extend` and :c:func:`PyList_Clear` functions: similar to
- Python ``list.extend()`` and ``list.clear()`` methods.
- (Contributed by Victor Stinner in :gh:`111138`.)
+ * ``%T``: Get the fully qualified name of an object type
+ * ``%#T``: As above, but use a colon as the separator
+ * ``%N``: Get the fully qualified name of a type
+ * ``%#N``: As above, but use a colon as the separator
-* Add :c:func:`PyDict_Pop` and :c:func:`PyDict_PopString` functions: remove a
- key from a dictionary and optionally return the removed value. This is
- similar to :meth:`dict.pop`, but without the default value and not raising
- :exc:`KeyError` if the key is missing.
- (Contributed by Stefan Behnel and Victor Stinner in :gh:`111262`.)
+ See :pep:`737` for more information.
+ (Contributed by Victor Stinner in :gh:`111696`.)
-* Add :c:func:`Py_HashPointer` function to hash a pointer.
- (Contributed by Victor Stinner in :gh:`111545`.)
+* You no longer have to define the ``PY_SSIZE_T_CLEAN`` macro before
+ including :file:`Python.h` when using ``#`` formats in
+ :ref:`format codes `.
+ APIs accepting the format codes always use ``Py_ssize_t`` for ``#`` formats.
+ (Contributed by Inada Naoki in :gh:`104922`.)
-* Add :c:func:`PyObject_GenericHash` function that implements the default
- hashing function of a Python object.
- (Contributed by Serhiy Storchaka in :gh:`113024`.)
+* If Python is built in :ref:`debug mode `
+ or :option:`with assertions <--with-assertions>`,
+ :c:func:`PyTuple_SET_ITEM` and :c:func:`PyList_SET_ITEM`
+ now check the index argument with an assertion.
+ (Contributed by Victor Stinner in :gh:`106168`.)
-* Add PyTime C API:
- * :c:type:`PyTime_t` type.
- * :c:var:`PyTime_MIN` and :c:var:`PyTime_MAX` constants.
- * Add functions:
+Limited C API Changes
+---------------------
- * :c:func:`PyTime_AsSecondsDouble`.
- * :c:func:`PyTime_Monotonic`.
- * :c:func:`PyTime_MonotonicRaw`.
- * :c:func:`PyTime_PerfCounter`.
- * :c:func:`PyTime_PerfCounterRaw`.
- * :c:func:`PyTime_Time`.
- * :c:func:`PyTime_TimeRaw`.
+* The following functions are now included in the Limited C API:
- (Contributed by Victor Stinner and Petr Viktorin in :gh:`110850`.)
+ * :c:func:`PyMem_RawMalloc`
+ * :c:func:`PyMem_RawCalloc`
+ * :c:func:`PyMem_RawRealloc`
+ * :c:func:`PyMem_RawFree`
+ * :c:func:`PySys_Audit`
+ * :c:func:`PySys_AuditTuple`
+ * :c:func:`PyType_GetModuleByDef`
-* Add :c:func:`PyLong_AsNativeBytes`, :c:func:`PyLong_FromNativeBytes` and
- :c:func:`PyLong_FromUnsignedNativeBytes` functions to simplify converting
- between native integer types and Python :class:`int` objects.
- (Contributed by Steve Dower in :gh:`111140`.)
+ (Contributed by Victor Stinner in :gh:`85283`, :gh:`85283`, and :gh:`116936`.)
-* Add :c:func:`PyType_GetFullyQualifiedName` function to get the type's fully
- qualified name. Equivalent to ``f"{type.__module__}.{type.__qualname__}"``,
- or ``type.__qualname__`` if ``type.__module__`` is not a string or is equal
- to ``"builtins"``.
- (Contributed by Victor Stinner in :gh:`111696`.)
+* Python built with :option:`--with-trace-refs` (tracing references)
+ now supports the :ref:`Limited API `.
+ (Contributed by Victor Stinner in :gh:`108634`.)
-* Add :c:func:`PyType_GetModuleName` function to get the type's module name.
- Equivalent to getting the ``type.__module__`` attribute.
- (Contributed by Eric Snow and Victor Stinner in :gh:`111696`.)
-* Add support for ``%T``, ``%#T``, ``%N`` and ``%#N`` formats to
- :c:func:`PyUnicode_FromFormat`: format the fully qualified name of an object
- type and of a type: call :c:func:`PyType_GetModuleName`. See :pep:`737` for
- more information.
- (Contributed by Victor Stinner in :gh:`111696`.)
+Removed C APIs
+--------------
-* Add :c:func:`Py_GetConstant` and :c:func:`Py_GetConstantBorrowed` functions
- to get constants. For example, ``Py_GetConstant(Py_CONSTANT_ZERO)`` returns a
- :term:`strong reference` to the constant zero.
- (Contributed by Victor Stinner in :gh:`115754`.)
+* Remove several functions, macros, variables, etc
+ with names prefixed by ``_Py`` or ``_PY`` (which are considered private).
+ If your project is affected by one of these removals
+ and you believe that the removed API should remain available,
+ please :ref:`open a new issue ` to request a public C API
+ and add ``cc: @vstinner`` to the issue to notify Victor Stinner.
+ (Contributed by Victor Stinner in :gh:`106320`.)
-* Add :c:func:`PyType_GetModuleByDef` to the limited C API
- (Contributed by Victor Stinner in :gh:`116936`.)
+* Remove old buffer protocols deprecated in Python 3.0.
+ Use :ref:`bufferobjects` instead.
-* Add two new functions to the C-API, :c:func:`PyRefTracer_SetTracer` and
- :c:func:`PyRefTracer_GetTracer`, that allow to track object creation and
- destruction the same way the :mod:`tracemalloc` module does. (Contributed
- by Pablo Galindo in :gh:`93502`.)
+ * :c:func:`!PyObject_CheckReadBuffer`:
+ Use :c:func:`PyObject_CheckBuffer` to test
+ whether the object supports the buffer protocol.
+ Note that :c:func:`PyObject_CheckBuffer` doesn't guarantee
+ that :c:func:`PyObject_GetBuffer` will succeed.
+ To test if the object is actually readable,
+ see the next example of :c:func:`PyObject_GetBuffer`.
-* Add :c:func:`PyEval_GetFrameBuiltins`, :c:func:`PyEval_GetFrameGlobals`, and
- :c:func:`PyEval_GetFrameLocals` to the C API. These replacements for
- :c:func:`PyEval_GetBuiltins`, :c:func:`PyEval_GetGlobals`, and
- :c:func:`PyEval_GetLocals` return :term:`strong references `
- rather than borrowed references. (Added as part of :pep:`667`.)
+ * :c:func:`!PyObject_AsCharBuffer`, :c:func:`!PyObject_AsReadBuffer`:
+ Use :c:func:`PyObject_GetBuffer` and :c:func:`PyBuffer_Release` instead:
-* Add :c:type:`PyMutex` API, a lightweight mutex that occupies a single byte.
- The :c:func:`PyMutex_Lock` function will release the GIL (if currently held)
- if the operation needs to block.
- (Contributed by Sam Gross in :gh:`108724`.)
+ .. code-block:: c
-* Add C API functions for generating :pep:`669` monitoring events.
- (Contributed by Irit Katriel in :gh:`111997`).
+ Py_buffer view;
+ if (PyObject_GetBuffer(obj, &view, PyBUF_SIMPLE) < 0) {
+ return NULL;
+ }
+ // Use `view.buf` and `view.len` to read from the buffer.
+ // You may need to cast buf as `(const char*)view.buf`.
+ PyBuffer_Release(&view);
-Build Changes
-=============
+ * :c:func:`!PyObject_AsWriteBuffer`:
+ Use :c:func:`PyObject_GetBuffer` and :c:func:`PyBuffer_Release` instead:
-* The :file:`configure` option :option:`--with-system-libmpdec` now defaults
- to ``yes``. The bundled copy of ``libmpdecimal`` will be removed in Python
- 3.15.
+ .. code-block:: c
-* Autoconf 2.71 and aclocal 1.16.4 are now required to regenerate
- the :file:`configure` script.
- (Contributed by Christian Heimes in :gh:`89886`.)
+ Py_buffer view;
+ if (PyObject_GetBuffer(obj, &view, PyBUF_WRITABLE) < 0) {
+ return NULL;
+ }
+ // Use `view.buf` and `view.len` to write to the buffer.
+ PyBuffer_Release(&view);
-* SQLite 3.15.2 or newer is required to build the :mod:`sqlite3` extension module.
- (Contributed by Erlend Aasland in :gh:`105875`.)
+ (Contributed by Inada Naoki in :gh:`85275`.)
-* Python built with :file:`configure` :option:`--with-trace-refs` (tracing
- references) is now ABI compatible with the Python release build and
- :ref:`debug build `.
- (Contributed by Victor Stinner in :gh:`108634`.)
+* Remove various functions deprecated in Python 3.9:
-* Building CPython now requires a compiler with support for the C11 atomic
- library, GCC built-in atomic functions, or MSVC interlocked intrinsics.
+ * :c:func:`!PyEval_CallObject`, :c:func:`!PyEval_CallObjectWithKeywords`:
+ Use :c:func:`PyObject_CallNoArgs` or :c:func:`PyObject_Call` instead.
-* The ``errno``, ``fcntl``, ``grp``, ``md5``, ``pwd``, ``resource``,
- ``termios``, ``winsound``,
- ``_ctypes_test``, ``_multiprocessing.posixshmem``, ``_scproxy``, ``_stat``,
- ``_statistics``, ``_testconsole``, ``_testimportmultiple`` and ``_uuid``
- C extensions are now built with the :ref:`limited C API `.
- (Contributed by Victor Stinner in :gh:`85283`.)
+ .. warning::
+
+ In :c:func:`PyObject_Call`, positional arguments must be a :class:`tuple`
+ and must not be ``NULL``,
+ and keyword arguments must be a :class:`dict` or ``NULL``,
+ whereas the removed functions checked argument types
+ and accepted ``NULL`` positional and keyword arguments.
+ To replace ``PyEval_CallObjectWithKeywords(func, NULL, kwargs)`` with
+ :c:func:`PyObject_Call`,
+ pass an empty tuple as positional arguments using
+ :c:func:`PyTuple_New(0) `.
+
+ * :c:func:`!PyEval_CallFunction`:
+ Use :c:func:`PyObject_CallFunction` instead.
+ * :c:func:`!PyEval_CallMethod`:
+ Use :c:func:`PyObject_CallMethod` instead.
+ * :c:func:`!PyCFunction_Call`:
+ Use :c:func:`PyObject_Call` instead.
+
+ (Contributed by Victor Stinner in :gh:`105107`.)
+
+* Remove the following old functions to configure the Python initialization,
+ deprecated in Python 3.11:
+
+ * :c:func:`!PySys_AddWarnOptionUnicode`:
+ Use :c:member:`PyConfig.warnoptions` instead.
+ * :c:func:`!PySys_AddWarnOption`:
+ Use :c:member:`PyConfig.warnoptions` instead.
+ * :c:func:`!PySys_AddXOption`:
+ Use :c:member:`PyConfig.xoptions` instead.
+ * :c:func:`!PySys_HasWarnOptions`:
+ Use :c:member:`PyConfig.xoptions` instead.
+ * :c:func:`!PySys_SetPath`:
+ Set :c:member:`PyConfig.module_search_paths` instead.
+ * :c:func:`!Py_SetPath`:
+ Set :c:member:`PyConfig.module_search_paths` instead.
+ * :c:func:`!Py_SetStandardStreamEncoding`:
+ Set :c:member:`PyConfig.stdio_encoding` instead,
+ and set also maybe :c:member:`PyConfig.legacy_windows_stdio` (on Windows).
+ * :c:func:`!_Py_SetProgramFullPath`:
+ Set :c:member:`PyConfig.executable` instead.
+
+ Use the new :c:type:`PyConfig` API of the :ref:`Python Initialization
+ Configuration ` instead (:pep:`587`), added to Python 3.8.
+ (Contributed by Victor Stinner in :gh:`105145`.)
+
+* Remove :c:func:`!PyEval_AcquireLock` and :c:func:`!PyEval_ReleaseLock` functions,
+ deprecated in Python 3.2.
+ They didn't update the current thread state.
+ They can be replaced with:
+
+ * :c:func:`PyEval_SaveThread` and :c:func:`PyEval_RestoreThread`;
+ * low-level :c:func:`PyEval_AcquireThread` and :c:func:`PyEval_RestoreThread`;
+ * or :c:func:`PyGILState_Ensure` and :c:func:`PyGILState_Release`.
+
+ (Contributed by Victor Stinner in :gh:`105182`.)
+
+* Remove the :c:func:`!PyEval_ThreadsInitialized` function,
+ deprecated in Python 3.9.
+ Since Python 3.7, :c:func:`!Py_Initialize` always creates the GIL:
+ calling :c:func:`!PyEval_InitThreads` does nothing and
+ :c:func:`!PyEval_ThreadsInitialized` always returns non-zero.
+ (Contributed by Victor Stinner in :gh:`105182`.)
+
+* Remove the :c:func:`!_PyInterpreterState_Get` alias to
+ :c:func:`PyInterpreterState_Get()`
+ which was kept for backward compatibility with Python 3.8.
+ The `pythoncapi-compat project`_ can be used to get
+ :c:func:`PyInterpreterState_Get()` on Python 3.8 and older.
+ (Contributed by Victor Stinner in :gh:`106320`.)
+
+* Remove the private :c:func:`!_PyObject_FastCall` function:
+ use :c:func:`!PyObject_Vectorcall` which is available since Python 3.8
+ (:pep:`590`).
+ (Contributed by Victor Stinner in :gh:`106023`.)
+
+* Remove the ``cpython/pytime.h`` header file,
+ which only contained private functions.
+ (Contributed by Victor Stinner in :gh:`106316`.)
+
+* Remove the undocumented ``PY_TIMEOUT_MAX`` constant from the limited C API.
+ (Contributed by Victor Stinner in :gh:`110014`.)
+
+* Remove the old trashcan macros ``Py_TRASHCAN_SAFE_BEGIN``
+ and ``Py_TRASHCAN_SAFE_END``.
+ Replace both with the new macros ``Py_TRASHCAN_BEGIN``
+ and ``Py_TRASHCAN_END``.
+ (Contributed by Irit Katriel in :gh:`105111`.)
+
+Deprecated C APIs
+-----------------
+
+* Deprecate old Python initialization functions:
+
+ * :c:func:`PySys_ResetWarnOptions`:
+ Clear :data:`sys.warnoptions` and :data:`!warnings.filters` instead.
+ * :c:func:`Py_GetExecPrefix`:
+ Get :data:`sys.exec_prefix` instead.
+ * :c:func:`Py_GetPath`:
+ Get :data:`sys.path` instead.
+ * :c:func:`Py_GetPrefix`:
+ Get :data:`sys.prefix` instead.
+ * :c:func:`Py_GetProgramFullPath`:
+ Get :data:`sys.executable` instead.
+ * :c:func:`Py_GetProgramName`:
+ Get :data:`sys.executable` instead.
+ * :c:func:`Py_GetPythonHome`:
+ Get :c:member:`PyConfig.home`
+ or the :envvar:`PYTHONHOME` environment variable instead.
+
+ (Contributed by Victor Stinner in :gh:`105145`.)
+
+* :term:`Soft deprecate ` the
+ :c:func:`PyEval_GetBuiltins`, :c:func:`PyEval_GetGlobals`,
+ and :c:func:`PyEval_GetLocals` functions,
+ which return a :term:`borrowed reference`.
+ (Soft deprecated as part of :pep:`667`.)
+
+* Deprecate the :c:func:`PyImport_ImportModuleNoBlock` function,
+ which is just an alias to :c:func:`PyImport_ImportModule` since Python 3.3.
+ (Contributed by Victor Stinner in :gh:`105396`.)
+
+* :term:`Soft deprecate ` the
+ :c:func:`PyModule_AddObject` function.
+ It should be replaced with :c:func:`PyModule_Add`
+ or :c:func:`PyModule_AddObjectRef`.
+ (Contributed by Serhiy Storchaka in :gh:`86493`.)
+
+* Deprecate the old ``Py_UNICODE`` and ``PY_UNICODE_TYPE`` types
+ and the :c:macro:`!Py_UNICODE_WIDE` define.
+ Use the :c:type:`wchar_t` type directly instead.
+ Since Python 3.3, ``Py_UNICODE`` and ``PY_UNICODE_TYPE``
+ are just aliases to :c:type:`!wchar_t`.
+ (Contributed by Victor Stinner in :gh:`105156`.)
+
+* Deprecate the :c:func:`PyWeakref_GetObject` and
+ :c:func:`PyWeakref_GET_OBJECT` functions,
+ which return a :term:`borrowed reference`.
+ Replace them with the new :c:func:`PyWeakref_GetRef` function,
+ which returns a :term:`strong reference`.
+ The `pythoncapi-compat project`_ can be used to get
+ :c:func:`PyWeakref_GetRef` on Python 3.12 and older.
+ (Contributed by Victor Stinner in :gh:`105927`.)
+
+.. Add deprecations above alphabetically, not here at the end.
+
+.. include:: ../deprecations/c-api-pending-removal-in-3.14.rst
+
+.. include:: ../deprecations/c-api-pending-removal-in-3.15.rst
+
+.. include:: ../deprecations/c-api-pending-removal-in-future.rst
+
+.. _pythoncapi-compat project: https://github.com/python/pythoncapi-compat/
+
+Build Changes
+=============
+
+* ``arm64-apple-ios`` and ``arm64-apple-ios-simulator`` are both
+ now :pep:`11` tier 3 platforms.
+ (:ref:`PEP 730 ` written
+ and implementation contributed by Russell Keith-Magee in :gh:`114099`.)
+
+* ``aarch64-linux-android`` and ``x86_64-linux-android`` are both
+ now :pep:`11` tier 3 platforms.
+ (:ref:`PEP 738 ` written
+ and implementation contributed by Malcolm Smith in :gh:`116622`.)
* ``wasm32-wasi`` is now a :pep:`11` tier 2 platform.
(Contributed by Brett Cannon in :gh:`115192`.)
@@ -2266,15 +2531,45 @@ Build Changes
* ``wasm32-emscripten`` is no longer a :pep:`11` supported platform.
(Contributed by Brett Cannon in :gh:`115192`.)
-* Python now bundles the `mimalloc library `__.
- It is licensed under the MIT license; see :ref:`mimalloc license `.
+* Building CPython now requires a compiler with support for the C11 atomic
+ library, GCC built-in atomic functions, or MSVC interlocked intrinsics.
+
+* Autoconf 2.71 and aclocal 1.16.4 are now required to regenerate
+ the :file:`configure` script.
+ (Contributed by Christian Heimes in :gh:`89886`.)
+
+* SQLite 3.15.2 or newer is required to build
+ the :mod:`sqlite3` extension module.
+ (Contributed by Erlend Aasland in :gh:`105875`.)
+
+* CPython now bundles the `mimalloc library`_ by default.
+ It is licensed under the MIT license;
+ see :ref:`mimalloc license `.
The bundled mimalloc has custom changes, see :gh:`113141` for details.
(Contributed by Dino Viehland in :gh:`109914`.)
+ .. _mimalloc library: https://github.com/microsoft/mimalloc/
+
+* The :file:`configure` option :option:`--with-system-libmpdec`
+ now defaults to ``yes``.
+ The bundled copy of ``libmpdecimal`` will be removed in Python 3.15.
+
+* Python built with :file:`configure` :option:`--with-trace-refs`
+ (tracing references) is now ABI compatible with the Python release build
+ and :ref:`debug build `.
+ (Contributed by Victor Stinner in :gh:`108634`.)
+
* On POSIX systems, the pkg-config (``.pc``) filenames now include the ABI
flags. For example, the free-threaded build generates ``python-3.13t.pc``
and the debug build generates ``python-3.13d.pc``.
+* The ``errno``, ``fcntl``, ``grp``, ``md5``, ``pwd``, ``resource``,
+ ``termios``, ``winsound``,
+ ``_ctypes_test``, ``_multiprocessing.posixshmem``, ``_scproxy``, ``_stat``,
+ ``_statistics``, ``_testconsole``, ``_testimportmultiple`` and ``_uuid``
+ C extensions are now built with the :ref:`limited C API `.
+ (Contributed by Victor Stinner in :gh:`85283`.)
+
Porting to Python 3.13
======================
@@ -2285,84 +2580,96 @@ that may require changes to your code.
Changes in the Python API
-------------------------
-* An :exc:`OSError` is now raised by :func:`getpass.getuser` for any failure to
- retrieve a username, instead of :exc:`ImportError` on non-Unix platforms or
- :exc:`KeyError` on Unix platforms where the password database is empty.
+.. _pep667-porting-notes-py:
-* The :mod:`threading` module now expects the :mod:`!_thread` module to have
- an ``_is_main_interpreter`` attribute. It is a function with no
- arguments that returns ``True`` if the current interpreter is the
- main interpreter.
+* :ref:`PEP 667 ` introduces several changes
+ to the semantics of :func:`locals` and :attr:`f_locals `:
+
+ * Calling :func:`locals` in an :term:`optimized scope` now produces an
+ independent snapshot on each call, and hence no longer implicitly updates
+ previously returned references. Obtaining the legacy CPython behavior now
+ requires explicit calls to update the initially returned dictionary with the
+ results of subsequent calls to :func:`!locals`. Code execution functions that
+ implicitly target :func:`!locals` (such as ``exec`` and ``eval``) must be
+ passed an explicit namespace to access their results in an optimized scope.
+ (Changed as part of :pep:`667`.)
+
+ * Calling :func:`locals` from a comprehension at module or class scope
+ (including via ``exec`` or ``eval``) once more behaves as if the comprehension
+ were running as an independent nested function (i.e. the local variables from
+ the containing scope are not included). In Python 3.12, this had changed
+ to include the local variables from the containing scope when implementing
+ :pep:`709`. (Changed as part of :pep:`667`.)
+
+ * Accessing :attr:`FrameType.f_locals ` in an
+ :term:`optimized scope` now returns a write-through proxy rather than a
+ snapshot that gets updated at ill-specified times. If a snapshot is desired,
+ it must be created explicitly with ``dict`` or the proxy's ``.copy()`` method.
+ (Changed as part of :pep:`667`.)
+
+* :class:`functools.partial` now emits a :exc:`FutureWarning`
+ when used as a method.
+ The behavior will change in future Python versions.
+ Wrap it in :func:`staticmethod` if you want to preserve the old behavior.
+ (Contributed by Serhiy Storchaka in :gh:`121027`.)
- Any library or application that provides a custom ``_thread`` module
- must provide ``_is_main_interpreter()``, just like the module's
- other "private" attributes.
- (See :gh:`112826`.)
+* The :ref:`garbage collector is now incremental `,
+ which means that the behavior of :func:`gc.collect` changes slightly:
-* :class:`mailbox.Maildir` now ignores files with a leading dot.
- (Contributed by Zackery Spytz in :gh:`65559`.)
+ * ``gc.collect(1)``: Performs an increment of garbage collection,
+ rather than collecting generation 1.
+ * Other calls to :func:`!gc.collect` are unchanged.
-* :meth:`pathlib.Path.glob` and :meth:`~pathlib.Path.rglob` now return both
- files and directories if a pattern that ends with "``**``" is given, rather
- than directories only. Users may add a trailing slash to match only
- directories.
+* An :exc:`OSError` is now raised by :func:`getpass.getuser`
+ for any failure to retrieve a username,
+ instead of :exc:`ImportError` on non-Unix platforms
+ or :exc:`KeyError` on Unix platforms where the password database is empty.
-* The value of the :attr:`!mode` attribute of :class:`gzip.GzipFile` was
- changed from integer (``1`` or ``2``) to string (``'rb'`` or ``'wb'``).
+* The value of the :attr:`!mode` attribute of :class:`gzip.GzipFile`
+ is now a string (``'rb'`` or ``'wb'``) instead of an integer (``1`` or ``2``).
The value of the :attr:`!mode` attribute of the readable file-like object
- returned by :meth:`zipfile.ZipFile.open` was changed from ``'r'`` to ``'rb'``.
+ returned by :meth:`zipfile.ZipFile.open` is now ``'rb'`` instead of ``'r'``.
(Contributed by Serhiy Storchaka in :gh:`115961`.)
-* :class:`functools.partial` now emits a :exc:`FutureWarning` when it is
- used as a method.
- Its behavior will be changed in future Python versions.
- Wrap it in :func:`staticmethod` if you want to preserve the old behavior.
- (Contributed by Serhiy Storchaka in :gh:`121027`.)
+* :class:`mailbox.Maildir` now ignores files with a leading dot (``.``).
+ (Contributed by Zackery Spytz in :gh:`65559`.)
-.. _pep667-porting-notes-py:
+* :meth:`pathlib.Path.glob` and :meth:`~pathlib.Path.rglob` now return both
+ files and directories if a pattern that ends with "``**``" is given,
+ rather than directories only.
+ Add a trailing slash to keep the previous behavior and only match directories.
+
+* The :mod:`threading` module now expects the :mod:`!_thread` module
+ to have an :func:`!_is_main_interpreter` function.
+ This function takes no arguments and returns ``True``
+ if the current interpreter is the main interpreter.
+
+ Any library or application that provides a custom :mod:`!_thread` module
+ must provide :func:`!_is_main_interpreter`,
+ just like the module's other "private" attributes.
+ (:gh:`112826`.)
-* Calling :func:`locals` in an :term:`optimized scope` now produces an
- independent snapshot on each call, and hence no longer implicitly updates
- previously returned references. Obtaining the legacy CPython behaviour now
- requires explicit calls to update the initially returned dictionary with the
- results of subsequent calls to :func:`!locals`. Code execution functions that
- implicitly target :func:`!locals` (such as ``exec`` and ``eval``) must be
- passed an explicit namespace to access their results in an optimized scope.
- (Changed as part of :pep:`667`.)
-
-* Calling :func:`locals` from a comprehension at module or class scope
- (including via ``exec`` or ``eval``) once more behaves as if the comprehension
- were running as an independent nested function (i.e. the local variables from
- the containing scope are not included). In Python 3.12, this had changed
- to include the local variables from the containing scope when implementing
- :pep:`709`. (Changed as part of :pep:`667`.)
-
-* Accessing :attr:`FrameType.f_locals ` in an
- :term:`optimized scope` now returns a write-through proxy rather than a
- snapshot that gets updated at ill-specified times. If a snapshot is desired,
- it must be created explicitly with ``dict`` or the proxy's ``.copy()`` method.
- (Changed as part of :pep:`667`.)
Changes in the C API
--------------------
* ``Python.h`` no longer includes the ```` standard header. It was
- included for the ``finite()`` function which is now provided by the
+ included for the :c:func:`!finite` function which is now provided by the
```` header. It should now be included explicitly if needed. Remove
also the ``HAVE_IEEEFP_H`` macro.
(Contributed by Victor Stinner in :gh:`108765`.)
* ``Python.h`` no longer includes these standard header files: ````,
```` and ````. If needed, they should now be
- included explicitly. For example, ```` provides the ``clock()`` and
- ``gmtime()`` functions, ```` provides the ``select()``
- function, and ```` provides the ``futimes()``, ``gettimeofday()``
- and ``setitimer()`` functions.
+ included explicitly. For example, ```` provides the :c:func:`!clock` and
+ :c:func:`!gmtime` functions, ```` provides the :c:func:`!select`
+ function, and ```` provides the :c:func:`!futimes`, :c:func:`!gettimeofday`
+ and :c:func:`!setitimer` functions.
(Contributed by Victor Stinner in :gh:`108765`.)
* On Windows, ``Python.h`` no longer includes the ```` standard
header file. If needed, it should now be included explicitly. For example, it
- provides ``offsetof()`` function, and ``size_t`` and ``ptrdiff_t`` types.
+ provides :c:func:`!offsetof` function, and ``size_t`` and ``ptrdiff_t`` types.
Including ```` explicitly was already needed by all other
platforms, the ``HAVE_STDDEF_H`` macro is only defined on Windows.
(Contributed by Victor Stinner in :gh:`108765`.)
@@ -2403,214 +2710,39 @@ Changes in the C API
added in Python 3.8 and the old macros were deprecated in Python 3.11.
(Contributed by Irit Katriel in :gh:`105111`.)
-* Functions :c:func:`PyDict_GetItem`, :c:func:`PyDict_GetItemString`,
- :c:func:`PyMapping_HasKey`, :c:func:`PyMapping_HasKeyString`,
- :c:func:`PyObject_HasAttr`, :c:func:`PyObject_HasAttrString`, and
- :c:func:`PySys_GetObject`, which clear all errors which occurred when calling
- them, now report them using :func:`sys.unraisablehook`.
- You may replace them with other functions as
- recommended in the documentation.
- (Contributed by Serhiy Storchaka in :gh:`106672`.)
-
-* :c:func:`!PyCode_GetFirstFree` is an unstable API now and has been renamed
- to :c:func:`PyUnstable_Code_GetFirstFree`.
- (Contributed by Bogdan Romanyuk in :gh:`115781`.)
-
.. _pep667-porting-notes-c:
-* The effects of mutating the dictionary returned from :c:func:`PyEval_GetLocals` in an
- :term:`optimized scope` have changed. New dict entries added this way will now *only* be
- visible to subsequent :c:func:`PyEval_GetLocals` calls in that frame, as
- :c:func:`PyFrame_GetLocals`, :func:`locals`, and
- :attr:`FrameType.f_locals ` no longer access the same underlying cached
- dictionary. Changes made to entries for actual variable names and names added via the
- write-through proxy interfaces will be overwritten on subsequent calls to
- :c:func:`PyEval_GetLocals` in that frame. The recommended code update depends on how the
- function was being used, so refer to the deprecation notice on the function for details.
- (Changed as part of :pep:`667`.)
-
-* Calling :c:func:`PyFrame_GetLocals` in an :term:`optimized scope` now returns a
- write-through proxy rather than a snapshot that gets updated at ill-specified times.
- If a snapshot is desired, it must be created explicitly (e.g. with :c:func:`PyDict_Copy`)
- or by calling the new :c:func:`PyEval_GetFrameLocals` API. (Changed as part of :pep:`667`.)
-
-* :c:func:`!PyFrame_FastToLocals` and :c:func:`!PyFrame_FastToLocalsWithError`
- no longer have any effect. Calling these functions has been redundant since
- Python 3.11, when :c:func:`PyFrame_GetLocals` was first introduced.
- (Changed as part of :pep:`667`.)
-
-* :c:func:`!PyFrame_LocalsToFast` no longer has any effect. Calling this function
- is redundant now that :c:func:`PyFrame_GetLocals` returns a write-through proxy
- for :term:`optimized scopes `. (Changed as part of :pep:`667`.)
-
-Removed C APIs
---------------
-
-* Remove many APIs (functions, macros, variables) with names prefixed by
- ``_Py`` or ``_PY`` (considered as private API). If your project is affected
- by one of these removals and you consider that the removed API should remain
- available, please open a new issue to request a public C API and
- add ``cc @vstinner`` to the issue to notify Victor Stinner.
- (Contributed by Victor Stinner in :gh:`106320`.)
-
-* Remove functions deprecated in Python 3.9:
-
- * ``PyEval_CallObject()``, ``PyEval_CallObjectWithKeywords()``: use
- :c:func:`PyObject_CallNoArgs` or :c:func:`PyObject_Call` instead.
- Warning: :c:func:`PyObject_Call` positional arguments must be a
- :class:`tuple` and must not be ``NULL``, keyword arguments must be a
- :class:`dict` or ``NULL``, whereas removed functions checked arguments type
- and accepted ``NULL`` positional and keyword arguments.
- To replace ``PyEval_CallObjectWithKeywords(func, NULL, kwargs)`` with
- :c:func:`PyObject_Call`, pass an empty tuple as positional arguments using
- :c:func:`PyTuple_New(0) `.
- * ``PyEval_CallFunction()``: use :c:func:`PyObject_CallFunction` instead.
- * ``PyEval_CallMethod()``: use :c:func:`PyObject_CallMethod` instead.
- * ``PyCFunction_Call()``: use :c:func:`PyObject_Call` instead.
-
- (Contributed by Victor Stinner in :gh:`105107`.)
-
-* Remove old buffer protocols deprecated in Python 3.0. Use :ref:`bufferobjects` instead.
-
- * :c:func:`!PyObject_CheckReadBuffer`: Use :c:func:`PyObject_CheckBuffer` to
- test if the object supports the buffer protocol.
- Note that :c:func:`PyObject_CheckBuffer` doesn't guarantee that
- :c:func:`PyObject_GetBuffer` will succeed.
- To test if the object is actually readable, see the next example
- of :c:func:`PyObject_GetBuffer`.
-
- * :c:func:`!PyObject_AsCharBuffer`, :c:func:`!PyObject_AsReadBuffer`:
- :c:func:`PyObject_GetBuffer` and :c:func:`PyBuffer_Release` instead:
-
- .. code-block:: c
-
- Py_buffer view;
- if (PyObject_GetBuffer(obj, &view, PyBUF_SIMPLE) < 0) {
- return NULL;
- }
- // Use `view.buf` and `view.len` to read from the buffer.
- // You may need to cast buf as `(const char*)view.buf`.
- PyBuffer_Release(&view);
-
- * :c:func:`!PyObject_AsWriteBuffer`: Use
- :c:func:`PyObject_GetBuffer` and :c:func:`PyBuffer_Release` instead:
-
- .. code-block:: c
-
- Py_buffer view;
- if (PyObject_GetBuffer(obj, &view, PyBUF_WRITABLE) < 0) {
- return NULL;
- }
- // Use `view.buf` and `view.len` to write to the buffer.
- PyBuffer_Release(&view);
-
- (Contributed by Inada Naoki in :gh:`85275`.)
-
-* Remove the following old functions to configure the Python initialization,
- deprecated in Python 3.11:
-
- * ``PySys_AddWarnOptionUnicode()``: use :c:member:`PyConfig.warnoptions` instead.
- * ``PySys_AddWarnOption()``: use :c:member:`PyConfig.warnoptions` instead.
- * ``PySys_AddXOption()``: use :c:member:`PyConfig.xoptions` instead.
- * ``PySys_HasWarnOptions()``: use :c:member:`PyConfig.xoptions` instead.
- * ``PySys_SetPath()``: set :c:member:`PyConfig.module_search_paths` instead.
- * ``Py_SetPath()``: set :c:member:`PyConfig.module_search_paths` instead.
- * ``Py_SetStandardStreamEncoding()``: set :c:member:`PyConfig.stdio_encoding`
- instead, and set also maybe :c:member:`PyConfig.legacy_windows_stdio` (on
- Windows).
- * ``_Py_SetProgramFullPath()``: set :c:member:`PyConfig.executable` instead.
-
- Use the new :c:type:`PyConfig` API of the :ref:`Python Initialization
- Configuration ` instead (:pep:`587`), added to Python 3.8.
- (Contributed by Victor Stinner in :gh:`105145`.)
-
-* Remove ``PyEval_ThreadsInitialized()``
- function, deprecated in Python 3.9. Since Python 3.7, ``Py_Initialize()``
- always creates the GIL: calling ``PyEval_InitThreads()`` does nothing and
- ``PyEval_ThreadsInitialized()`` always returned non-zero.
- (Contributed by Victor Stinner in :gh:`105182`.)
-
-* Remove ``PyEval_AcquireLock()`` and ``PyEval_ReleaseLock()`` functions,
- deprecated in Python 3.2. They didn't update the current thread state.
- They can be replaced with:
-
- * :c:func:`PyEval_SaveThread` and :c:func:`PyEval_RestoreThread`;
- * low-level :c:func:`PyEval_AcquireThread` and :c:func:`PyEval_RestoreThread`;
- * or :c:func:`PyGILState_Ensure` and :c:func:`PyGILState_Release`.
-
- (Contributed by Victor Stinner in :gh:`105182`.)
-
-* Remove private ``_PyObject_FastCall()`` function:
- use ``PyObject_Vectorcall()`` which is available since Python 3.8
- (:pep:`590`).
- (Contributed by Victor Stinner in :gh:`106023`.)
-
-* Remove ``cpython/pytime.h`` header file: it only contained private functions.
- (Contributed by Victor Stinner in :gh:`106316`.)
-
-* Remove ``_PyInterpreterState_Get()`` alias to
- :c:func:`PyInterpreterState_Get()` which was kept for backward compatibility
- with Python 3.8. The `pythoncapi-compat project
- `__ can be used to get
- :c:func:`PyInterpreterState_Get()` on Python 3.8 and older.
- (Contributed by Victor Stinner in :gh:`106320`.)
-
-* The :c:func:`PyModule_AddObject` function is now :term:`soft deprecated`:
- :c:func:`PyModule_Add` or :c:func:`PyModule_AddObjectRef` functions should
- be used instead.
- (Contributed by Serhiy Storchaka in :gh:`86493`.)
-
-* Remove undocumented ``PY_TIMEOUT_MAX`` constant from the limited C API.
- (Contributed by Victor Stinner in :gh:`110014`.)
-
-Deprecated C APIs
------------------
-
-* Deprecate the old ``Py_UNICODE`` and ``PY_UNICODE_TYPE`` types: use directly
- the :c:type:`wchar_t` type instead. Since Python 3.3, ``Py_UNICODE`` and
- ``PY_UNICODE_TYPE`` are just aliases to :c:type:`wchar_t`.
- (Contributed by Victor Stinner in :gh:`105156`.)
-
-* Deprecate old Python initialization functions:
-
- * :c:func:`PySys_ResetWarnOptions`:
- clear :data:`sys.warnoptions` and :data:`!warnings.filters` instead.
- * :c:func:`Py_GetExecPrefix`: get :data:`sys.exec_prefix` instead.
- * :c:func:`Py_GetPath`: get :data:`sys.path` instead.
- * :c:func:`Py_GetPrefix`: get :data:`sys.prefix` instead.
- * :c:func:`Py_GetProgramFullPath`: get :data:`sys.executable` instead.
- * :c:func:`Py_GetProgramName`: get :data:`sys.executable` instead.
- * :c:func:`Py_GetPythonHome`: get :c:member:`PyConfig.home` or
- :envvar:`PYTHONHOME` environment variable instead.
-
- Functions scheduled for removal in Python 3.15.
- (Contributed by Victor Stinner in :gh:`105145`.)
-
-* Deprecate the :c:func:`PyImport_ImportModuleNoBlock` function which is just
- an alias to :c:func:`PyImport_ImportModule` since Python 3.3.
- Scheduled for removal in Python 3.15.
- (Contributed by Victor Stinner in :gh:`105396`.)
-
-* Deprecate the :c:func:`PyWeakref_GetObject` and
- :c:func:`PyWeakref_GET_OBJECT` functions, which return a :term:`borrowed
- reference`: use the new :c:func:`PyWeakref_GetRef` function instead, it
- returns a :term:`strong reference`. The `pythoncapi-compat project
- `__ can be used to get
- :c:func:`PyWeakref_GetRef` on Python 3.12 and older.
- (Contributed by Victor Stinner in :gh:`105927`.)
-
-* Deprecate the :c:func:`PyEval_GetBuiltins`, :c:func:`PyEval_GetGlobals`, and
- :c:func:`PyEval_GetLocals` functions, which return a :term:`borrowed reference`.
- Refer to the deprecation notices on each function for their recommended replacements.
- (Soft deprecated as part of :pep:`667`.)
-
-.. Add deprecations above alphabetically, not here at the end.
-
-.. include:: ../deprecations/c-api-pending-removal-in-3.14.rst
-
-.. include:: ../deprecations/c-api-pending-removal-in-3.15.rst
-
-.. include:: ../deprecations/c-api-pending-removal-in-future.rst
+* :ref:`PEP 667 ` introduces several changes
+ to frame-related functions:
+
+ * The effects of mutating the dictionary returned from
+ :c:func:`PyEval_GetLocals` in an :term:`optimized scope` have changed.
+ New dict entries added this way will now *only* be visible to
+ subsequent :c:func:`PyEval_GetLocals` calls in that frame,
+ as :c:func:`PyFrame_GetLocals`, :func:`locals`,
+ and :attr:`FrameType.f_locals ` no longer access
+ the same underlying cached dictionary.
+ Changes made to entries for actual variable names and names added via
+ the write-through proxy interfaces will be overwritten on subsequent calls
+ to :c:func:`PyEval_GetLocals` in that frame.
+ The recommended code update depends on how the function was being used,
+ so refer to the deprecation notice on the function for details.
+
+ * Calling :c:func:`PyFrame_GetLocals` in an :term:`optimized scope`
+ now returns a write-through proxy rather than a snapshot
+ that gets updated at ill-specified times.
+ If a snapshot is desired, it must be created explicitly
+ (e.g. with :c:func:`PyDict_Copy`),
+ or by calling the new :c:func:`PyEval_GetFrameLocals` API.
+
+ * :c:func:`!PyFrame_FastToLocals` and :c:func:`!PyFrame_FastToLocalsWithError`
+ no longer have any effect.
+ Calling these functions has been redundant since Python 3.11,
+ when :c:func:`PyFrame_GetLocals` was first introduced.
+
+ * :c:func:`!PyFrame_LocalsToFast` no longer has any effect.
+ Calling this function is redundant now that :c:func:`PyFrame_GetLocals`
+ returns a write-through proxy for :term:`optimized scopes `.
Regression Test Changes
=======================
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index 5e762336e547f6..4d22f7ac58a5b7 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -70,6 +70,91 @@ Summary -- Release highlights
New Features
============
+.. _whatsnew-314-pep649:
+
+PEP 649: Deferred Evaluation of Annotations
+-------------------------------------------
+
+The :term:`annotations ` on functions, classes, and modules are no
+longer evaluated eagerly. Instead, annotations are stored in special-purpose
+:term:`annotate functions ` and evaluated only when
+necessary. This is specified in :pep:`649` and :pep:`749`.
+
+This change is designed to make annotations in Python more performant and more
+usable in most circumstances. The runtime cost for defining annotations is
+minimized, but it remains possible to introspect annotations at runtime.
+It is usually no longer necessary to enclose annotations in strings if they
+contain forward references.
+
+The new :mod:`annotationlib` module provides tools for inspecting deferred
+annotations. Annotations may be evaluated in the :attr:`~annotationlib.Format.VALUE`
+format (which evaluates annotations to runtime values, similar to the behavior in
+earlier Python versions), the :attr:`~annotationlib.Format.FORWARDREF` format
+(which replaces undefined names with special markers), and the
+:attr:`~annotationlib.Format.SOURCE` format (which returns annotations as strings).
+
+This example shows how these formats behave:
+
+.. doctest::
+
+ >>> from annotationlib import get_annotations, Format
+ >>> def func(arg: Undefined):
+ ... pass
+ >>> get_annotations(func, format=Format.VALUE)
+ Traceback (most recent call last):
+ ...
+ NameError: name 'Undefined' is not defined
+ >>> get_annotations(func, format=Format.FORWARDREF)
+ {'arg': ForwardRef('Undefined')}
+ >>> get_annotations(func, format=Format.SOURCE)
+ {'arg': 'Undefined'}
+
+Implications for annotated code
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If you define annotations in your code (for example, for use with a static type
+checker), then this change probably does not affect you: you can keep
+writing annotations the same way you did with previous versions of Python.
+
+You will likely be able to remove quoted strings in annotations, which are frequently
+used for forward references. Similarly, if you use ``from __future__ import annotations``
+to avoid having to write strings in annotations, you may well be able to
+remove that import. However, if you rely on third-party libraries that read annotations,
+those libraries may need changes to support unquoted annotations before they
+work as expected.
+
+Implications for readers of ``__annotations__``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If your code reads the ``__annotations__`` attribute on objects, you may want
+to make changes in order to support code that relies on deferred evaluation of
+annotations. For example, you may want to use :func:`annotationlib.get_annotations`
+with the :attr:`~annotationlib.Format.FORWARDREF` format, as the :mod:`dataclasses`
+module now does.
+
+Related changes
+^^^^^^^^^^^^^^^
+
+The changes in Python 3.14 are designed to rework how ``__annotations__``
+works at runtime while minimizing breakage to code that contains
+annotations in source code and to code that reads ``__annotations__``. However,
+if you rely on undocumented details of the annotation behavior or on private
+functions in the standard library, there are many ways in which your code may
+not work in Python 3.14. To safeguard your code against future changes,
+use only the documented functionality of the :mod:`annotationlib` module.
+
+``from __future__ import annotations``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+In Python 3.7, :pep:`563` introduced the ``from __future__ import annotations``
+directive, which turns all annotations into strings. This directive is now
+considered deprecated and it is expected to be removed in a future version of Python.
+However, this removal will not happen until after Python 3.13, the last version of
+Python without deferred evaluation of annotations, reaches its end of life.
+In Python 3.14, the behavior of code using ``from __future__ import annotations``
+is unchanged.
+
+
Improved Error Messages
-----------------------
@@ -109,7 +194,9 @@ Other Language Changes
New Modules
===========
-* None yet.
+* :mod:`annotationlib`: For introspecting :term:`annotations `.
+ See :pep:`749` for more details.
+ (Contributed by Jelle Zijlstra in :gh:`119180`.)
Improved Modules
@@ -231,6 +318,9 @@ pdb
:pdbcmd:`commands` are preserved across hard-coded breakpoints.
(Contributed by Tian Gao in :gh:`121450`.)
+* Added a new argument ``mode`` to :class:`pdb.Pdb`. Disabled ``restart``
+ command when :mod:`pdb` is in ``inline`` mode.
+ (Contributed by Tian Gao in :gh:`123757`.)
pickle
------
@@ -554,13 +644,19 @@ New Features
(Contributed by Victor Stinner in :gh:`107954`.)
+* Add :c:func:`PyType_GetBaseByToken` and :c:data:`Py_tp_token` slot for easier
+ superclass identification, which attempts to resolve the `type checking issue
+ `__ mentioned in :pep:`630`
+ (:gh:`124153`).
+
Porting to Python 3.14
----------------------
-* In the limited C API 3.14 and newer, :c:func:`Py_TYPE` is now implemented as
- an opaque function call to hide implementation details.
- (Contributed by Victor Stinner in :gh:`120600`.)
+* In the limited C API 3.14 and newer, :c:func:`Py_TYPE` and
+ :c:func:`Py_REFCNT` are now implemented as an opaque function call to hide
+ implementation details.
+ (Contributed by Victor Stinner in :gh:`120600` and :gh:`124127`.)
Deprecated
diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst
index 35ae0e338d9c03..d0e60bc280a217 100644
--- a/Doc/whatsnew/3.8.rst
+++ b/Doc/whatsnew/3.8.rst
@@ -1755,7 +1755,7 @@ The following features and APIs have been removed from Python 3.8:
* Starting with Python 3.3, importing ABCs from :mod:`collections` was
deprecated, and importing should be done from :mod:`collections.abc`. Being
able to import from collections was marked for removal in 3.8, but has been
- delayed to 3.9. (See :issue:`36952`.)
+ delayed to 3.9. (See :gh:`81134`.)
* The :mod:`macpath` module, deprecated in Python 3.7, has been removed.
(Contributed by Victor Stinner in :issue:`35471`.)
diff --git a/Include/cpython/context.h b/Include/cpython/context.h
index a3249fc29b082e..a509f4eaba3d77 100644
--- a/Include/cpython/context.h
+++ b/Include/cpython/context.h
@@ -27,6 +27,38 @@ PyAPI_FUNC(PyObject *) PyContext_CopyCurrent(void);
PyAPI_FUNC(int) PyContext_Enter(PyObject *);
PyAPI_FUNC(int) PyContext_Exit(PyObject *);
+typedef enum {
+ Py_CONTEXT_EVENT_ENTER,
+ Py_CONTEXT_EVENT_EXIT,
+} PyContextEvent;
+
+/*
+ * A Callback to clue in non-python contexts impls about a
+ * change in the active python context.
+ *
+ * The callback is invoked with the event and a reference to =
+ * the context after its entered and before its exited.
+ *
+ * if the callback returns with an exception set, it must return -1. Otherwise
+ * it should return 0
+ */
+typedef int (*PyContext_WatchCallback)(PyContextEvent, PyContext *);
+
+/*
+ * Register a per-interpreter callback that will be invoked for context object
+ * enter/exit events.
+ *
+ * Returns a handle that may be passed to PyContext_ClearWatcher on success,
+ * or -1 and sets and error if no more handles are available.
+ */
+PyAPI_FUNC(int) PyContext_AddWatcher(PyContext_WatchCallback callback);
+
+/*
+ * Clear the watcher associated with the watcher_id handle.
+ *
+ * Returns 0 on success or -1 if no watcher exists for the provided id.
+ */
+PyAPI_FUNC(int) PyContext_ClearWatcher(int watcher_id);
/* Create a new context variable.
diff --git a/Include/cpython/object.h b/Include/cpython/object.h
index e1024ddbdf6062..9d092749b90096 100644
--- a/Include/cpython/object.h
+++ b/Include/cpython/object.h
@@ -269,6 +269,7 @@ typedef struct _heaptypeobject {
struct _dictkeysobject *ht_cached_keys;
PyObject *ht_module;
char *_ht_tpname; // Storage for "tp_name"; see PyType_FromModuleAndSpec
+ void *ht_token; // Storage for the "Py_tp_token" slot
struct _specialization_cache _spec_cache; // For use by the specializer.
#ifdef Py_GIL_DISABLED
Py_ssize_t unique_id; // ID used for thread-local refcounting
diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h
index f005729fff11b6..32f68378ea5d72 100644
--- a/Include/cpython/pystate.h
+++ b/Include/cpython/pystate.h
@@ -218,9 +218,15 @@ struct _ts {
# define Py_C_RECURSION_LIMIT 3000
#elif defined(_Py_ADDRESS_SANITIZER)
# define Py_C_RECURSION_LIMIT 4000
+#elif defined(__sparc__)
+ // test_descr crashed on sparc64 with >7000 but let's keep a margin of error.
+# define Py_C_RECURSION_LIMIT 4000
#elif defined(__wasi__)
// Based on wasmtime 16.
# define Py_C_RECURSION_LIMIT 5000
+#elif defined(__hppa__) || defined(__powerpc64__)
+ // test_descr crashed with >8000 but let's keep a margin of error.
+# define Py_C_RECURSION_LIMIT 5000
#else
// This value is duplicated in Lib/test/support/__init__.py
# define Py_C_RECURSION_LIMIT 10000
diff --git a/Include/cpython/pystats.h b/Include/cpython/pystats.h
index c4480758f48514..f1ca54839fbc38 100644
--- a/Include/cpython/pystats.h
+++ b/Include/cpython/pystats.h
@@ -70,6 +70,10 @@ typedef struct _object_stats {
uint64_t decrefs;
uint64_t interpreter_increfs;
uint64_t interpreter_decrefs;
+ uint64_t immortal_increfs;
+ uint64_t immortal_decrefs;
+ uint64_t interpreter_immortal_increfs;
+ uint64_t interpreter_immortal_decrefs;
uint64_t allocations;
uint64_t allocations512;
uint64_t allocations4k;
@@ -163,7 +167,11 @@ PyAPI_DATA(PyStats*) _Py_stats;
#ifdef _PY_INTERPRETER
# define _Py_INCREF_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.interpreter_increfs++; } while (0)
# define _Py_DECREF_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.interpreter_decrefs++; } while (0)
+# define _Py_INCREF_IMMORTAL_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.interpreter_immortal_increfs++; } while (0)
+# define _Py_DECREF_IMMORTAL_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.interpreter_immortal_decrefs++; } while (0)
#else
# define _Py_INCREF_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.increfs++; } while (0)
# define _Py_DECREF_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.decrefs++; } while (0)
+# define _Py_INCREF_IMMORTAL_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.immortal_increfs++; } while (0)
+# define _Py_DECREF_IMMORTAL_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.immortal_decrefs++; } while (0)
#endif
diff --git a/Include/internal/pycore_context.h b/Include/internal/pycore_context.h
index 2ecb40ba584f7f..c2b98d15da68fa 100644
--- a/Include/internal/pycore_context.h
+++ b/Include/internal/pycore_context.h
@@ -7,6 +7,7 @@
#include "pycore_hamt.h" // PyHamtObject
+#define CONTEXT_MAX_WATCHERS 8
extern PyTypeObject _PyContextTokenMissing_Type;
diff --git a/Include/internal/pycore_frame.h b/Include/internal/pycore_frame.h
index fadecaf75be37b..5f29c1ef9490e7 100644
--- a/Include/internal/pycore_frame.h
+++ b/Include/internal/pycore_frame.h
@@ -62,7 +62,7 @@ enum _frameowner {
typedef struct _PyInterpreterFrame {
_PyStackRef f_executable; /* Deferred or strong reference (code object or None) */
struct _PyInterpreterFrame *previous;
- PyObject *f_funcobj; /* Strong reference. Only valid if not on C stack */
+ _PyStackRef f_funcobj; /* Deferred or strong reference. Only valid if not on C stack */
PyObject *f_globals; /* Borrowed reference. Only valid if not on C stack */
PyObject *f_builtins; /* Borrowed reference. Only valid if not on C stack */
PyObject *f_locals; /* Strong reference, may be NULL. Only valid if not on C stack */
@@ -97,6 +97,12 @@ _PyFrame_GetBytecode(_PyInterpreterFrame *f)
#endif
}
+static inline PyFunctionObject *_PyFrame_GetFunction(_PyInterpreterFrame *f) {
+ PyObject *func = PyStackRef_AsPyObjectBorrow(f->f_funcobj);
+ assert(PyFunction_Check(func));
+ return (PyFunctionObject *)func;
+}
+
static inline _PyStackRef *_PyFrame_Stackbase(_PyInterpreterFrame *f) {
return (f->localsplus + _PyFrame_GetCode(f)->co_nlocalsplus);
}
@@ -157,14 +163,15 @@ static inline void _PyFrame_Copy(_PyInterpreterFrame *src, _PyInterpreterFrame *
*/
static inline void
_PyFrame_Initialize(
- _PyInterpreterFrame *frame, PyFunctionObject *func,
+ _PyInterpreterFrame *frame, _PyStackRef func,
PyObject *locals, PyCodeObject *code, int null_locals_from, _PyInterpreterFrame *previous)
{
frame->previous = previous;
- frame->f_funcobj = (PyObject *)func;
+ frame->f_funcobj = func;
frame->f_executable = PyStackRef_FromPyObjectNew(code);
- frame->f_builtins = func->func_builtins;
- frame->f_globals = func->func_globals;
+ PyFunctionObject *func_obj = (PyFunctionObject *)PyStackRef_AsPyObjectBorrow(func);
+ frame->f_builtins = func_obj->func_builtins;
+ frame->f_globals = func_obj->func_globals;
frame->f_locals = locals;
frame->stackpointer = frame->localsplus + code->co_nlocalsplus;
frame->frame_obj = NULL;
@@ -315,10 +322,11 @@ PyAPI_FUNC(void) _PyThreadState_PopFrame(PyThreadState *tstate, _PyInterpreterFr
* Must be guarded by _PyThreadState_HasStackSpace()
* Consumes reference to func. */
static inline _PyInterpreterFrame *
-_PyFrame_PushUnchecked(PyThreadState *tstate, PyFunctionObject *func, int null_locals_from, _PyInterpreterFrame * previous)
+_PyFrame_PushUnchecked(PyThreadState *tstate, _PyStackRef func, int null_locals_from, _PyInterpreterFrame * previous)
{
CALL_STAT_INC(frames_pushed);
- PyCodeObject *code = (PyCodeObject *)func->func_code;
+ PyFunctionObject *func_obj = (PyFunctionObject *)PyStackRef_AsPyObjectBorrow(func);
+ PyCodeObject *code = (PyCodeObject *)func_obj->func_code;
_PyInterpreterFrame *new_frame = (_PyInterpreterFrame *)tstate->datastack_top;
tstate->datastack_top += code->co_framesize;
assert(tstate->datastack_top < tstate->datastack_limit);
@@ -336,7 +344,7 @@ _PyFrame_PushTrampolineUnchecked(PyThreadState *tstate, PyCodeObject *code, int
tstate->datastack_top += code->co_framesize;
assert(tstate->datastack_top < tstate->datastack_limit);
frame->previous = previous;
- frame->f_funcobj = Py_None;
+ frame->f_funcobj = PyStackRef_None;
frame->f_executable = PyStackRef_FromPyObjectNew(code);
#ifdef Py_DEBUG
frame->f_builtins = NULL;
@@ -361,7 +369,7 @@ _PyFrame_PushTrampolineUnchecked(PyThreadState *tstate, PyCodeObject *code, int
}
PyAPI_FUNC(_PyInterpreterFrame *)
-_PyEvalFramePushAndInit(PyThreadState *tstate, PyFunctionObject *func,
+_PyEvalFramePushAndInit(PyThreadState *tstate, _PyStackRef func,
PyObject *locals, _PyStackRef const* args,
size_t argcount, PyObject *kwnames,
_PyInterpreterFrame *previous);
diff --git a/Include/internal/pycore_freelist.h b/Include/internal/pycore_freelist.h
index 1ac0fed584ebd9..da2d7bf6ae1393 100644
--- a/Include/internal/pycore_freelist.h
+++ b/Include/internal/pycore_freelist.h
@@ -28,13 +28,6 @@ _Py_freelists_GET(void)
#endif
}
-#ifndef WITH_FREELISTS
-#define _Py_FREELIST_FREE(NAME, op, freefunc) freefunc(op)
-#define _Py_FREELIST_PUSH(NAME, op, limit) (0)
-#define _Py_FREELIST_POP(TYPE, NAME) (NULL)
-#define _Py_FREELIST_POP_MEM(NAME) (NULL)
-#define _Py_FREELIST_SIZE(NAME) (0)
-#else
// Pushes `op` to the freelist, calls `freefunc` if the freelist is full
#define _Py_FREELIST_FREE(NAME, op, freefunc) \
_PyFreeList_Free(&_Py_freelists_GET()->NAME, _PyObject_CAST(op), \
@@ -108,7 +101,6 @@ _PyFreeList_PopMem(struct _Py_freelist *fl)
}
return op;
}
-#endif
extern void _PyObject_ClearFreeLists(struct _Py_freelists *freelists, int is_finalization);
diff --git a/Include/internal/pycore_freelist_state.h b/Include/internal/pycore_freelist_state.h
index e8df784bcba06e..762c583ce94e9a 100644
--- a/Include/internal/pycore_freelist_state.h
+++ b/Include/internal/pycore_freelist_state.h
@@ -8,8 +8,6 @@ extern "C" {
# error "this header requires Py_BUILD_CORE define"
#endif
-#ifdef WITH_FREELISTS
-// with freelists
# define PyTuple_MAXSAVESIZE 20 // Largest tuple to save on freelist
# define Py_tuple_MAXFREELIST 2000 // Maximum number of tuples of each size to save
# define Py_lists_MAXFREELIST 80
@@ -22,9 +20,6 @@ extern "C" {
# define Py_async_gen_asends_MAXFREELIST 80
# define Py_futureiters_MAXFREELIST 255
# define Py_object_stack_chunks_MAXFREELIST 4
-#else
-# define PyTuple_MAXSAVESIZE 0
-#endif
// A generic freelist of either PyObjects or other data structures.
struct _Py_freelist {
@@ -38,7 +33,6 @@ struct _Py_freelist {
};
struct _Py_freelists {
-#ifdef WITH_FREELISTS
struct _Py_freelist floats;
struct _Py_freelist tuples[PyTuple_MAXSAVESIZE];
struct _Py_freelist lists;
@@ -50,9 +44,6 @@ struct _Py_freelists {
struct _Py_freelist async_gen_asends;
struct _Py_freelist futureiters;
struct _Py_freelist object_stack_chunks;
-#else
- char _unused; // Empty structs are not allowed.
-#endif
};
#ifdef __cplusplus
diff --git a/Include/internal/pycore_gc.h b/Include/internal/pycore_gc.h
index cb67a7ee2b3402..cf96f661e6cd7e 100644
--- a/Include/internal/pycore_gc.h
+++ b/Include/internal/pycore_gc.h
@@ -387,6 +387,17 @@ union _PyStackRef;
extern int _PyGC_VisitFrameStack(struct _PyInterpreterFrame *frame, visitproc visit, void *arg);
extern int _PyGC_VisitStackRef(union _PyStackRef *ref, visitproc visit, void *arg);
+// Like Py_VISIT but for _PyStackRef fields
+#define _Py_VISIT_STACKREF(ref) \
+ do { \
+ if (!PyStackRef_IsNull(ref)) { \
+ int vret = _PyGC_VisitStackRef(&(ref), visit, arg); \
+ if (vret) \
+ return vret; \
+ } \
+ } while (0)
+
+
#ifdef __cplusplus
}
#endif
diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h
index 8080d894e77f95..8f681796e27bc7 100644
--- a/Include/internal/pycore_interp.h
+++ b/Include/internal/pycore_interp.h
@@ -242,8 +242,10 @@ struct _is {
PyObject *audit_hooks;
PyType_WatchCallback type_watchers[TYPE_MAX_WATCHERS];
PyCode_WatchCallback code_watchers[CODE_MAX_WATCHERS];
+ PyContext_WatchCallback context_watchers[CONTEXT_MAX_WATCHERS];
// One bit is set for each non-NULL entry in code_watchers
uint8_t active_code_watchers;
+ uint8_t active_context_watchers;
struct _py_object_state object_state;
struct _Py_unicode_state unicode;
diff --git a/Include/internal/pycore_magic_number.h b/Include/internal/pycore_magic_number.h
index 095eb0f8a89b79..2414d25d41bfbf 100644
--- a/Include/internal/pycore_magic_number.h
+++ b/Include/internal/pycore_magic_number.h
@@ -258,6 +258,7 @@ Known values:
Python 3.14a1 3604 (Do not duplicate test at end of while statements)
Python 3.14a1 3605 (Move ENTER_EXECUTOR to opcode 255)
Python 3.14a1 3606 (Specialize CALL_KW)
+ Python 3.14a1 3607 (Add pseudo instructions JUMP_IF_TRUE/FALSE)
Python 3.15 will start with 3650
@@ -270,7 +271,7 @@ PC/launcher.c must also be updated.
*/
-#define PYC_MAGIC_NUMBER 3606
+#define PYC_MAGIC_NUMBER 3607
/* This is equivalent to converting PYC_MAGIC_NUMBER to 2 bytes
(little-endian) and then appending b'\r\n'. */
#define PYC_MAGIC_NUMBER_TOKEN \
diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h
index ad92a74d2b6b56..80b588815bc9cf 100644
--- a/Include/internal/pycore_object.h
+++ b/Include/internal/pycore_object.h
@@ -214,6 +214,7 @@ static inline void
_Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct)
{
if (_Py_IsImmortal(op)) {
+ _Py_DECREF_IMMORTAL_STAT_INC();
return;
}
_Py_DECREF_STAT_INC();
@@ -235,6 +236,7 @@ static inline void
_Py_DECREF_NO_DEALLOC(PyObject *op)
{
if (_Py_IsImmortal(op)) {
+ _Py_DECREF_IMMORTAL_STAT_INC();
return;
}
_Py_DECREF_STAT_INC();
@@ -315,6 +317,7 @@ _Py_INCREF_TYPE(PyTypeObject *type)
{
if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
assert(_Py_IsImmortalLoose(type));
+ _Py_INCREF_IMMORTAL_STAT_INC();
return;
}
@@ -355,6 +358,7 @@ _Py_DECREF_TYPE(PyTypeObject *type)
{
if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
assert(_Py_IsImmortalLoose(type));
+ _Py_DECREF_IMMORTAL_STAT_INC();
return;
}
@@ -511,6 +515,7 @@ _Py_TryIncrefFast(PyObject *op) {
local += 1;
if (local == 0) {
// immortal
+ _Py_INCREF_IMMORTAL_STAT_INC();
return 1;
}
if (_Py_IsOwnedByCurrentThread(op)) {
@@ -727,12 +732,15 @@ _PyObject_GET_WEAKREFS_LISTPTR_FROM_OFFSET(PyObject *op)
return (PyWeakReference **)((char *)op + offset);
}
+// Fast inlined version of PyType_IS_GC()
+#define _PyType_IS_GC(t) _PyType_HasFeature((t), Py_TPFLAGS_HAVE_GC)
+
// Fast inlined version of PyObject_IS_GC()
static inline int
_PyObject_IS_GC(PyObject *obj)
{
PyTypeObject *type = Py_TYPE(obj);
- return (PyType_IS_GC(type)
+ return (_PyType_IS_GC(type)
&& (type->tp_is_gc == NULL || type->tp_is_gc(obj)));
}
@@ -750,9 +758,6 @@ _PyObject_HashFast(PyObject *op)
return PyObject_Hash(op);
}
-// Fast inlined version of PyType_IS_GC()
-#define _PyType_IS_GC(t) _PyType_HasFeature((t), Py_TPFLAGS_HAVE_GC)
-
static inline size_t
_PyType_PreHeaderSize(PyTypeObject *tp)
{
diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h
index a7b19ee158bcd2..adb1c22987a9fb 100644
--- a/Include/internal/pycore_opcode_metadata.h
+++ b/Include/internal/pycore_opcode_metadata.h
@@ -22,6 +22,8 @@ extern "C" {
((OP) == STORE_FAST_MAYBE_NULL) || \
((OP) == JUMP) || \
((OP) == JUMP_NO_INTERRUPT) || \
+ ((OP) == JUMP_IF_FALSE) || \
+ ((OP) == JUMP_IF_TRUE) || \
((OP) == SETUP_FINALLY) || \
((OP) == SETUP_CLEANUP) || \
((OP) == SETUP_WITH) || \
@@ -269,6 +271,10 @@ int _PyOpcode_num_popped(int opcode, int oparg) {
return 0;
case JUMP_FORWARD:
return 0;
+ case JUMP_IF_FALSE:
+ return 1;
+ case JUMP_IF_TRUE:
+ return 1;
case JUMP_NO_INTERRUPT:
return 0;
case LIST_APPEND:
@@ -726,6 +732,10 @@ int _PyOpcode_num_pushed(int opcode, int oparg) {
return 0;
case JUMP_FORWARD:
return 0;
+ case JUMP_IF_FALSE:
+ return 1;
+ case JUMP_IF_TRUE:
+ return 1;
case JUMP_NO_INTERRUPT:
return 0;
case LIST_APPEND:
@@ -956,7 +966,7 @@ enum InstructionFormat {
};
#define IS_VALID_OPCODE(OP) \
- (((OP) >= 0) && ((OP) < 264) && \
+ (((OP) >= 0) && ((OP) < 266) && \
(_PyOpcode_opcode_metadata[(OP)].valid_entry))
#define HAS_ARG_FLAG (1)
@@ -1005,9 +1015,9 @@ struct opcode_metadata {
int16_t flags;
};
-extern const struct opcode_metadata _PyOpcode_opcode_metadata[264];
+extern const struct opcode_metadata _PyOpcode_opcode_metadata[266];
#ifdef NEED_OPCODE_METADATA
-const struct opcode_metadata _PyOpcode_opcode_metadata[264] = {
+const struct opcode_metadata _PyOpcode_opcode_metadata[266] = {
[BINARY_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[BINARY_OP_ADD_FLOAT] = { true, INSTR_FMT_IXC, HAS_EXIT_FLAG },
[BINARY_OP_ADD_INT] = { true, INSTR_FMT_IXC, HAS_EXIT_FLAG | HAS_ERROR_FLAG },
@@ -1224,6 +1234,8 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[264] = {
[YIELD_VALUE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG },
[_DO_CALL_FUNCTION_EX] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
[JUMP] = { true, -1, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
+ [JUMP_IF_FALSE] = { true, -1, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
+ [JUMP_IF_TRUE] = { true, -1, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[JUMP_NO_INTERRUPT] = { true, -1, HAS_ARG_FLAG | HAS_JUMP_FLAG },
[LOAD_CLOSURE] = { true, -1, HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_PURE_FLAG },
[POP_BLOCK] = { true, -1, HAS_PURE_FLAG },
@@ -1422,9 +1434,9 @@ _PyOpcode_macro_expansion[256] = {
};
#endif // NEED_OPCODE_METADATA
-extern const char *_PyOpcode_OpName[264];
+extern const char *_PyOpcode_OpName[266];
#ifdef NEED_OPCODE_METADATA
-const char *_PyOpcode_OpName[264] = {
+const char *_PyOpcode_OpName[266] = {
[BINARY_OP] = "BINARY_OP",
[BINARY_OP_ADD_FLOAT] = "BINARY_OP_ADD_FLOAT",
[BINARY_OP_ADD_INT] = "BINARY_OP_ADD_INT",
@@ -1543,6 +1555,8 @@ const char *_PyOpcode_OpName[264] = {
[JUMP_BACKWARD] = "JUMP_BACKWARD",
[JUMP_BACKWARD_NO_INTERRUPT] = "JUMP_BACKWARD_NO_INTERRUPT",
[JUMP_FORWARD] = "JUMP_FORWARD",
+ [JUMP_IF_FALSE] = "JUMP_IF_FALSE",
+ [JUMP_IF_TRUE] = "JUMP_IF_TRUE",
[JUMP_NO_INTERRUPT] = "JUMP_NO_INTERRUPT",
[LIST_APPEND] = "LIST_APPEND",
[LIST_EXTEND] = "LIST_EXTEND",
@@ -1943,25 +1957,28 @@ const uint8_t _PyOpcode_Deopt[256] = {
case 235: \
;
struct pseudo_targets {
- uint8_t targets[3];
+ uint8_t as_sequence;
+ uint8_t targets[4];
};
-extern const struct pseudo_targets _PyOpcode_PseudoTargets[8];
+extern const struct pseudo_targets _PyOpcode_PseudoTargets[10];
#ifdef NEED_OPCODE_METADATA
-const struct pseudo_targets _PyOpcode_PseudoTargets[8] = {
- [LOAD_CLOSURE-256] = { { LOAD_FAST, 0, 0 } },
- [STORE_FAST_MAYBE_NULL-256] = { { STORE_FAST, 0, 0 } },
- [JUMP-256] = { { JUMP_FORWARD, JUMP_BACKWARD, 0 } },
- [JUMP_NO_INTERRUPT-256] = { { JUMP_FORWARD, JUMP_BACKWARD_NO_INTERRUPT, 0 } },
- [SETUP_FINALLY-256] = { { NOP, 0, 0 } },
- [SETUP_CLEANUP-256] = { { NOP, 0, 0 } },
- [SETUP_WITH-256] = { { NOP, 0, 0 } },
- [POP_BLOCK-256] = { { NOP, 0, 0 } },
+const struct pseudo_targets _PyOpcode_PseudoTargets[10] = {
+ [LOAD_CLOSURE-256] = { 0, { LOAD_FAST, 0, 0, 0 } },
+ [STORE_FAST_MAYBE_NULL-256] = { 0, { STORE_FAST, 0, 0, 0 } },
+ [JUMP-256] = { 0, { JUMP_FORWARD, JUMP_BACKWARD, 0, 0 } },
+ [JUMP_NO_INTERRUPT-256] = { 0, { JUMP_FORWARD, JUMP_BACKWARD_NO_INTERRUPT, 0, 0 } },
+ [JUMP_IF_FALSE-256] = { 1, { COPY, TO_BOOL, POP_JUMP_IF_FALSE, 0 } },
+ [JUMP_IF_TRUE-256] = { 1, { COPY, TO_BOOL, POP_JUMP_IF_TRUE, 0 } },
+ [SETUP_FINALLY-256] = { 0, { NOP, 0, 0, 0 } },
+ [SETUP_CLEANUP-256] = { 0, { NOP, 0, 0, 0 } },
+ [SETUP_WITH-256] = { 0, { NOP, 0, 0, 0 } },
+ [POP_BLOCK-256] = { 0, { NOP, 0, 0, 0 } },
};
#endif // NEED_OPCODE_METADATA
static inline bool
is_pseudo_target(int pseudo, int target) {
- if (pseudo < 256 || pseudo >= 264) {
+ if (pseudo < 256 || pseudo >= 266) {
return false;
}
for (int i = 0; _PyOpcode_PseudoTargets[pseudo-256].targets[i]; i++) {
diff --git a/Include/object.h b/Include/object.h
index abfdb6ce24df21..7124f58f6bdb37 100644
--- a/Include/object.h
+++ b/Include/object.h
@@ -391,6 +391,10 @@ PyAPI_FUNC(PyObject *) PyType_FromMetaclass(PyTypeObject*, PyObject*, PyType_Spe
PyAPI_FUNC(void *) PyObject_GetTypeData(PyObject *obj, PyTypeObject *cls);
PyAPI_FUNC(Py_ssize_t) PyType_GetTypeDataSize(PyTypeObject *cls);
#endif
+#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030E0000
+PyAPI_FUNC(int) PyType_GetBaseByToken(PyTypeObject *, void *, PyTypeObject **);
+#define Py_TP_USE_SPEC NULL
+#endif
/* Generic type check */
PyAPI_FUNC(int) PyType_IsSubtype(PyTypeObject *, PyTypeObject *);
diff --git a/Include/opcode_ids.h b/Include/opcode_ids.h
index 5ded0b41b4830e..8ba1ab25a77770 100644
--- a/Include/opcode_ids.h
+++ b/Include/opcode_ids.h
@@ -226,13 +226,15 @@ extern "C" {
#define INSTRUMENTED_LINE 254
#define ENTER_EXECUTOR 255
#define JUMP 256
-#define JUMP_NO_INTERRUPT 257
-#define LOAD_CLOSURE 258
-#define POP_BLOCK 259
-#define SETUP_CLEANUP 260
-#define SETUP_FINALLY 261
-#define SETUP_WITH 262
-#define STORE_FAST_MAYBE_NULL 263
+#define JUMP_IF_FALSE 257
+#define JUMP_IF_TRUE 258
+#define JUMP_NO_INTERRUPT 259
+#define LOAD_CLOSURE 260
+#define POP_BLOCK 261
+#define SETUP_CLEANUP 262
+#define SETUP_FINALLY 263
+#define SETUP_WITH 264
+#define STORE_FAST_MAYBE_NULL 265
#define HAVE_ARGUMENT 41
#define MIN_SPECIALIZED_OPCODE 150
diff --git a/Include/pystats.h b/Include/pystats.h
index acfa32201711e0..a515570d1bb3bc 100644
--- a/Include/pystats.h
+++ b/Include/pystats.h
@@ -18,6 +18,8 @@ extern "C" {
#else
# define _Py_INCREF_STAT_INC() ((void)0)
# define _Py_DECREF_STAT_INC() ((void)0)
+# define _Py_INCREF_IMMORTAL_STAT_INC() ((void)0)
+# define _Py_DECREF_IMMORTAL_STAT_INC() ((void)0)
#endif // !Py_STATS
#ifdef __cplusplus
diff --git a/Include/refcount.h b/Include/refcount.h
index a0bd2087fb1b57..9a4e15065ecab8 100644
--- a/Include/refcount.h
+++ b/Include/refcount.h
@@ -77,21 +77,29 @@ check by comparing the reference count field to the immortality reference count.
#endif // Py_GIL_DISABLED
-static inline Py_ssize_t Py_REFCNT(PyObject *ob) {
-#if !defined(Py_GIL_DISABLED)
- return ob->ob_refcnt;
+// Py_REFCNT() implementation for the stable ABI
+PyAPI_FUNC(Py_ssize_t) Py_REFCNT(PyObject *ob);
+
+#if defined(Py_LIMITED_API) && Py_LIMITED_API+0 >= 0x030e0000
+ // Stable ABI implements Py_REFCNT() as a function call
+ // on limited C API version 3.14 and newer.
#else
- uint32_t local = _Py_atomic_load_uint32_relaxed(&ob->ob_ref_local);
- if (local == _Py_IMMORTAL_REFCNT_LOCAL) {
- return _Py_IMMORTAL_REFCNT;
+ static inline Py_ssize_t _Py_REFCNT(PyObject *ob) {
+ #if !defined(Py_GIL_DISABLED)
+ return ob->ob_refcnt;
+ #else
+ uint32_t local = _Py_atomic_load_uint32_relaxed(&ob->ob_ref_local);
+ if (local == _Py_IMMORTAL_REFCNT_LOCAL) {
+ return _Py_IMMORTAL_REFCNT;
+ }
+ Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&ob->ob_ref_shared);
+ return _Py_STATIC_CAST(Py_ssize_t, local) +
+ Py_ARITHMETIC_RIGHT_SHIFT(Py_ssize_t, shared, _Py_REF_SHARED_SHIFT);
+ #endif
}
- Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&ob->ob_ref_shared);
- return _Py_STATIC_CAST(Py_ssize_t, local) +
- Py_ARITHMETIC_RIGHT_SHIFT(Py_ssize_t, shared, _Py_REF_SHARED_SHIFT);
-#endif
-}
-#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000
-# define Py_REFCNT(ob) Py_REFCNT(_PyObject_CAST(ob))
+ #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000
+ # define Py_REFCNT(ob) _Py_REFCNT(_PyObject_CAST(ob))
+ #endif
#endif
@@ -227,6 +235,7 @@ static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op)
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
uint32_t new_local = local + 1;
if (new_local == 0) {
+ _Py_INCREF_IMMORTAL_STAT_INC();
// local is equal to _Py_IMMORTAL_REFCNT: do nothing
return;
}
@@ -241,6 +250,7 @@ static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op)
PY_UINT32_T cur_refcnt = op->ob_refcnt_split[PY_BIG_ENDIAN];
PY_UINT32_T new_refcnt = cur_refcnt + 1;
if (new_refcnt == 0) {
+ _Py_INCREF_IMMORTAL_STAT_INC();
// cur_refcnt is equal to _Py_IMMORTAL_REFCNT: the object is immortal,
// do nothing
return;
@@ -249,6 +259,7 @@ static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op)
#else
// Explicitly check immortality against the immortal value
if (_Py_IsImmortal(op)) {
+ _Py_INCREF_IMMORTAL_STAT_INC();
return;
}
op->ob_refcnt++;
@@ -295,6 +306,7 @@ static inline void Py_DECREF(const char *filename, int lineno, PyObject *op)
{
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
if (local == _Py_IMMORTAL_REFCNT_LOCAL) {
+ _Py_DECREF_IMMORTAL_STAT_INC();
return;
}
_Py_DECREF_STAT_INC();
@@ -320,6 +332,7 @@ static inline void Py_DECREF(PyObject *op)
{
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
if (local == _Py_IMMORTAL_REFCNT_LOCAL) {
+ _Py_DECREF_IMMORTAL_STAT_INC();
return;
}
_Py_DECREF_STAT_INC();
@@ -343,6 +356,7 @@ static inline void Py_DECREF(const char *filename, int lineno, PyObject *op)
_Py_NegativeRefcount(filename, lineno, op);
}
if (_Py_IsImmortal(op)) {
+ _Py_DECREF_IMMORTAL_STAT_INC();
return;
}
_Py_DECREF_STAT_INC();
@@ -359,6 +373,7 @@ static inline Py_ALWAYS_INLINE void Py_DECREF(PyObject *op)
// Non-limited C API and limited C API for Python 3.9 and older access
// directly PyObject.ob_refcnt.
if (_Py_IsImmortal(op)) {
+ _Py_DECREF_IMMORTAL_STAT_INC();
return;
}
_Py_DECREF_STAT_INC();
diff --git a/Include/typeslots.h b/Include/typeslots.h
index e91caa1509c34b..a7f3017ec02e92 100644
--- a/Include/typeslots.h
+++ b/Include/typeslots.h
@@ -90,3 +90,7 @@
/* New in 3.14 */
#define Py_tp_vectorcall 82
#endif
+#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030E0000
+/* New in 3.14 */
+#define Py_tp_token 83
+#endif
diff --git a/Lib/_opcode_metadata.py b/Lib/_opcode_metadata.py
index 6e4b33921863cb..dd70c5250c0b1e 100644
--- a/Lib/_opcode_metadata.py
+++ b/Lib/_opcode_metadata.py
@@ -335,13 +335,15 @@
'INSTRUMENTED_CALL': 252,
'INSTRUMENTED_JUMP_BACKWARD': 253,
'JUMP': 256,
- 'JUMP_NO_INTERRUPT': 257,
- 'LOAD_CLOSURE': 258,
- 'POP_BLOCK': 259,
- 'SETUP_CLEANUP': 260,
- 'SETUP_FINALLY': 261,
- 'SETUP_WITH': 262,
- 'STORE_FAST_MAYBE_NULL': 263,
+ 'JUMP_IF_FALSE': 257,
+ 'JUMP_IF_TRUE': 258,
+ 'JUMP_NO_INTERRUPT': 259,
+ 'LOAD_CLOSURE': 260,
+ 'POP_BLOCK': 261,
+ 'SETUP_CLEANUP': 262,
+ 'SETUP_FINALLY': 263,
+ 'SETUP_WITH': 264,
+ 'STORE_FAST_MAYBE_NULL': 265,
}
HAVE_ARGUMENT = 41
diff --git a/Lib/_pyio.py b/Lib/_pyio.py
index 75b5ad1b1a47d2..18849b309b8605 100644
--- a/Lib/_pyio.py
+++ b/Lib/_pyio.py
@@ -242,14 +242,7 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
buffering = -1
line_buffering = True
if buffering < 0:
- buffering = DEFAULT_BUFFER_SIZE
- try:
- bs = os.fstat(raw.fileno()).st_blksize
- except (OSError, AttributeError):
- pass
- else:
- if bs > 1:
- buffering = bs
+ buffering = raw._blksize
if buffering < 0:
raise ValueError("invalid buffering size")
if buffering == 0:
@@ -1565,19 +1558,15 @@ def __init__(self, file, mode='r', closefd=True, opener=None):
os.set_inheritable(fd, False)
self._closefd = closefd
- fdfstat = os.fstat(fd)
+ self._stat_atopen = os.fstat(fd)
try:
- if stat.S_ISDIR(fdfstat.st_mode):
+ if stat.S_ISDIR(self._stat_atopen.st_mode):
raise IsADirectoryError(errno.EISDIR,
os.strerror(errno.EISDIR), file)
except AttributeError:
# Ignore the AttributeError if stat.S_ISDIR or errno.EISDIR
# don't exist.
pass
- self._blksize = getattr(fdfstat, 'st_blksize', 0)
- if self._blksize <= 1:
- self._blksize = DEFAULT_BUFFER_SIZE
- self._estimated_size = fdfstat.st_size
if _setmode:
# don't translate newlines (\r\n <=> \n)
@@ -1623,6 +1612,17 @@ def __repr__(self):
return ('<%s name=%r mode=%r closefd=%r>' %
(class_name, name, self.mode, self._closefd))
+ @property
+ def _blksize(self):
+ if self._stat_atopen is None:
+ return DEFAULT_BUFFER_SIZE
+
+ blksize = getattr(self._stat_atopen, "st_blksize", 0)
+ # WASI sets blsize to 0
+ if not blksize:
+ return DEFAULT_BUFFER_SIZE
+ return blksize
+
def _checkReadable(self):
if not self._readable:
raise UnsupportedOperation('File not open for reading')
@@ -1655,16 +1655,20 @@ def readall(self):
"""
self._checkClosed()
self._checkReadable()
- if self._estimated_size <= 0:
+ if self._stat_atopen is None or self._stat_atopen.st_size <= 0:
bufsize = DEFAULT_BUFFER_SIZE
else:
- bufsize = self._estimated_size + 1
+ # In order to detect end of file, need a read() of at least 1
+ # byte which returns size 0. Oversize the buffer by 1 byte so the
+ # I/O can be completed with two read() calls (one for all data, one
+ # for EOF) without needing to resize the buffer.
+ bufsize = self._stat_atopen.st_size + 1
- if self._estimated_size > 65536:
+ if self._stat_atopen.st_size > 65536:
try:
pos = os.lseek(self._fd, 0, SEEK_CUR)
- if self._estimated_size >= pos:
- bufsize = self._estimated_size - pos + 1
+ if self._stat_atopen.st_size >= pos:
+ bufsize = self._stat_atopen.st_size - pos + 1
except OSError:
pass
@@ -1742,7 +1746,7 @@ def truncate(self, size=None):
if size is None:
size = self.tell()
os.ftruncate(self._fd, size)
- self._estimated_size = size
+ self._stat_atopen = None
return size
def close(self):
diff --git a/Lib/_pyrepl/pager.py b/Lib/_pyrepl/pager.py
index 66dcd99111adfc..1fddc63e3ee3ad 100644
--- a/Lib/_pyrepl/pager.py
+++ b/Lib/_pyrepl/pager.py
@@ -36,6 +36,8 @@ def get_pager() -> Pager:
return plain_pager
if sys.platform == 'win32':
return lambda text, title='': tempfile_pager(plain(text), 'more <')
+ if hasattr(os, 'system') and os.system('(pager) 2>/dev/null') == 0:
+ return lambda text, title='': pipe_pager(text, 'pager', title)
if hasattr(os, 'system') and os.system('(less) 2>/dev/null') == 0:
return lambda text, title='': pipe_pager(text, 'less', title)
diff --git a/Lib/_pyrepl/simple_interact.py b/Lib/_pyrepl/simple_interact.py
index 3c79cf61d04051..342a4b58bfd0f3 100644
--- a/Lib/_pyrepl/simple_interact.py
+++ b/Lib/_pyrepl/simple_interact.py
@@ -28,6 +28,7 @@
import _sitebuiltins
import linecache
import functools
+import os
import sys
import code
@@ -50,7 +51,9 @@ def check() -> str:
try:
_get_reader()
except _error as e:
- return str(e) or repr(e) or "unknown error"
+ if term := os.environ.get("TERM", ""):
+ term = f"; TERM={term}"
+ return str(str(e) or repr(e) or "unknown error") + term
return ""
@@ -159,10 +162,8 @@ def maybe_run_command(statement: str) -> bool:
input_n += 1
except KeyboardInterrupt:
r = _get_reader()
- if r.last_command and 'isearch' in r.last_command.__name__:
- r.isearch_direction = ''
- r.console.forgetinput()
- r.pop_input_trans()
+ if r.input_trans is r.isearch_trans:
+ r.do_cmd(("isearch-end", [""]))
r.pos = len(r.get_unicode())
r.dirty = True
r.refresh()
diff --git a/Lib/annotationlib.py b/Lib/annotationlib.py
index 9d1943b27e8e9c..0a67742a2b3081 100644
--- a/Lib/annotationlib.py
+++ b/Lib/annotationlib.py
@@ -45,7 +45,17 @@ class Format(enum.IntEnum):
class ForwardRef:
- """Wrapper that holds a forward reference."""
+ """Wrapper that holds a forward reference.
+
+ Constructor arguments:
+ * arg: a string representing the code to be evaluated.
+ * module: the module where the forward reference was created.
+ Must be a string, not a module object.
+ * owner: The owning object (module, class, or function).
+ * is_argument: Does nothing, retained for compatibility.
+ * is_class: True if the forward reference was created in class scope.
+
+ """
__slots__ = _SLOTS
@@ -57,8 +67,6 @@ def __init__(
owner=None,
is_argument=True,
is_class=False,
- _globals=None,
- _cell=None,
):
if not isinstance(arg, str):
raise TypeError(f"Forward reference must be a string -- got {arg!r}")
@@ -71,8 +79,8 @@ def __init__(
self.__forward_module__ = module
self.__code__ = None
self.__ast_node__ = None
- self.__globals__ = _globals
- self.__cell__ = _cell
+ self.__globals__ = None
+ self.__cell__ = None
self.__owner__ = owner
def __init_subclass__(cls, /, *args, **kwds):
@@ -115,6 +123,10 @@ def evaluate(self, *, globals=None, locals=None, type_params=None, owner=None):
elif callable(owner):
globals = getattr(owner, "__globals__", None)
+ # If we pass None to eval() below, the globals of this module are used.
+ if globals is None:
+ globals = {}
+
if locals is None:
locals = {}
if isinstance(owner, type):
@@ -134,14 +146,8 @@ def evaluate(self, *, globals=None, locals=None, type_params=None, owner=None):
# but should in turn be overridden by names in the class scope
# (which here are called `globalns`!)
if type_params is not None:
- if globals is None:
- globals = {}
- else:
- globals = dict(globals)
- if locals is None:
- locals = {}
- else:
- locals = dict(locals)
+ globals = dict(globals)
+ locals = dict(locals)
for param in type_params:
param_name = param.__name__
if not self.__forward_is_class__ or param_name not in globals:
@@ -575,7 +581,11 @@ def get_annotate_function(obj):
Returns the __annotate__ function or None.
"""
if isinstance(obj, type):
- return _BASE_GET_ANNOTATE(obj)
+ try:
+ return _BASE_GET_ANNOTATE(obj)
+ except AttributeError:
+ # AttributeError is raised for static types.
+ return None
return getattr(obj, "__annotate__", None)
diff --git a/Lib/argparse.py b/Lib/argparse.py
index a88a8c65c40a1d..690b2a9db9481b 100644
--- a/Lib/argparse.py
+++ b/Lib/argparse.py
@@ -261,13 +261,12 @@ def add_argument(self, action):
# find all invocations
get_invocation = self._format_action_invocation
- invocations = [get_invocation(action)]
+ invocation_lengths = [len(get_invocation(action)) + self._current_indent]
for subaction in self._iter_indented_subactions(action):
- invocations.append(get_invocation(subaction))
+ invocation_lengths.append(len(get_invocation(subaction)) + self._current_indent)
# update the maximum item length
- invocation_length = max(map(len, invocations))
- action_length = invocation_length + self._current_indent
+ action_length = max(invocation_lengths)
self._action_max_length = max(self._action_max_length,
action_length)
@@ -396,12 +395,12 @@ def _get_actions_usage_parts(self, actions, groups):
continue
try:
- start = actions.index(group._group_actions[0])
+ start = min(actions.index(item) for item in group._group_actions)
except ValueError:
continue
else:
end = start + len(group._group_actions)
- if actions[start:end] == group._group_actions:
+ if set(actions[start:end]) == set(group._group_actions):
group_actions.update(group._group_actions)
inserts[start, end] = group
@@ -1360,7 +1359,7 @@ def __init__(self,
self._defaults = {}
# determines whether an "option" looks like a negative number
- self._negative_number_matcher = _re.compile(r'^-\d[\d_]*(\.\d[\d_]*)?$')
+ self._negative_number_matcher = _re.compile(r'^-(?:\d+(?:_\d+)*(?:\.\d+(?:_\d+)*)?|\.\d+(?:_\d+)*)$')
# whether or not there are any optionals that look like negative
# numbers -- uses a list so it can be shared and edited
@@ -1533,9 +1532,8 @@ def _get_positional_kwargs(self, dest, **kwargs):
# mark positional arguments as required if at least one is
# always required
- if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]:
- kwargs['required'] = True
- if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs:
+ nargs = kwargs.get('nargs')
+ if nargs not in [OPTIONAL, ZERO_OR_MORE, REMAINDER, SUPPRESS, 0]:
kwargs['required'] = True
# return the keyword arguments with no option strings
@@ -1806,8 +1804,8 @@ def add_subparsers(self, **kwargs):
kwargs.setdefault('parser_class', type(self))
if 'title' in kwargs or 'description' in kwargs:
- title = _(kwargs.pop('title', 'subcommands'))
- description = _(kwargs.pop('description', None))
+ title = kwargs.pop('title', _('subcommands'))
+ description = kwargs.pop('description', None)
self._subparsers = self.add_argument_group(title, description)
else:
self._subparsers = self._positionals
@@ -1950,9 +1948,8 @@ def take_action(action, argument_strings, option_string=None):
argument_values = self._get_values(action, argument_strings)
# error if this argument is not allowed with other previously
- # seen arguments, assuming that actions that use the default
- # value don't really count as "present"
- if argument_values is not action.default:
+ # seen arguments
+ if action.option_strings or argument_strings:
seen_non_default_actions.add(action)
for conflict_action in action_conflicts.get(action, []):
if conflict_action in seen_non_default_actions:
@@ -2070,6 +2067,15 @@ def consume_positionals(start_index):
# and add the Positional and its args to the list
for action, arg_count in zip(positionals, arg_counts):
args = arg_strings[start_index: start_index + arg_count]
+ # Strip out the first '--' if it is not in REMAINDER arg.
+ if action.nargs == PARSER:
+ if arg_strings_pattern[start_index] == '-':
+ assert args[0] == '--'
+ args.remove('--')
+ elif action.nargs != REMAINDER:
+ if (arg_strings_pattern.find('-', start_index,
+ start_index + arg_count) >= 0):
+ args.remove('--')
start_index += arg_count
if args and action.deprecated and action.dest not in warned:
self._warning(_("argument '%(argument_name)s' is deprecated") %
@@ -2221,18 +2227,19 @@ def _match_argument(self, action, arg_strings_pattern):
def _match_arguments_partial(self, actions, arg_strings_pattern):
# progressively shorten the actions list by slicing off the
# final actions until we find a match
- result = []
for i in range(len(actions), 0, -1):
actions_slice = actions[:i]
pattern = ''.join([self._get_nargs_pattern(action)
for action in actions_slice])
match = _re.match(pattern, arg_strings_pattern)
if match is not None:
- result.extend([len(string) for string in match.groups()])
- break
-
- # return the list of arg string counts
- return result
+ result = [len(string) for string in match.groups()]
+ if (match.end() < len(arg_strings_pattern)
+ and arg_strings_pattern[match.end()] == 'O'):
+ while result and not result[-1]:
+ del result[-1]
+ return result
+ return []
def _parse_optional(self, arg_string):
# if it's an empty string, it was meant to be a positional
@@ -2470,13 +2477,6 @@ def parse_known_intermixed_args(self, args=None, namespace=None):
# Value conversion methods
# ========================
def _get_values(self, action, arg_strings):
- # for everything but PARSER, REMAINDER args, strip out first '--'
- if not action.option_strings and action.nargs not in [PARSER, REMAINDER]:
- try:
- arg_strings.remove('--')
- except ValueError:
- pass
-
# optional argument produces a default when not present
if not arg_strings and action.nargs == OPTIONAL:
if action.option_strings:
diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py
index ac7d40cf2cac2e..f5cb97edaf72cd 100644
--- a/Lib/dataclasses.py
+++ b/Lib/dataclasses.py
@@ -690,11 +690,8 @@ def _frozen_get_del_attr(cls, fields, func_builder):
def _is_classvar(a_type, typing):
- # This test uses a typing internal class, but it's the best way to
- # test if this is a ClassVar.
return (a_type is typing.ClassVar
- or (type(a_type) is typing._GenericAlias
- and a_type.__origin__ is typing.ClassVar))
+ or (typing.get_origin(a_type) is typing.ClassVar))
def _is_initvar(a_type, dataclasses):
@@ -1221,9 +1218,31 @@ def _get_slots(cls):
raise TypeError(f"Slots of '{cls.__name__}' cannot be determined")
+def _update_func_cell_for__class__(f, oldcls, newcls):
+ # Returns True if we update a cell, else False.
+ if f is None:
+ # f will be None in the case of a property where not all of
+ # fget, fset, and fdel are used. Nothing to do in that case.
+ return False
+ try:
+ idx = f.__code__.co_freevars.index("__class__")
+ except ValueError:
+ # This function doesn't reference __class__, so nothing to do.
+ return False
+ # Fix the cell to point to the new class, if it's already pointing
+ # at the old class. I'm not convinced that the "is oldcls" test
+ # is needed, but other than performance can't hurt.
+ closure = f.__closure__[idx]
+ if closure.cell_contents is oldcls:
+ closure.cell_contents = newcls
+ return True
+ return False
+
+
def _add_slots(cls, is_frozen, weakref_slot):
- # Need to create a new class, since we can't set __slots__
- # after a class has been created.
+ # Need to create a new class, since we can't set __slots__ after a
+ # class has been created, and the @dataclass decorator is called
+ # after the class is created.
# Make sure __slots__ isn't already set.
if '__slots__' in cls.__dict__:
@@ -1262,18 +1281,37 @@ def _add_slots(cls, is_frozen, weakref_slot):
# And finally create the class.
qualname = getattr(cls, '__qualname__', None)
- cls = type(cls)(cls.__name__, cls.__bases__, cls_dict)
+ newcls = type(cls)(cls.__name__, cls.__bases__, cls_dict)
if qualname is not None:
- cls.__qualname__ = qualname
+ newcls.__qualname__ = qualname
if is_frozen:
# Need this for pickling frozen classes with slots.
if '__getstate__' not in cls_dict:
- cls.__getstate__ = _dataclass_getstate
+ newcls.__getstate__ = _dataclass_getstate
if '__setstate__' not in cls_dict:
- cls.__setstate__ = _dataclass_setstate
-
- return cls
+ newcls.__setstate__ = _dataclass_setstate
+
+ # Fix up any closures which reference __class__. This is used to
+ # fix zero argument super so that it points to the correct class
+ # (the newly created one, which we're returning) and not the
+ # original class. We can break out of this loop as soon as we
+ # make an update, since all closures for a class will share a
+ # given cell.
+ for member in newcls.__dict__.values():
+ # If this is a wrapped function, unwrap it.
+ member = inspect.unwrap(member)
+
+ if isinstance(member, types.FunctionType):
+ if _update_func_cell_for__class__(member, cls, newcls):
+ break
+ elif isinstance(member, property):
+ if (_update_func_cell_for__class__(member.fget, cls, newcls)
+ or _update_func_cell_for__class__(member.fset, cls, newcls)
+ or _update_func_cell_for__class__(member.fdel, cls, newcls)):
+ break
+
+ return newcls
def dataclass(cls=None, /, *, init=True, repr=True, eq=True, order=False,
diff --git a/Lib/doctest.py b/Lib/doctest.py
index ea7d275c91db04..bb281fc483c41c 100644
--- a/Lib/doctest.py
+++ b/Lib/doctest.py
@@ -389,11 +389,11 @@ def __init__(self, out):
# still use input() to get user input
self.use_rawinput = 1
- def set_trace(self, frame=None):
+ def set_trace(self, frame=None, *, commands=None):
self.__debugger_used = True
if frame is None:
frame = sys._getframe().f_back
- pdb.Pdb.set_trace(self, frame)
+ pdb.Pdb.set_trace(self, frame, commands=commands)
def set_continue(self):
# Calling set_continue unconditionally would break unit test
diff --git a/Lib/idlelib/idle_test/test_outwin.py b/Lib/idlelib/idle_test/test_outwin.py
index d6e85ad674417c..81f4aad7e95e95 100644
--- a/Lib/idlelib/idle_test/test_outwin.py
+++ b/Lib/idlelib/idle_test/test_outwin.py
@@ -1,6 +1,7 @@
"Test outwin, coverage 76%."
from idlelib import outwin
+import sys
import unittest
from test.support import requires
from tkinter import Tk, Text
@@ -18,6 +19,10 @@ def setUpClass(cls):
root.withdraw()
w = cls.window = outwin.OutputWindow(None, None, None, root)
cls.text = w.text = Text(root)
+ if sys.platform == 'darwin': # Issue 112938
+ cls.text.update = cls.text.update_idletasks
+ # Without this, test write, writelines, and goto... fail.
+ # The reasons and why macOS-specific are unclear.
@classmethod
def tearDownClass(cls):
diff --git a/Lib/idlelib/outwin.py b/Lib/idlelib/outwin.py
index 5ed3f35a7af655..8baa657550de94 100644
--- a/Lib/idlelib/outwin.py
+++ b/Lib/idlelib/outwin.py
@@ -112,7 +112,7 @@ def write(self, s, tags=(), mark="insert"):
assert isinstance(s, str)
self.text.insert(mark, s, tags)
self.text.see(mark)
- self.text.update_idletasks()
+ self.text.update()
return len(s)
def writelines(self, lines):
diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py
index cb41c522883d74..703fa289dda1fb 100644
--- a/Lib/ipaddress.py
+++ b/Lib/ipaddress.py
@@ -2375,6 +2375,8 @@ class _IPv6Constants:
IPv6Network('2001:db8::/32'),
# IANA says N/A, let's consider it not globally reachable to be safe
IPv6Network('2002::/16'),
+ # RFC 9637: https://www.rfc-editor.org/rfc/rfc9637.html#section-6-2.2
+ IPv6Network('3fff::/20'),
IPv6Network('fc00::/7'),
IPv6Network('fe80::/10'),
]
diff --git a/Lib/pdb.py b/Lib/pdb.py
index 228de489a9cef1..443160eaaae887 100644
--- a/Lib/pdb.py
+++ b/Lib/pdb.py
@@ -309,7 +309,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
_last_pdb_instance = None
def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
- nosigint=False, readrc=True):
+ nosigint=False, readrc=True, mode=None):
bdb.Bdb.__init__(self, skip=skip)
cmd.Cmd.__init__(self, completekey, stdin, stdout)
sys.audit("pdb.Pdb")
@@ -321,6 +321,7 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
self.mainpyfile = ''
self._wait_for_mainpyfile = False
self.tb_lineno = {}
+ self.mode = mode
# Try to load readline if it exists
try:
import readline
@@ -361,10 +362,14 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
self._chained_exceptions = tuple()
self._chained_exception_index = 0
- def set_trace(self, frame=None):
+ def set_trace(self, frame=None, *, commands=None):
Pdb._last_pdb_instance = self
if frame is None:
frame = sys._getframe().f_back
+
+ if commands is not None:
+ self.rcLines.extend(commands)
+
super().set_trace(frame)
def sigint_handler(self, signum, frame):
@@ -1607,6 +1612,11 @@ def do_run(self, arg):
sys.argv. History, breakpoints, actions and debugger options
are preserved. "restart" is an alias for "run".
"""
+ if self.mode == 'inline':
+ self.error('run/restart command is disabled when pdb is running in inline mode.\n'
+ 'Use the command line interface to enable restarting your program\n'
+ 'e.g. "python -m pdb myscript.py"')
+ return
if arg:
import shlex
argv0 = sys.argv[0:1]
@@ -2350,21 +2360,22 @@ def runcall(*args, **kwds):
"""
return Pdb().runcall(*args, **kwds)
-def set_trace(*, header=None):
+def set_trace(*, header=None, commands=None):
"""Enter the debugger at the calling stack frame.
This is useful to hard-code a breakpoint at a given point in a
program, even if the code is not otherwise being debugged (e.g. when
an assertion fails). If given, *header* is printed to the console
- just before debugging begins.
+ just before debugging begins. *commands* is an optional list of
+ pdb commands to run when the debugger starts.
"""
if Pdb._last_pdb_instance is not None:
pdb = Pdb._last_pdb_instance
else:
- pdb = Pdb()
+ pdb = Pdb(mode='inline')
if header is not None:
pdb.message(header)
- pdb.set_trace(sys._getframe().f_back)
+ pdb.set_trace(sys._getframe().f_back, commands=commands)
# Post-Mortem interface
@@ -2476,7 +2487,7 @@ def main():
# modified by the script being debugged. It's a bad idea when it was
# changed by the user from the command line. There is a "restart" command
# which allows explicit specification of command line arguments.
- pdb = Pdb()
+ pdb = Pdb(mode='cli')
pdb.rcLines.extend(opts.commands)
while True:
try:
diff --git a/Lib/pty.py b/Lib/pty.py
index eb3d5f1ff657bb..4b25ac32c8da14 100644
--- a/Lib/pty.py
+++ b/Lib/pty.py
@@ -39,8 +39,8 @@ def openpty():
except ImportError:
return master_fd, slave_fd
try:
- ioctl(result, I_PUSH, "ptem")
- ioctl(result, I_PUSH, "ldterm")
+ ioctl(slave_fd, I_PUSH, "ptem")
+ ioctl(slave_fd, I_PUSH, "ldterm")
except OSError:
pass
return master_fd, slave_fd
diff --git a/Lib/shutil.py b/Lib/shutil.py
index 6037092a5e09f2..dab3ca5ee91245 100644
--- a/Lib/shutil.py
+++ b/Lib/shutil.py
@@ -48,7 +48,7 @@
# This should never be removed, see rationale in:
# https://bugs.python.org/issue43743#msg393429
_USE_CP_SENDFILE = (hasattr(os, "sendfile")
- and sys.platform.startswith(("linux", "android")))
+ and sys.platform.startswith(("linux", "android", "sunos")))
_HAS_FCOPYFILE = posix and hasattr(posix, "_fcopyfile") # macOS
# CMD defaults in Windows 10
@@ -110,7 +110,7 @@ def _fastcopy_fcopyfile(fsrc, fdst, flags):
def _fastcopy_sendfile(fsrc, fdst):
"""Copy data from one regular mmap-like fd to another by using
high-performance sendfile(2) syscall.
- This should work on Linux >= 2.6.33 only.
+ This should work on Linux >= 2.6.33, Android and Solaris.
"""
# Note: copyfileobj() is left alone in order to not introduce any
# unexpected breakage. Possible risks by using zero-copy calls
@@ -265,7 +265,7 @@ def copyfile(src, dst, *, follow_symlinks=True):
return dst
except _GiveupOnFastCopy:
pass
- # Linux
+ # Linux / Android / Solaris
elif _USE_CP_SENDFILE:
try:
_fastcopy_sendfile(fsrc, fdst)
diff --git a/Lib/test/pickletester.py b/Lib/test/pickletester.py
index 334d4dfebdf893..1722cc8612ca6b 100644
--- a/Lib/test/pickletester.py
+++ b/Lib/test/pickletester.py
@@ -4345,7 +4345,9 @@ class MyIntWithNew2(MyIntWithNew):
class SlotList(MyList):
__slots__ = ["foo"]
-class SimpleNewObj(int):
+# Ruff "redefined while unused" false positive here due to `global` variables
+# being assigned (and then restored) from within test methods earlier in the file
+class SimpleNewObj(int): # noqa: F811
def __init__(self, *args, **kwargs):
# raise an error, to make sure this isn't called
raise TypeError("SimpleNewObj.__init__() didn't expect to get called")
diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py
index d928e002ebda10..05a28bda2d38ba 100644
--- a/Lib/test/pythoninfo.py
+++ b/Lib/test/pythoninfo.py
@@ -553,7 +553,6 @@ def collect_sysconfig(info_add):
for name in (
'WITH_DOC_STRINGS',
'WITH_DTRACE',
- 'WITH_FREELISTS',
'WITH_MIMALLOC',
'WITH_PYMALLOC',
'WITH_VALGRIND',
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index 722cd0c96ad3c6..f77095875c2023 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -61,6 +61,7 @@
"without_optimizer",
"force_not_colorized",
"BrokenIter",
+ "in_systemd_nspawn_sync_suppressed",
]
@@ -2887,3 +2888,35 @@ def __iter__(self):
if self.iter_raises:
1/0
return self
+
+
+def in_systemd_nspawn_sync_suppressed() -> bool:
+ """
+ Test whether the test suite is runing in systemd-nspawn
+ with ``--suppress-sync=true``.
+
+ This can be used to skip tests that rely on ``fsync()`` calls
+ and similar not being intercepted.
+ """
+
+ if not hasattr(os, "O_SYNC"):
+ return False
+
+ try:
+ with open("/run/systemd/container", "rb") as fp:
+ if fp.read().rstrip() != b"systemd-nspawn":
+ return False
+ except FileNotFoundError:
+ return False
+
+ # If systemd-nspawn is used, O_SYNC flag will immediately
+ # trigger EINVAL. Otherwise, ENOENT will be given instead.
+ import errno
+ try:
+ with os.open(__file__, os.O_RDONLY | os.O_SYNC):
+ pass
+ except OSError as err:
+ if err.errno == errno.EINVAL:
+ return True
+
+ return False
diff --git a/Lib/test/support/script_helper.py b/Lib/test/support/script_helper.py
index d0be3179b0efa3..46ce950433ddf6 100644
--- a/Lib/test/support/script_helper.py
+++ b/Lib/test/support/script_helper.py
@@ -234,9 +234,13 @@ def make_script(script_dir, script_basename, source, omit_suffix=False):
if not omit_suffix:
script_filename += os.extsep + 'py'
script_name = os.path.join(script_dir, script_filename)
- # The script should be encoded to UTF-8, the default string encoding
- with open(script_name, 'w', encoding='utf-8') as script_file:
- script_file.write(source)
+ if isinstance(source, str):
+ # The script should be encoded to UTF-8, the default string encoding
+ with open(script_name, 'w', encoding='utf-8') as script_file:
+ script_file.write(source)
+ else:
+ with open(script_name, 'wb') as script_file:
+ script_file.write(source)
importlib.invalidate_caches()
return script_name
diff --git a/Lib/test/test_annotationlib.py b/Lib/test/test_annotationlib.py
index ce4f92624d9036..dd8ceb55a411fb 100644
--- a/Lib/test/test_annotationlib.py
+++ b/Lib/test/test_annotationlib.py
@@ -1,6 +1,7 @@
"""Tests for the annotations module."""
import annotationlib
+import collections
import functools
import itertools
import pickle
@@ -278,11 +279,24 @@ class Gen[T]:
)
def test_fwdref_with_module(self):
- self.assertIs(ForwardRef("Format", module=annotationlib).evaluate(), Format)
+ self.assertIs(ForwardRef("Format", module="annotationlib").evaluate(), Format)
+ self.assertIs(ForwardRef("Counter", module="collections").evaluate(), collections.Counter)
with self.assertRaises(NameError):
# If globals are passed explicitly, we don't look at the module dict
- ForwardRef("Format", module=annotationlib).evaluate(globals={})
+ ForwardRef("Format", module="annotationlib").evaluate(globals={})
+
+ def test_fwdref_to_builtin(self):
+ self.assertIs(ForwardRef("int").evaluate(), int)
+ self.assertIs(ForwardRef("int", module="collections").evaluate(), int)
+ self.assertIs(ForwardRef("int", owner=str).evaluate(), int)
+
+ # builtins are still searched with explicit globals
+ self.assertIs(ForwardRef("int").evaluate(globals={}), int)
+
+ # explicit values in globals have precedence
+ obj = object()
+ self.assertIs(ForwardRef("int").evaluate(globals={"int": obj}), obj)
def test_fwdref_value_is_cached(self):
fr = ForwardRef("hello")
@@ -928,6 +942,27 @@ class D(metaclass=Meta):
self.assertIs(annotate_func, None)
+class TestGetAnnotateFunction(unittest.TestCase):
+ def test_static_class(self):
+ self.assertIsNone(get_annotate_function(object))
+ self.assertIsNone(get_annotate_function(int))
+
+ def test_unannotated_class(self):
+ class C:
+ pass
+
+ self.assertIsNone(get_annotate_function(C))
+
+ D = type("D", (), {})
+ self.assertIsNone(get_annotate_function(D))
+
+ def test_annotated_class(self):
+ class C:
+ a: int
+
+ self.assertEqual(get_annotate_function(C)(Format.VALUE), {"a": int})
+
+
class TestAnnotationLib(unittest.TestCase):
def test__all__(self):
support.check__all__(self, annotationlib)
diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py
index 584462b741ee99..ef05a6fefcffcc 100644
--- a/Lib/test/test_argparse.py
+++ b/Lib/test/test_argparse.py
@@ -280,16 +280,18 @@ def test_failures(self, tester):
parser = self._get_parser(tester)
for args_str in tester.failures:
args = args_str.split()
- with tester.assertRaises(ArgumentParserError, msg=args):
- parser.parse_args(args)
+ with tester.subTest(args=args):
+ with tester.assertRaises(ArgumentParserError, msg=args):
+ parser.parse_args(args)
def test_successes(self, tester):
parser = self._get_parser(tester)
for args, expected_ns in tester.successes:
if isinstance(args, str):
args = args.split()
- result_ns = self._parse_args(parser, args)
- tester.assertEqual(expected_ns, result_ns)
+ with tester.subTest(args=args):
+ result_ns = self._parse_args(parser, args)
+ tester.assertEqual(expected_ns, result_ns)
# add tests for each combination of an optionals adding method
# and an arg parsing method
@@ -1089,57 +1091,87 @@ class TestPositionalsNargs2None(ParserTestCase):
class TestPositionalsNargsNoneZeroOrMore(ParserTestCase):
"""Test a Positional with no nargs followed by one with unlimited"""
- argument_signatures = [Sig('foo'), Sig('bar', nargs='*')]
- failures = ['', '--foo']
+ argument_signatures = [Sig('-x'), Sig('foo'), Sig('bar', nargs='*')]
+ failures = ['', '--foo', 'a b -x X c']
successes = [
- ('a', NS(foo='a', bar=[])),
- ('a b', NS(foo='a', bar=['b'])),
- ('a b c', NS(foo='a', bar=['b', 'c'])),
+ ('a', NS(x=None, foo='a', bar=[])),
+ ('a b', NS(x=None, foo='a', bar=['b'])),
+ ('a b c', NS(x=None, foo='a', bar=['b', 'c'])),
+ ('-x X a', NS(x='X', foo='a', bar=[])),
+ ('a -x X', NS(x='X', foo='a', bar=[])),
+ ('-x X a b', NS(x='X', foo='a', bar=['b'])),
+ ('a -x X b', NS(x='X', foo='a', bar=['b'])),
+ ('a b -x X', NS(x='X', foo='a', bar=['b'])),
+ ('-x X a b c', NS(x='X', foo='a', bar=['b', 'c'])),
+ ('a -x X b c', NS(x='X', foo='a', bar=['b', 'c'])),
+ ('a b c -x X', NS(x='X', foo='a', bar=['b', 'c'])),
]
class TestPositionalsNargsNoneOneOrMore(ParserTestCase):
"""Test a Positional with no nargs followed by one with one or more"""
- argument_signatures = [Sig('foo'), Sig('bar', nargs='+')]
- failures = ['', '--foo', 'a']
+ argument_signatures = [Sig('-x'), Sig('foo'), Sig('bar', nargs='+')]
+ failures = ['', '--foo', 'a', 'a b -x X c']
successes = [
- ('a b', NS(foo='a', bar=['b'])),
- ('a b c', NS(foo='a', bar=['b', 'c'])),
+ ('a b', NS(x=None, foo='a', bar=['b'])),
+ ('a b c', NS(x=None, foo='a', bar=['b', 'c'])),
+ ('-x X a b', NS(x='X', foo='a', bar=['b'])),
+ ('a -x X b', NS(x='X', foo='a', bar=['b'])),
+ ('a b -x X', NS(x='X', foo='a', bar=['b'])),
+ ('-x X a b c', NS(x='X', foo='a', bar=['b', 'c'])),
+ ('a -x X b c', NS(x='X', foo='a', bar=['b', 'c'])),
+ ('a b c -x X', NS(x='X', foo='a', bar=['b', 'c'])),
]
class TestPositionalsNargsNoneOptional(ParserTestCase):
"""Test a Positional with no nargs followed by one with an Optional"""
- argument_signatures = [Sig('foo'), Sig('bar', nargs='?')]
+ argument_signatures = [Sig('-x'), Sig('foo'), Sig('bar', nargs='?')]
failures = ['', '--foo', 'a b c']
successes = [
- ('a', NS(foo='a', bar=None)),
- ('a b', NS(foo='a', bar='b')),
+ ('a', NS(x=None, foo='a', bar=None)),
+ ('a b', NS(x=None, foo='a', bar='b')),
+ ('-x X a', NS(x='X', foo='a', bar=None)),
+ ('a -x X', NS(x='X', foo='a', bar=None)),
+ ('-x X a b', NS(x='X', foo='a', bar='b')),
+ ('a -x X b', NS(x='X', foo='a', bar='b')),
+ ('a b -x X', NS(x='X', foo='a', bar='b')),
]
class TestPositionalsNargsZeroOrMoreNone(ParserTestCase):
"""Test a Positional with unlimited nargs followed by one with none"""
- argument_signatures = [Sig('foo', nargs='*'), Sig('bar')]
- failures = ['', '--foo']
+ argument_signatures = [Sig('-x'), Sig('foo', nargs='*'), Sig('bar')]
+ failures = ['', '--foo', 'a -x X b', 'a -x X b c', 'a b -x X c']
successes = [
- ('a', NS(foo=[], bar='a')),
- ('a b', NS(foo=['a'], bar='b')),
- ('a b c', NS(foo=['a', 'b'], bar='c')),
+ ('a', NS(x=None, foo=[], bar='a')),
+ ('a b', NS(x=None, foo=['a'], bar='b')),
+ ('a b c', NS(x=None, foo=['a', 'b'], bar='c')),
+ ('-x X a', NS(x='X', foo=[], bar='a')),
+ ('a -x X', NS(x='X', foo=[], bar='a')),
+ ('-x X a b', NS(x='X', foo=['a'], bar='b')),
+ ('a b -x X', NS(x='X', foo=['a'], bar='b')),
+ ('-x X a b c', NS(x='X', foo=['a', 'b'], bar='c')),
+ ('a b c -x X', NS(x='X', foo=['a', 'b'], bar='c')),
]
class TestPositionalsNargsOneOrMoreNone(ParserTestCase):
"""Test a Positional with one or more nargs followed by one with none"""
- argument_signatures = [Sig('foo', nargs='+'), Sig('bar')]
- failures = ['', '--foo', 'a']
+ argument_signatures = [Sig('-x'), Sig('foo', nargs='+'), Sig('bar')]
+ failures = ['', '--foo', 'a', 'a -x X b c', 'a b -x X c']
successes = [
- ('a b', NS(foo=['a'], bar='b')),
- ('a b c', NS(foo=['a', 'b'], bar='c')),
+ ('a b', NS(x=None, foo=['a'], bar='b')),
+ ('a b c', NS(x=None, foo=['a', 'b'], bar='c')),
+ ('-x X a b', NS(x='X', foo=['a'], bar='b')),
+ ('a -x X b', NS(x='X', foo=['a'], bar='b')),
+ ('a b -x X', NS(x='X', foo=['a'], bar='b')),
+ ('-x X a b c', NS(x='X', foo=['a', 'b'], bar='c')),
+ ('a b c -x X', NS(x='X', foo=['a', 'b'], bar='c')),
]
@@ -1224,14 +1256,21 @@ class TestPositionalsNargsNoneZeroOrMore1(ParserTestCase):
"""Test three Positionals: no nargs, unlimited nargs and 1 nargs"""
argument_signatures = [
+ Sig('-x'),
Sig('foo'),
Sig('bar', nargs='*'),
Sig('baz', nargs=1),
]
- failures = ['', '--foo', 'a']
+ failures = ['', '--foo', 'a', 'a b -x X c']
successes = [
- ('a b', NS(foo='a', bar=[], baz=['b'])),
- ('a b c', NS(foo='a', bar=['b'], baz=['c'])),
+ ('a b', NS(x=None, foo='a', bar=[], baz=['b'])),
+ ('a b c', NS(x=None, foo='a', bar=['b'], baz=['c'])),
+ ('-x X a b', NS(x='X', foo='a', bar=[], baz=['b'])),
+ ('a -x X b', NS(x='X', foo='a', bar=[], baz=['b'])),
+ ('a b -x X', NS(x='X', foo='a', bar=[], baz=['b'])),
+ ('-x X a b c', NS(x='X', foo='a', bar=['b'], baz=['c'])),
+ ('a -x X b c', NS(x='X', foo='a', bar=['b'], baz=['c'])),
+ ('a b c -x X', NS(x='X', foo='a', bar=['b'], baz=['c'])),
]
@@ -1239,14 +1278,22 @@ class TestPositionalsNargsNoneOneOrMore1(ParserTestCase):
"""Test three Positionals: no nargs, one or more nargs and 1 nargs"""
argument_signatures = [
+ Sig('-x'),
Sig('foo'),
Sig('bar', nargs='+'),
Sig('baz', nargs=1),
]
- failures = ['', '--foo', 'a', 'b']
+ failures = ['', '--foo', 'a', 'b', 'a b -x X c d', 'a b c -x X d']
successes = [
- ('a b c', NS(foo='a', bar=['b'], baz=['c'])),
- ('a b c d', NS(foo='a', bar=['b', 'c'], baz=['d'])),
+ ('a b c', NS(x=None, foo='a', bar=['b'], baz=['c'])),
+ ('a b c d', NS(x=None, foo='a', bar=['b', 'c'], baz=['d'])),
+ ('-x X a b c', NS(x='X', foo='a', bar=['b'], baz=['c'])),
+ ('a -x X b c', NS(x='X', foo='a', bar=['b'], baz=['c'])),
+ ('a b -x X c', NS(x='X', foo='a', bar=['b'], baz=['c'])),
+ ('a b c -x X', NS(x='X', foo='a', bar=['b'], baz=['c'])),
+ ('-x X a b c d', NS(x='X', foo='a', bar=['b', 'c'], baz=['d'])),
+ ('a -x X b c d', NS(x='X', foo='a', bar=['b', 'c'], baz=['d'])),
+ ('a b c d -x X', NS(x='X', foo='a', bar=['b', 'c'], baz=['d'])),
]
@@ -1254,14 +1301,21 @@ class TestPositionalsNargsNoneOptional1(ParserTestCase):
"""Test three Positionals: no nargs, optional narg and 1 nargs"""
argument_signatures = [
+ Sig('-x'),
Sig('foo'),
Sig('bar', nargs='?', default=0.625),
Sig('baz', nargs=1),
]
- failures = ['', '--foo', 'a']
+ failures = ['', '--foo', 'a', 'a b -x X c']
successes = [
- ('a b', NS(foo='a', bar=0.625, baz=['b'])),
- ('a b c', NS(foo='a', bar='b', baz=['c'])),
+ ('a b', NS(x=None, foo='a', bar=0.625, baz=['b'])),
+ ('a b c', NS(x=None, foo='a', bar='b', baz=['c'])),
+ ('-x X a b', NS(x='X', foo='a', bar=0.625, baz=['b'])),
+ ('a -x X b', NS(x='X', foo='a', bar=0.625, baz=['b'])),
+ ('a b -x X', NS(x='X', foo='a', bar=0.625, baz=['b'])),
+ ('-x X a b c', NS(x='X', foo='a', bar='b', baz=['c'])),
+ ('a -x X b c', NS(x='X', foo='a', bar='b', baz=['c'])),
+ ('a b c -x X', NS(x='X', foo='a', bar='b', baz=['c'])),
]
@@ -1477,6 +1531,9 @@ class TestNargsRemainder(ParserTestCase):
successes = [
('X', NS(x='X', y=[], z=None)),
('-z Z X', NS(x='X', y=[], z='Z')),
+ ('-z Z X A B', NS(x='X', y=['A', 'B'], z='Z')),
+ ('X -z Z A B', NS(x='X', y=['-z', 'Z', 'A', 'B'], z=None)),
+ ('X A -z Z B', NS(x='X', y=['A', '-z', 'Z', 'B'], z=None)),
('X A B -z Z', NS(x='X', y=['A', 'B', '-z', 'Z'], z=None)),
('X Y --foo', NS(x='X', y=['Y', '--foo'], z=None)),
]
@@ -2111,6 +2168,8 @@ class TestNegativeNumber(ParserTestCase):
('--int -1_000_000 --float -1_000_000.0', NS(int=-1000000, float=-1000000.0)),
('--float -1_000.0', NS(int=None, float=-1000.0)),
('--float -1_000_000.0_0', NS(int=None, float=-1000000.0)),
+ ('--float -.5', NS(int=None, float=-0.5)),
+ ('--float -.5_000', NS(int=None, float=-0.5)),
]
class TestInvalidAction(TestCase):
@@ -2843,6 +2902,29 @@ def test_help(self):
'''
self.assertEqual(parser.format_help(), textwrap.dedent(expected))
+ def test_optional_order(self):
+ parser = ErrorRaisingArgumentParser(prog='PROG')
+ group = parser.add_mutually_exclusive_group(required=True)
+ group.add_argument('--foo')
+ group.add_argument('bar', nargs='?')
+ expected = '''\
+ usage: PROG [-h] (--foo FOO | bar)
+
+ positional arguments:
+ bar
+
+ options:
+ -h, --help show this help message and exit
+ --foo FOO
+ '''
+ self.assertEqual(parser.format_help(), textwrap.dedent(expected))
+
+ parser = ErrorRaisingArgumentParser(prog='PROG')
+ group = parser.add_mutually_exclusive_group(required=True)
+ group.add_argument('bar', nargs='?')
+ group.add_argument('--foo')
+ self.assertEqual(parser.format_help(), textwrap.dedent(expected))
+
def test_help_subparser_all_mutually_exclusive_group_members_suppressed(self):
self.maxDiff = None
parser = ErrorRaisingArgumentParser(prog='PROG')
@@ -2877,26 +2959,30 @@ def test_failures_when_not_required(self):
parse_args = self.get_parser(required=False).parse_args
error = ArgumentParserError
for args_string in self.failures:
- self.assertRaises(error, parse_args, args_string.split())
+ with self.subTest(args=args_string):
+ self.assertRaises(error, parse_args, args_string.split())
def test_failures_when_required(self):
parse_args = self.get_parser(required=True).parse_args
error = ArgumentParserError
for args_string in self.failures + ['']:
- self.assertRaises(error, parse_args, args_string.split())
+ with self.subTest(args=args_string):
+ self.assertRaises(error, parse_args, args_string.split())
def test_successes_when_not_required(self):
parse_args = self.get_parser(required=False).parse_args
successes = self.successes + self.successes_when_not_required
for args_string, expected_ns in successes:
- actual_ns = parse_args(args_string.split())
- self.assertEqual(actual_ns, expected_ns)
+ with self.subTest(args=args_string):
+ actual_ns = parse_args(args_string.split())
+ self.assertEqual(actual_ns, expected_ns)
def test_successes_when_required(self):
parse_args = self.get_parser(required=True).parse_args
for args_string, expected_ns in self.successes:
- actual_ns = parse_args(args_string.split())
- self.assertEqual(actual_ns, expected_ns)
+ with self.subTest(args=args_string):
+ actual_ns = parse_args(args_string.split())
+ self.assertEqual(actual_ns, expected_ns)
def test_usage_when_not_required(self):
format_usage = self.get_parser(required=False).format_usage
@@ -3073,7 +3159,7 @@ def get_parser(self, required):
group = parser.add_mutually_exclusive_group(required=required)
group.add_argument('--foo', action='store_true', help='FOO')
group.add_argument('--spam', help='SPAM')
- group.add_argument('badger', nargs='*', default='X', help='BADGER')
+ group.add_argument('badger', nargs='*', help='BADGER')
return parser
failures = [
@@ -3084,13 +3170,13 @@ def get_parser(self, required):
'--foo X Y',
]
successes = [
- ('--foo', NS(foo=True, spam=None, badger='X')),
- ('--spam S', NS(foo=False, spam='S', badger='X')),
+ ('--foo', NS(foo=True, spam=None, badger=[])),
+ ('--spam S', NS(foo=False, spam='S', badger=[])),
('X', NS(foo=False, spam=None, badger=['X'])),
('X Y Z', NS(foo=False, spam=None, badger=['X', 'Y', 'Z'])),
]
successes_when_not_required = [
- ('', NS(foo=False, spam=None, badger='X')),
+ ('', NS(foo=False, spam=None, badger=[])),
]
usage_when_not_required = '''\
@@ -3283,6 +3369,111 @@ def get_parser(self, required):
test_successes_when_not_required = None
test_successes_when_required = None
+
+class TestMutuallyExclusiveOptionalOptional(MEMixin, TestCase):
+ def get_parser(self, required=None):
+ parser = ErrorRaisingArgumentParser(prog='PROG')
+ group = parser.add_mutually_exclusive_group(required=required)
+ group.add_argument('--foo')
+ group.add_argument('--bar', nargs='?')
+ return parser
+
+ failures = [
+ '--foo X --bar Y',
+ '--foo X --bar',
+ ]
+ successes = [
+ ('--foo X', NS(foo='X', bar=None)),
+ ('--bar X', NS(foo=None, bar='X')),
+ ('--bar', NS(foo=None, bar=None)),
+ ]
+ successes_when_not_required = [
+ ('', NS(foo=None, bar=None)),
+ ]
+ usage_when_required = '''\
+ usage: PROG [-h] (--foo FOO | --bar [BAR])
+ '''
+ usage_when_not_required = '''\
+ usage: PROG [-h] [--foo FOO | --bar [BAR]]
+ '''
+ help = '''\
+
+ options:
+ -h, --help show this help message and exit
+ --foo FOO
+ --bar [BAR]
+ '''
+
+
+class TestMutuallyExclusiveOptionalWithDefault(MEMixin, TestCase):
+ def get_parser(self, required=None):
+ parser = ErrorRaisingArgumentParser(prog='PROG')
+ group = parser.add_mutually_exclusive_group(required=required)
+ group.add_argument('--foo')
+ group.add_argument('--bar', type=bool, default=True)
+ return parser
+
+ failures = [
+ '--foo X --bar Y',
+ '--foo X --bar=',
+ ]
+ successes = [
+ ('--foo X', NS(foo='X', bar=True)),
+ ('--bar X', NS(foo=None, bar=True)),
+ ('--bar=', NS(foo=None, bar=False)),
+ ]
+ successes_when_not_required = [
+ ('', NS(foo=None, bar=True)),
+ ]
+ usage_when_required = '''\
+ usage: PROG [-h] (--foo FOO | --bar BAR)
+ '''
+ usage_when_not_required = '''\
+ usage: PROG [-h] [--foo FOO | --bar BAR]
+ '''
+ help = '''\
+
+ options:
+ -h, --help show this help message and exit
+ --foo FOO
+ --bar BAR
+ '''
+
+
+class TestMutuallyExclusivePositionalWithDefault(MEMixin, TestCase):
+ def get_parser(self, required=None):
+ parser = ErrorRaisingArgumentParser(prog='PROG')
+ group = parser.add_mutually_exclusive_group(required=required)
+ group.add_argument('--foo')
+ group.add_argument('bar', nargs='?', type=bool, default=True)
+ return parser
+
+ failures = [
+ '--foo X Y',
+ ]
+ successes = [
+ ('--foo X', NS(foo='X', bar=True)),
+ ('X', NS(foo=None, bar=True)),
+ ]
+ successes_when_not_required = [
+ ('', NS(foo=None, bar=True)),
+ ]
+ usage_when_required = '''\
+ usage: PROG [-h] (--foo FOO | bar)
+ '''
+ usage_when_not_required = '''\
+ usage: PROG [-h] [--foo FOO | bar]
+ '''
+ help = '''\
+
+ positional arguments:
+ bar
+
+ options:
+ -h, --help show this help message and exit
+ --foo FOO
+ '''
+
# =================================================
# Mutually exclusive group in parent parser tests
# =================================================
@@ -4958,6 +5149,46 @@ def custom_type(string):
version = ''
+class TestHelpUsageLongSubparserCommand(TestCase):
+ """Test that subparser commands are formatted correctly in help"""
+ maxDiff = None
+
+ def test_parent_help(self):
+ def custom_formatter(prog):
+ return argparse.RawTextHelpFormatter(prog, max_help_position=50)
+
+ parent_parser = argparse.ArgumentParser(
+ prog='PROG',
+ formatter_class=custom_formatter
+ )
+
+ cmd_subparsers = parent_parser.add_subparsers(title="commands",
+ metavar='CMD',
+ help='command to use')
+ cmd_subparsers.add_parser("add",
+ help="add something")
+
+ cmd_subparsers.add_parser("remove",
+ help="remove something")
+
+ cmd_subparsers.add_parser("a-very-long-command",
+ help="command that does something")
+
+ parser_help = parent_parser.format_help()
+ self.assertEqual(parser_help, textwrap.dedent('''\
+ usage: PROG [-h] CMD ...
+
+ options:
+ -h, --help show this help message and exit
+
+ commands:
+ CMD command to use
+ add add something
+ remove remove something
+ a-very-long-command command that does something
+ '''))
+
+
# =====================================
# Optional/Positional constructor tests
# =====================================
@@ -5720,8 +5951,34 @@ def test_zero_or_more_optional(self):
args = parser.parse_args([])
self.assertEqual(NS(x=[]), args)
- def test_double_dash(self):
- parser = argparse.ArgumentParser()
+
+class TestDoubleDash(TestCase):
+ def test_single_argument_option(self):
+ parser = argparse.ArgumentParser(exit_on_error=False)
+ parser.add_argument('-f', '--foo')
+ parser.add_argument('bar', nargs='*')
+
+ args = parser.parse_args(['--foo=--'])
+ self.assertEqual(NS(foo='--', bar=[]), args)
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'argument -f/--foo: expected one argument',
+ parser.parse_args, ['--foo', '--'])
+ args = parser.parse_args(['-f--'])
+ self.assertEqual(NS(foo='--', bar=[]), args)
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'argument -f/--foo: expected one argument',
+ parser.parse_args, ['-f', '--'])
+ args = parser.parse_args(['--foo', 'a', '--', 'b', 'c'])
+ self.assertEqual(NS(foo='a', bar=['b', 'c']), args)
+ args = parser.parse_args(['a', 'b', '--foo', 'c'])
+ self.assertEqual(NS(foo='c', bar=['a', 'b']), args)
+ args = parser.parse_args(['a', '--', 'b', '--foo', 'c'])
+ self.assertEqual(NS(foo=None, bar=['a', 'b', '--foo', 'c']), args)
+ args = parser.parse_args(['a', '--', 'b', '--', 'c', '--foo', 'd'])
+ self.assertEqual(NS(foo=None, bar=['a', 'b', '--', 'c', '--foo', 'd']), args)
+
+ def test_multiple_argument_option(self):
+ parser = argparse.ArgumentParser(exit_on_error=False)
parser.add_argument('-f', '--foo', nargs='*')
parser.add_argument('bar', nargs='*')
@@ -5735,6 +5992,91 @@ def test_double_dash(self):
self.assertEqual(NS(foo=[], bar=[]), args)
args = parser.parse_args(['--foo', 'a', 'b', '--', 'c', 'd'])
self.assertEqual(NS(foo=['a', 'b'], bar=['c', 'd']), args)
+ args = parser.parse_args(['a', 'b', '--foo', 'c', 'd'])
+ self.assertEqual(NS(foo=['c', 'd'], bar=['a', 'b']), args)
+ args = parser.parse_args(['a', '--', 'b', '--foo', 'c', 'd'])
+ self.assertEqual(NS(foo=None, bar=['a', 'b', '--foo', 'c', 'd']), args)
+ args, argv = parser.parse_known_args(['a', 'b', '--foo', 'c', '--', 'd'])
+ self.assertEqual(NS(foo=['c'], bar=['a', 'b']), args)
+ self.assertEqual(argv, ['--', 'd'])
+
+ def test_multiple_double_dashes(self):
+ parser = argparse.ArgumentParser(exit_on_error=False)
+ parser.add_argument('foo')
+ parser.add_argument('bar', nargs='*')
+
+ args = parser.parse_args(['--', 'a', 'b', 'c'])
+ self.assertEqual(NS(foo='a', bar=['b', 'c']), args)
+ args = parser.parse_args(['a', '--', 'b', 'c'])
+ self.assertEqual(NS(foo='a', bar=['b', 'c']), args)
+ args = parser.parse_args(['a', 'b', '--', 'c'])
+ self.assertEqual(NS(foo='a', bar=['b', 'c']), args)
+ args = parser.parse_args(['a', '--', 'b', '--', 'c'])
+ self.assertEqual(NS(foo='a', bar=['b', '--', 'c']), args)
+ args = parser.parse_args(['--', '--', 'a', '--', 'b', 'c'])
+ self.assertEqual(NS(foo='--', bar=['a', '--', 'b', 'c']), args)
+
+ def test_remainder(self):
+ parser = argparse.ArgumentParser(exit_on_error=False)
+ parser.add_argument('foo')
+ parser.add_argument('bar', nargs='...')
+
+ args = parser.parse_args(['--', 'a', 'b', 'c'])
+ self.assertEqual(NS(foo='a', bar=['b', 'c']), args)
+ args = parser.parse_args(['a', '--', 'b', 'c'])
+ self.assertEqual(NS(foo='a', bar=['b', 'c']), args)
+ args = parser.parse_args(['a', 'b', '--', 'c'])
+ self.assertEqual(NS(foo='a', bar=['b', '--', 'c']), args)
+ args = parser.parse_args(['a', '--', 'b', '--', 'c'])
+ self.assertEqual(NS(foo='a', bar=['b', '--', 'c']), args)
+
+ parser = argparse.ArgumentParser(exit_on_error=False)
+ parser.add_argument('--foo')
+ parser.add_argument('bar', nargs='...')
+ args = parser.parse_args(['--foo', 'a', '--', 'b', '--', 'c'])
+ self.assertEqual(NS(foo='a', bar=['--', 'b', '--', 'c']), args)
+
+ def test_subparser(self):
+ parser = argparse.ArgumentParser(exit_on_error=False)
+ parser.add_argument('foo')
+ subparsers = parser.add_subparsers()
+ parser1 = subparsers.add_parser('run')
+ parser1.add_argument('-f')
+ parser1.add_argument('bar', nargs='*')
+
+ args = parser.parse_args(['x', 'run', 'a', 'b', '-f', 'c'])
+ self.assertEqual(NS(foo='x', f='c', bar=['a', 'b']), args)
+ args = parser.parse_args(['x', 'run', 'a', 'b', '--', '-f', 'c'])
+ self.assertEqual(NS(foo='x', f=None, bar=['a', 'b', '-f', 'c']), args)
+ args = parser.parse_args(['x', 'run', 'a', '--', 'b', '-f', 'c'])
+ self.assertEqual(NS(foo='x', f=None, bar=['a', 'b', '-f', 'c']), args)
+ args = parser.parse_args(['x', 'run', '--', 'a', 'b', '-f', 'c'])
+ self.assertEqual(NS(foo='x', f=None, bar=['a', 'b', '-f', 'c']), args)
+ args = parser.parse_args(['x', '--', 'run', 'a', 'b', '-f', 'c'])
+ self.assertEqual(NS(foo='x', f='c', bar=['a', 'b']), args)
+ args = parser.parse_args(['--', 'x', 'run', 'a', 'b', '-f', 'c'])
+ self.assertEqual(NS(foo='x', f='c', bar=['a', 'b']), args)
+ args = parser.parse_args(['x', 'run', '--', 'a', '--', 'b'])
+ self.assertEqual(NS(foo='x', f=None, bar=['a', '--', 'b']), args)
+ args = parser.parse_args(['x', '--', 'run', '--', 'a', '--', 'b'])
+ self.assertEqual(NS(foo='x', f=None, bar=['a', '--', 'b']), args)
+ self.assertRaisesRegex(argparse.ArgumentError,
+ "invalid choice: '--'",
+ parser.parse_args, ['--', 'x', '--', 'run', 'a', 'b'])
+
+ def test_subparser_after_multiple_argument_option(self):
+ parser = argparse.ArgumentParser(exit_on_error=False)
+ parser.add_argument('--foo', nargs='*')
+ subparsers = parser.add_subparsers()
+ parser1 = subparsers.add_parser('run')
+ parser1.add_argument('-f')
+ parser1.add_argument('bar', nargs='*')
+
+ args = parser.parse_args(['--foo', 'x', 'y', '--', 'run', 'a', 'b', '-f', 'c'])
+ self.assertEqual(NS(foo=['x', 'y'], f='c', bar=['a', 'b']), args)
+ self.assertRaisesRegex(argparse.ArgumentError,
+ "invalid choice: '--'",
+ parser.parse_args, ['--foo', 'x', '--', '--', 'run', 'a', 'b'])
# ===========================
@@ -5756,8 +6098,8 @@ def test_basic(self):
args, extras = parser.parse_known_args(argv)
# cannot parse the '1,2,3'
- self.assertEqual(NS(bar='y', cmd='cmd', foo='x', rest=[]), args)
- self.assertEqual(["1", "2", "3"], extras)
+ self.assertEqual(NS(bar='y', cmd='cmd', foo='x', rest=[1]), args)
+ self.assertEqual(["2", "3"], extras)
argv = 'cmd --foo x 1 --error 2 --bar y 3'.split()
args, extras = parser.parse_known_intermixed_args(argv)
@@ -5811,9 +6153,8 @@ def test_invalid_args(self):
parser = ErrorRaisingArgumentParser(prog='PROG')
parser.add_argument('--foo', nargs="*")
parser.add_argument('foo')
- with captured_stderr() as stderr:
+ with self.assertWarns(UserWarning):
parser.parse_intermixed_args(['hello', '--foo'])
- self.assertIn("UserWarning", stderr.getvalue())
class TestIntermixedMessageContentError(TestCase):
# case where Intermixed gives different error message
@@ -6108,7 +6449,28 @@ def test_required_args(self):
self.parser.add_argument('bar')
self.parser.add_argument('baz')
self.assertRaisesRegex(argparse.ArgumentError,
- 'the following arguments are required: bar, baz',
+ 'the following arguments are required: bar, baz$',
+ self.parser.parse_args, [])
+
+ def test_required_args_optional(self):
+ self.parser.add_argument('bar')
+ self.parser.add_argument('baz', nargs='?')
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'the following arguments are required: bar$',
+ self.parser.parse_args, [])
+
+ def test_required_args_zero_or_more(self):
+ self.parser.add_argument('bar')
+ self.parser.add_argument('baz', nargs='*')
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'the following arguments are required: bar$',
+ self.parser.parse_args, [])
+
+ def test_required_args_remainder(self):
+ self.parser.add_argument('bar')
+ self.parser.add_argument('baz', nargs='...')
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'the following arguments are required: bar$',
self.parser.parse_args, [])
def test_required_mutually_exclusive_args(self):
diff --git a/Lib/test/test_ast/data/ast_repr.txt b/Lib/test/test_ast/data/ast_repr.txt
new file mode 100644
index 00000000000000..3778b9e70a4605
--- /dev/null
+++ b/Lib/test/test_ast/data/ast_repr.txt
@@ -0,0 +1,209 @@
+Module(body=[Expr(value=Constant(value='module docstring', kind=None))], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Expr(value=Constant(...))], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[arg(...)], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[arg(...)], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[Constant(...)]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=arg(...), kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=arg(...), kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=arg(...), kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=arg(...), kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=arg(...), defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[arg(...), ..., arg(...)], vararg=arg(...), kwonlyargs=[arg(...)], kw_defaults=[Constant(...)], kwarg=arg(...), defaults=[Constant(...), ..., Dict(...)]), body=[Expr(value=Constant(...))], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=Subscript(value=Name(...), slice=Tuple(...), ctx=Load(...)), type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=Subscript(value=Name(...), slice=Tuple(...), ctx=Load(...)), type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=Subscript(value=Name(...), slice=Tuple(...), ctx=Load(...)), type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[ClassDef(name='C', bases=[], keywords=[], body=[Pass()], decorator_list=[], type_params=[])], type_ignores=[])
+Module(body=[ClassDef(name='C', bases=[], keywords=[], body=[Expr(value=Constant(...))], decorator_list=[], type_params=[])], type_ignores=[])
+Module(body=[ClassDef(name='C', bases=[Name(id='object', ctx=Load(...))], keywords=[], body=[Pass()], decorator_list=[], type_params=[])], type_ignores=[])
+Module(body=[ClassDef(name='C', bases=[Name(id='A', ctx=Load(...)), Name(id='B', ctx=Load(...))], keywords=[], body=[Pass()], decorator_list=[], type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Return(value=Constant(...))], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Return(value=None)], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[Delete(targets=[Name(id='v', ctx=Del(...))])], type_ignores=[])
+Module(body=[Assign(targets=[Name(id='v', ctx=Store(...))], value=Constant(value=1, kind=None), type_comment=None)], type_ignores=[])
+Module(body=[Assign(targets=[Tuple(elts=[Name(...), Name(...)], ctx=Store(...))], value=Name(id='c', ctx=Load(...)), type_comment=None)], type_ignores=[])
+Module(body=[Assign(targets=[Tuple(elts=[Name(...), Name(...)], ctx=Store(...))], value=Name(id='c', ctx=Load(...)), type_comment=None)], type_ignores=[])
+Module(body=[Assign(targets=[List(elts=[Name(...), Name(...)], ctx=Store(...))], value=Name(id='c', ctx=Load(...)), type_comment=None)], type_ignores=[])
+Module(body=[Assign(targets=[Subscript(value=Name(...), slice=Name(...), ctx=Store(...))], value=Name(id='c', ctx=Load(...)), type_comment=None)], type_ignores=[])
+Module(body=[AnnAssign(target=Name(id='x', ctx=Store(...)), annotation=Subscript(value=Name(...), slice=Tuple(...), ctx=Load(...)), value=None, simple=1)], type_ignores=[])
+Module(body=[AnnAssign(target=Name(id='x', ctx=Store(...)), annotation=Subscript(value=Name(...), slice=Tuple(...), ctx=Load(...)), value=None, simple=1)], type_ignores=[])
+Module(body=[AnnAssign(target=Name(id='x', ctx=Store(...)), annotation=Subscript(value=Name(...), slice=Tuple(...), ctx=Load(...)), value=None, simple=1)], type_ignores=[])
+Module(body=[AugAssign(target=Name(id='v', ctx=Store(...)), op=Add(), value=Constant(value=1, kind=None))], type_ignores=[])
+Module(body=[AugAssign(target=Name(id='v', ctx=Store(...)), op=Sub(), value=Constant(value=1, kind=None))], type_ignores=[])
+Module(body=[AugAssign(target=Name(id='v', ctx=Store(...)), op=Mult(), value=Constant(value=1, kind=None))], type_ignores=[])
+Module(body=[AugAssign(target=Name(id='v', ctx=Store(...)), op=MatMult(), value=Constant(value=1, kind=None))], type_ignores=[])
+Module(body=[AugAssign(target=Name(id='v', ctx=Store(...)), op=Div(), value=Constant(value=1, kind=None))], type_ignores=[])
+Module(body=[AugAssign(target=Name(id='v', ctx=Store(...)), op=Mod(), value=Constant(value=1, kind=None))], type_ignores=[])
+Module(body=[AugAssign(target=Name(id='v', ctx=Store(...)), op=Pow(), value=Constant(value=1, kind=None))], type_ignores=[])
+Module(body=[AugAssign(target=Name(id='v', ctx=Store(...)), op=LShift(), value=Constant(value=1, kind=None))], type_ignores=[])
+Module(body=[AugAssign(target=Name(id='v', ctx=Store(...)), op=RShift(), value=Constant(value=1, kind=None))], type_ignores=[])
+Module(body=[AugAssign(target=Name(id='v', ctx=Store(...)), op=BitOr(), value=Constant(value=1, kind=None))], type_ignores=[])
+Module(body=[AugAssign(target=Name(id='v', ctx=Store(...)), op=BitXor(), value=Constant(value=1, kind=None))], type_ignores=[])
+Module(body=[AugAssign(target=Name(id='v', ctx=Store(...)), op=BitAnd(), value=Constant(value=1, kind=None))], type_ignores=[])
+Module(body=[AugAssign(target=Name(id='v', ctx=Store(...)), op=FloorDiv(), value=Constant(value=1, kind=None))], type_ignores=[])
+Module(body=[For(target=Name(id='v', ctx=Store(...)), iter=Name(id='v', ctx=Load(...)), body=[Pass()], orelse=[], type_comment=None)], type_ignores=[])
+Module(body=[For(target=Name(id='v', ctx=Store(...)), iter=Name(id='v', ctx=Load(...)), body=[Pass()], orelse=[Pass()], type_comment=None)], type_ignores=[])
+Module(body=[While(test=Name(id='v', ctx=Load(...)), body=[Pass()], orelse=[])], type_ignores=[])
+Module(body=[While(test=Name(id='v', ctx=Load(...)), body=[Pass()], orelse=[Pass()])], type_ignores=[])
+Module(body=[If(test=Name(id='v', ctx=Load(...)), body=[Pass()], orelse=[])], type_ignores=[])
+Module(body=[If(test=Name(id='a', ctx=Load(...)), body=[Pass()], orelse=[If(test=Name(...), body=[Pass(...)], orelse=[])])], type_ignores=[])
+Module(body=[If(test=Name(id='a', ctx=Load(...)), body=[Pass()], orelse=[Pass()])], type_ignores=[])
+Module(body=[If(test=Name(id='a', ctx=Load(...)), body=[Pass()], orelse=[If(test=Name(...), body=[Pass(...)], orelse=[Pass(...)])])], type_ignores=[])
+Module(body=[If(test=Name(id='a', ctx=Load(...)), body=[Pass()], orelse=[If(test=Name(...), body=[Pass(...)], orelse=[If(...)])])], type_ignores=[])
+Module(body=[With(items=[withitem(context_expr=Name(...), optional_vars=None)], body=[Pass()], type_comment=None)], type_ignores=[])
+Module(body=[With(items=[withitem(context_expr=Name(...), optional_vars=None), withitem(context_expr=Name(...), optional_vars=None)], body=[Pass()], type_comment=None)], type_ignores=[])
+Module(body=[With(items=[withitem(context_expr=Name(...), optional_vars=Name(...))], body=[Pass()], type_comment=None)], type_ignores=[])
+Module(body=[With(items=[withitem(context_expr=Name(...), optional_vars=Name(...)), withitem(context_expr=Name(...), optional_vars=Name(...))], body=[Pass()], type_comment=None)], type_ignores=[])
+Module(body=[With(items=[withitem(context_expr=Name(...), optional_vars=Name(...))], body=[Pass()], type_comment=None)], type_ignores=[])
+Module(body=[With(items=[withitem(context_expr=Name(...), optional_vars=None), withitem(context_expr=Name(...), optional_vars=None)], body=[Pass()], type_comment=None)], type_ignores=[])
+Module(body=[Raise(exc=None, cause=None)], type_ignores=[])
+Module(body=[Raise(exc=Call(func=Name(...), args=[Constant(...)], keywords=[]), cause=None)], type_ignores=[])
+Module(body=[Raise(exc=Name(id='Exception', ctx=Load(...)), cause=None)], type_ignores=[])
+Module(body=[Raise(exc=Call(func=Name(...), args=[Constant(...)], keywords=[]), cause=Constant(value=None, kind=None))], type_ignores=[])
+Module(body=[Try(body=[Pass()], handlers=[ExceptHandler(type=Name(...), name=None, body=[Pass(...)])], orelse=[], finalbody=[])], type_ignores=[])
+Module(body=[Try(body=[Pass()], handlers=[ExceptHandler(type=Name(...), name='exc', body=[Pass(...)])], orelse=[], finalbody=[])], type_ignores=[])
+Module(body=[Try(body=[Pass()], handlers=[], orelse=[], finalbody=[Pass()])], type_ignores=[])
+Module(body=[TryStar(body=[Pass()], handlers=[ExceptHandler(type=Name(...), name=None, body=[Pass(...)])], orelse=[], finalbody=[])], type_ignores=[])
+Module(body=[TryStar(body=[Pass()], handlers=[ExceptHandler(type=Name(...), name='exc', body=[Pass(...)])], orelse=[], finalbody=[])], type_ignores=[])
+Module(body=[Try(body=[Pass()], handlers=[ExceptHandler(type=Name(...), name=None, body=[Pass(...)])], orelse=[Pass()], finalbody=[Pass()])], type_ignores=[])
+Module(body=[Try(body=[Pass()], handlers=[ExceptHandler(type=Name(...), name='exc', body=[Pass(...)])], orelse=[Pass()], finalbody=[Pass()])], type_ignores=[])
+Module(body=[TryStar(body=[Pass()], handlers=[ExceptHandler(type=Name(...), name='exc', body=[Pass(...)])], orelse=[Pass()], finalbody=[Pass()])], type_ignores=[])
+Module(body=[Assert(test=Name(id='v', ctx=Load(...)), msg=None)], type_ignores=[])
+Module(body=[Assert(test=Name(id='v', ctx=Load(...)), msg=Constant(value='message', kind=None))], type_ignores=[])
+Module(body=[Import(names=[alias(name='sys', asname=None)])], type_ignores=[])
+Module(body=[Import(names=[alias(name='foo', asname='bar')])], type_ignores=[])
+Module(body=[ImportFrom(module='sys', names=[alias(name='x', asname='y')], level=0)], type_ignores=[])
+Module(body=[ImportFrom(module='sys', names=[alias(name='v', asname=None)], level=0)], type_ignores=[])
+Module(body=[Global(names=['v'])], type_ignores=[])
+Module(body=[Expr(value=Constant(value=1, kind=None))], type_ignores=[])
+Module(body=[Pass()], type_ignores=[])
+Module(body=[For(target=Name(id='v', ctx=Store(...)), iter=Name(id='v', ctx=Load(...)), body=[Break()], orelse=[], type_comment=None)], type_ignores=[])
+Module(body=[For(target=Name(id='v', ctx=Store(...)), iter=Name(id='v', ctx=Load(...)), body=[Continue()], orelse=[], type_comment=None)], type_ignores=[])
+Module(body=[For(target=Tuple(elts=[Name(...), Name(...)], ctx=Store(...)), iter=Name(id='c', ctx=Load(...)), body=[Pass()], orelse=[], type_comment=None)], type_ignores=[])
+Module(body=[For(target=Tuple(elts=[Name(...), Name(...)], ctx=Store(...)), iter=Name(id='c', ctx=Load(...)), body=[Pass()], orelse=[], type_comment=None)], type_ignores=[])
+Module(body=[For(target=List(elts=[Name(...), Name(...)], ctx=Store(...)), iter=Name(id='c', ctx=Load(...)), body=[Pass()], orelse=[], type_comment=None)], type_ignores=[])
+Module(body=[Expr(value=GeneratorExp(elt=Tuple(...), generators=[comprehension(...)]))], type_ignores=[])
+Module(body=[Expr(value=DictComp(key=Name(...), value=Name(...), generators=[comprehension(...), comprehension(...)]))], type_ignores=[])
+Module(body=[Expr(value=DictComp(key=Name(...), value=Name(...), generators=[comprehension(...)]))], type_ignores=[])
+Module(body=[Expr(value=SetComp(elt=Name(...), generators=[comprehension(...)]))], type_ignores=[])
+Module(body=[Expr(value=SetComp(elt=Name(...), generators=[comprehension(...)]))], type_ignores=[])
+Module(body=[AsyncFunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Expr(value=Constant(...)), Expr(value=Await(...))], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[AsyncFunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[AsyncFor(target=Name(...), iter=Name(...), body=[Expr(...)], orelse=[Expr(...)], type_comment=None)], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[AsyncFunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[AsyncWith(items=[withitem(...)], body=[Expr(...)], type_comment=None)], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[Expr(value=Dict(keys=[None, Constant(...)], values=[Dict(...), Constant(...)]))], type_ignores=[])
+Module(body=[Expr(value=Set(elts=[Starred(...), Constant(...)]))], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Expr(value=Yield(...))], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Expr(value=YieldFrom(...))], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[AsyncFunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Expr(value=ListComp(...))], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[Name(id='deco1', ctx=Load(...)), ..., Call(func=Name(...), args=[Constant(...)], keywords=[])], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[AsyncFunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[Name(id='deco1', ctx=Load(...)), ..., Call(func=Name(...), args=[Constant(...)], keywords=[])], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[ClassDef(name='C', bases=[], keywords=[], body=[Pass()], decorator_list=[Name(id='deco1', ctx=Load(...)), ..., Call(func=Name(...), args=[Constant(...)], keywords=[])], type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[Call(func=Name(...), args=[GeneratorExp(...)], keywords=[])], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[Attribute(value=Attribute(...), attr='c', ctx=Load(...))], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[Expr(value=NamedExpr(target=Name(...), value=Constant(...)))], type_ignores=[])
+Module(body=[If(test=NamedExpr(target=Name(...), value=Call(...)), body=[Pass()], orelse=[])], type_ignores=[])
+Module(body=[While(test=NamedExpr(target=Name(...), value=Call(...)), body=[Pass()], orelse=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[arg(...)], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[arg(...)], args=[arg(...), ..., arg(...)], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[arg(...)], args=[arg(...)], vararg=None, kwonlyargs=[arg(...), arg(...)], kw_defaults=[None, None], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[arg(...)], args=[arg(...)], vararg=None, kwonlyargs=[arg(...), arg(...)], kw_defaults=[None, None], kwarg=arg(...), defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[arg(...)], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[Constant(...)]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[arg(...)], args=[arg(...), arg(...)], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[Constant(...), ..., Constant(...)]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[arg(...)], args=[arg(...)], vararg=None, kwonlyargs=[arg(...)], kw_defaults=[Constant(...)], kwarg=None, defaults=[Constant(...), Constant(...)]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[arg(...)], args=[arg(...)], vararg=None, kwonlyargs=[arg(...)], kw_defaults=[None], kwarg=None, defaults=[Constant(...), Constant(...)]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[arg(...)], args=[arg(...)], vararg=None, kwonlyargs=[arg(...)], kw_defaults=[Constant(...)], kwarg=arg(...), defaults=[Constant(...), Constant(...)]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[arg(...)], args=[arg(...)], vararg=None, kwonlyargs=[arg(...)], kw_defaults=[None], kwarg=arg(...), defaults=[Constant(...), Constant(...)]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[])
+Module(body=[TypeAlias(name=Name(id='X', ctx=Store(...)), type_params=[], value=Name(id='int', ctx=Load(...)))], type_ignores=[])
+Module(body=[TypeAlias(name=Name(id='X', ctx=Store(...)), type_params=[TypeVar(name='T', bound=None, default_value=None)], value=Name(id='int', ctx=Load(...)))], type_ignores=[])
+Module(body=[TypeAlias(name=Name(id='X', ctx=Store(...)), type_params=[TypeVar(name='T', bound=None, default_value=None), ..., ParamSpec(name='P', default_value=None)], value=Tuple(elts=[Name(...), ..., Name(...)], ctx=Load(...)))], type_ignores=[])
+Module(body=[TypeAlias(name=Name(id='X', ctx=Store(...)), type_params=[TypeVar(name='T', bound=Name(...), default_value=None), ..., ParamSpec(name='P', default_value=None)], value=Tuple(elts=[Name(...), ..., Name(...)], ctx=Load(...)))], type_ignores=[])
+Module(body=[TypeAlias(name=Name(id='X', ctx=Store(...)), type_params=[TypeVar(name='T', bound=Tuple(...), default_value=None), ..., ParamSpec(name='P', default_value=None)], value=Tuple(elts=[Name(...), ..., Name(...)], ctx=Load(...)))], type_ignores=[])
+Module(body=[TypeAlias(name=Name(id='X', ctx=Store(...)), type_params=[TypeVar(name='T', bound=Name(...), default_value=Constant(...)), ..., ParamSpec(name='P', default_value=Constant(...))], value=Tuple(elts=[Name(...), ..., Name(...)], ctx=Load(...)))], type_ignores=[])
+Module(body=[ClassDef(name='X', bases=[], keywords=[], body=[Pass()], decorator_list=[], type_params=[TypeVar(name='T', bound=None, default_value=None)])], type_ignores=[])
+Module(body=[ClassDef(name='X', bases=[], keywords=[], body=[Pass()], decorator_list=[], type_params=[TypeVar(name='T', bound=None, default_value=None), ..., ParamSpec(name='P', default_value=None)])], type_ignores=[])
+Module(body=[ClassDef(name='X', bases=[], keywords=[], body=[Pass()], decorator_list=[], type_params=[TypeVar(name='T', bound=Name(...), default_value=None), ..., ParamSpec(name='P', default_value=None)])], type_ignores=[])
+Module(body=[ClassDef(name='X', bases=[], keywords=[], body=[Pass()], decorator_list=[], type_params=[TypeVar(name='T', bound=Tuple(...), default_value=None), ..., ParamSpec(name='P', default_value=None)])], type_ignores=[])
+Module(body=[ClassDef(name='X', bases=[], keywords=[], body=[Pass()], decorator_list=[], type_params=[TypeVar(name='T', bound=Name(...), default_value=Constant(...)), ..., ParamSpec(name='P', default_value=Constant(...))])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[TypeVar(name='T', bound=None, default_value=None)])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[TypeVar(name='T', bound=None, default_value=None), ..., ParamSpec(name='P', default_value=None)])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[TypeVar(name='T', bound=Name(...), default_value=None), ..., ParamSpec(name='P', default_value=None)])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[TypeVar(name='T', bound=Tuple(...), default_value=None), ..., ParamSpec(name='P', default_value=None)])], type_ignores=[])
+Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[TypeVar(name='T', bound=Name(...), default_value=Constant(...)), ..., ParamSpec(name='P', default_value=Constant(...))])], type_ignores=[])
+Module(body=[Match(subject=Name(id='x', ctx=Load(...)), cases=[match_case(pattern=MatchValue(...), guard=None, body=[Pass(...)])])], type_ignores=[])
+Module(body=[Match(subject=Name(id='x', ctx=Load(...)), cases=[match_case(pattern=MatchValue(...), guard=None, body=[Pass(...)]), match_case(pattern=MatchAs(...), guard=None, body=[Pass(...)])])], type_ignores=[])
+Module(body=[Expr(value=Constant(value=None, kind=None))], type_ignores=[])
+Module(body=[Expr(value=Constant(value=True, kind=None))], type_ignores=[])
+Module(body=[Expr(value=Constant(value=False, kind=None))], type_ignores=[])
+Module(body=[Expr(value=BoolOp(op=And(...), values=[Name(...), Name(...)]))], type_ignores=[])
+Module(body=[Expr(value=BoolOp(op=Or(...), values=[Name(...), Name(...)]))], type_ignores=[])
+Module(body=[Expr(value=BinOp(left=Name(...), op=Add(...), right=Name(...)))], type_ignores=[])
+Module(body=[Expr(value=BinOp(left=Name(...), op=Sub(...), right=Name(...)))], type_ignores=[])
+Module(body=[Expr(value=BinOp(left=Name(...), op=Mult(...), right=Name(...)))], type_ignores=[])
+Module(body=[Expr(value=BinOp(left=Name(...), op=Div(...), right=Name(...)))], type_ignores=[])
+Module(body=[Expr(value=BinOp(left=Name(...), op=MatMult(...), right=Name(...)))], type_ignores=[])
+Module(body=[Expr(value=BinOp(left=Name(...), op=FloorDiv(...), right=Name(...)))], type_ignores=[])
+Module(body=[Expr(value=BinOp(left=Name(...), op=Pow(...), right=Name(...)))], type_ignores=[])
+Module(body=[Expr(value=BinOp(left=Name(...), op=Mod(...), right=Name(...)))], type_ignores=[])
+Module(body=[Expr(value=BinOp(left=Name(...), op=RShift(...), right=Name(...)))], type_ignores=[])
+Module(body=[Expr(value=BinOp(left=Name(...), op=LShift(...), right=Name(...)))], type_ignores=[])
+Module(body=[Expr(value=BinOp(left=Name(...), op=BitXor(...), right=Name(...)))], type_ignores=[])
+Module(body=[Expr(value=BinOp(left=Name(...), op=BitOr(...), right=Name(...)))], type_ignores=[])
+Module(body=[Expr(value=BinOp(left=Name(...), op=BitAnd(...), right=Name(...)))], type_ignores=[])
+Module(body=[Expr(value=UnaryOp(op=Not(...), operand=Name(...)))], type_ignores=[])
+Module(body=[Expr(value=UnaryOp(op=UAdd(...), operand=Name(...)))], type_ignores=[])
+Module(body=[Expr(value=UnaryOp(op=USub(...), operand=Name(...)))], type_ignores=[])
+Module(body=[Expr(value=UnaryOp(op=Invert(...), operand=Name(...)))], type_ignores=[])
+Module(body=[Expr(value=Lambda(args=arguments(...), body=Constant(...)))], type_ignores=[])
+Module(body=[Expr(value=Dict(keys=[Constant(...)], values=[Constant(...)]))], type_ignores=[])
+Module(body=[Expr(value=Dict(keys=[], values=[]))], type_ignores=[])
+Module(body=[Expr(value=Set(elts=[Constant(...)]))], type_ignores=[])
+Module(body=[Expr(value=Dict(keys=[Constant(...)], values=[Constant(...)]))], type_ignores=[])
+Module(body=[Expr(value=List(elts=[Constant(...), Constant(...)], ctx=Load(...)))], type_ignores=[])
+Module(body=[Expr(value=Tuple(elts=[Constant(...)], ctx=Load(...)))], type_ignores=[])
+Module(body=[Expr(value=Set(elts=[Constant(...), Constant(...)]))], type_ignores=[])
+Module(body=[Expr(value=ListComp(elt=Name(...), generators=[comprehension(...)]))], type_ignores=[])
+Module(body=[Expr(value=GeneratorExp(elt=Name(...), generators=[comprehension(...)]))], type_ignores=[])
+Module(body=[Expr(value=SetComp(elt=Name(...), generators=[comprehension(...)]))], type_ignores=[])
+Module(body=[Expr(value=DictComp(key=Name(...), value=Name(...), generators=[comprehension(...)]))], type_ignores=[])
+Module(body=[Expr(value=ListComp(elt=Tuple(...), generators=[comprehension(...)]))], type_ignores=[])
+Module(body=[Expr(value=ListComp(elt=Tuple(...), generators=[comprehension(...)]))], type_ignores=[])
+Module(body=[Expr(value=ListComp(elt=Tuple(...), generators=[comprehension(...)]))], type_ignores=[])
+Module(body=[Expr(value=SetComp(elt=Tuple(...), generators=[comprehension(...)]))], type_ignores=[])
+Module(body=[Expr(value=SetComp(elt=Tuple(...), generators=[comprehension(...)]))], type_ignores=[])
+Module(body=[Expr(value=SetComp(elt=Tuple(...), generators=[comprehension(...)]))], type_ignores=[])
+Module(body=[Expr(value=GeneratorExp(elt=Tuple(...), generators=[comprehension(...)]))], type_ignores=[])
+Module(body=[Expr(value=GeneratorExp(elt=Tuple(...), generators=[comprehension(...)]))], type_ignores=[])
+Module(body=[Expr(value=GeneratorExp(elt=Tuple(...), generators=[comprehension(...)]))], type_ignores=[])
+Module(body=[Expr(value=Compare(left=Constant(...), ops=[Lt(...), Lt(...)], comparators=[Constant(...), Constant(...)]))], type_ignores=[])
+Module(body=[Expr(value=Compare(left=Name(...), ops=[Eq(...)], comparators=[Name(...)]))], type_ignores=[])
+Module(body=[Expr(value=Compare(left=Name(...), ops=[LtE(...)], comparators=[Name(...)]))], type_ignores=[])
+Module(body=[Expr(value=Compare(left=Name(...), ops=[GtE(...)], comparators=[Name(...)]))], type_ignores=[])
+Module(body=[Expr(value=Compare(left=Name(...), ops=[NotEq(...)], comparators=[Name(...)]))], type_ignores=[])
+Module(body=[Expr(value=Compare(left=Name(...), ops=[Is(...)], comparators=[Name(...)]))], type_ignores=[])
+Module(body=[Expr(value=Compare(left=Name(...), ops=[IsNot(...)], comparators=[Name(...)]))], type_ignores=[])
+Module(body=[Expr(value=Compare(left=Name(...), ops=[In(...)], comparators=[Name(...)]))], type_ignores=[])
+Module(body=[Expr(value=Compare(left=Name(...), ops=[NotIn(...)], comparators=[Name(...)]))], type_ignores=[])
+Module(body=[Expr(value=Call(func=Name(...), args=[], keywords=[]))], type_ignores=[])
+Module(body=[Expr(value=Call(func=Name(...), args=[Constant(...), ..., Starred(...)], keywords=[keyword(...), keyword(...)]))], type_ignores=[])
+Module(body=[Expr(value=Call(func=Name(...), args=[Starred(...)], keywords=[]))], type_ignores=[])
+Module(body=[Expr(value=Call(func=Name(...), args=[GeneratorExp(...)], keywords=[]))], type_ignores=[])
+Module(body=[Expr(value=Constant(value=10, kind=None))], type_ignores=[])
+Module(body=[Expr(value=Constant(value=1j, kind=None))], type_ignores=[])
+Module(body=[Expr(value=Constant(value='string', kind=None))], type_ignores=[])
+Module(body=[Expr(value=Attribute(value=Name(...), attr='b', ctx=Load(...)))], type_ignores=[])
+Module(body=[Expr(value=Subscript(value=Name(...), slice=Slice(...), ctx=Load(...)))], type_ignores=[])
+Module(body=[Expr(value=Name(id='v', ctx=Load(...)))], type_ignores=[])
+Module(body=[Expr(value=List(elts=[Constant(...), ..., Constant(...)], ctx=Load(...)))], type_ignores=[])
+Module(body=[Expr(value=List(elts=[], ctx=Load(...)))], type_ignores=[])
+Module(body=[Expr(value=Tuple(elts=[Constant(...), ..., Constant(...)], ctx=Load(...)))], type_ignores=[])
+Module(body=[Expr(value=Tuple(elts=[Constant(...), ..., Constant(...)], ctx=Load(...)))], type_ignores=[])
+Module(body=[Expr(value=Tuple(elts=[], ctx=Load(...)))], type_ignores=[])
+Module(body=[Expr(value=Call(func=Attribute(...), args=[Subscript(...)], keywords=[]))], type_ignores=[])
+Module(body=[Expr(value=Subscript(value=List(...), slice=Slice(...), ctx=Load(...)))], type_ignores=[])
+Module(body=[Expr(value=Subscript(value=List(...), slice=Slice(...), ctx=Load(...)))], type_ignores=[])
+Module(body=[Expr(value=Subscript(value=List(...), slice=Slice(...), ctx=Load(...)))], type_ignores=[])
+Module(body=[Expr(value=Subscript(value=List(...), slice=Slice(...), ctx=Load(...)))], type_ignores=[])
+Module(body=[Expr(value=IfExp(test=Name(...), body=Call(...), orelse=Call(...)))], type_ignores=[])
+Module(body=[Expr(value=JoinedStr(values=[FormattedValue(...)]))], type_ignores=[])
+Module(body=[Expr(value=JoinedStr(values=[FormattedValue(...)]))], type_ignores=[])
+Module(body=[Expr(value=JoinedStr(values=[FormattedValue(...)]))], type_ignores=[])
+Module(body=[Expr(value=JoinedStr(values=[Constant(...), ..., Constant(...)]))], type_ignores=[])
\ No newline at end of file
diff --git a/Lib/test/test_ast/test_ast.py b/Lib/test/test_ast/test_ast.py
index 77596eca5d8b74..f052822cb45273 100644
--- a/Lib/test/test_ast/test_ast.py
+++ b/Lib/test/test_ast/test_ast.py
@@ -10,6 +10,7 @@
import types
import unittest
import weakref
+from pathlib import Path
from textwrap import dedent
try:
import _testinternalcapi
@@ -29,6 +30,16 @@
STDLIB_FILES = [fn for fn in os.listdir(STDLIB) if fn.endswith(".py")]
STDLIB_FILES.extend(["test/test_grammar.py", "test/test_unpack_ex.py"])
+AST_REPR_DATA_FILE = Path(__file__).parent / "data" / "ast_repr.txt"
+
+def ast_repr_get_test_cases() -> list[str]:
+ return exec_tests + eval_tests
+
+
+def ast_repr_update_snapshots() -> None:
+ data = [repr(ast.parse(test)) for test in ast_repr_get_test_cases()]
+ AST_REPR_DATA_FILE.write_text("\n".join(data))
+
class AST_Tests(unittest.TestCase):
maxDiff = None
@@ -408,7 +419,7 @@ def test_invalid_sum(self):
m = ast.Module([ast.Expr(ast.expr(**pos), **pos)], [])
with self.assertRaises(TypeError) as cm:
compile(m, "", "exec")
- self.assertIn("but got None:
for node, attr, source in tests:
self.assert_none_check(node, attr, source)
+ def test_repr(self) -> None:
+ snapshots = AST_REPR_DATA_FILE.read_text().split("\n")
+ for test, snapshot in zip(ast_repr_get_test_cases(), snapshots, strict=True):
+ with self.subTest(test_input=test):
+ self.assertEqual(repr(ast.parse(test)), snapshot)
+
class CopyTests(unittest.TestCase):
"""Test copying and pickling AST nodes."""
@@ -3332,5 +3349,8 @@ def test_folding_type_param_in_type_alias(self):
self.assert_ast(result_code, non_optimized_target, optimized_target)
-if __name__ == "__main__":
+if __name__ == '__main__':
+ if len(sys.argv) > 1 and sys.argv[1] == '--snapshot-update':
+ ast_repr_update_snapshots()
+ sys.exit(0)
unittest.main()
diff --git a/Lib/test/test_bz2.py b/Lib/test/test_bz2.py
index e4d1381be5f340..7d786be1d25b1c 100644
--- a/Lib/test/test_bz2.py
+++ b/Lib/test/test_bz2.py
@@ -476,7 +476,6 @@ def testReadlinesNoNewline(self):
self.assertEqual(xlines, [b'Test'])
def testContextProtocol(self):
- f = None
with BZ2File(self.filename, "wb") as f:
f.write(b"xxx")
f = BZ2File(self.filename, "rb")
diff --git a/Lib/test/test_capi/test_complex.py b/Lib/test/test_capi/test_complex.py
index 328ea12f97462c..368edfbf2ce97e 100644
--- a/Lib/test/test_capi/test_complex.py
+++ b/Lib/test/test_capi/test_complex.py
@@ -226,7 +226,11 @@ def test_py_c_pow(self):
self.assertEqual(_py_c_pow(0j, -1)[1], errno.EDOM)
self.assertEqual(_py_c_pow(0j, 1j)[1], errno.EDOM)
- self.assertEqual(_py_c_pow(*[DBL_MAX+1j]*2)[0], complex(*[INF]*2))
+ max_num = DBL_MAX+1j
+ self.assertEqual(_py_c_pow(max_num, max_num),
+ (complex(INF, INF), errno.ERANGE))
+ self.assertEqual(_py_c_pow(max_num, 2),
+ (complex(INF, INF), errno.ERANGE))
def test_py_c_abs(self):
diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py
index ebc0a8a3d4efb5..5c6faa1626d380 100644
--- a/Lib/test/test_capi/test_misc.py
+++ b/Lib/test/test_capi/test_misc.py
@@ -1144,6 +1144,77 @@ class MyType:
MyType.__module__ = 123
self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname')
+ def test_get_base_by_token(self):
+ def get_base_by_token(src, key, comparable=True):
+ def run(use_mro):
+ find_first = _testcapi.pytype_getbasebytoken
+ ret1, result = find_first(src, key, use_mro, True)
+ ret2, no_result = find_first(src, key, use_mro, False)
+ self.assertIn(ret1, (0, 1))
+ self.assertEqual(ret1, result is not None)
+ self.assertEqual(ret1, ret2)
+ self.assertIsNone(no_result)
+ return result
+
+ found_in_mro = run(True)
+ found_in_bases = run(False)
+ if comparable:
+ self.assertIs(found_in_mro, found_in_bases)
+ return found_in_mro
+ return found_in_mro, found_in_bases
+
+ create_type = _testcapi.create_type_with_token
+ get_token = _testcapi.get_tp_token
+
+ Py_TP_USE_SPEC = _testcapi.Py_TP_USE_SPEC
+ self.assertEqual(Py_TP_USE_SPEC, 0)
+
+ A1 = create_type('_testcapi.A1', Py_TP_USE_SPEC)
+ self.assertTrue(get_token(A1) != Py_TP_USE_SPEC)
+
+ B1 = create_type('_testcapi.B1', id(self))
+ self.assertTrue(get_token(B1) == id(self))
+
+ tokenA1 = get_token(A1)
+ # find A1 from A1
+ found = get_base_by_token(A1, tokenA1)
+ self.assertIs(found, A1)
+
+ # no token in static types
+ STATIC = type(1)
+ self.assertEqual(get_token(STATIC), 0)
+ found = get_base_by_token(STATIC, tokenA1)
+ self.assertIs(found, None)
+
+ # no token in pure subtypes
+ class A2(A1): pass
+ self.assertEqual(get_token(A2), 0)
+ # find A1
+ class Z(STATIC, B1, A2): pass
+ found = get_base_by_token(Z, tokenA1)
+ self.assertIs(found, A1)
+
+ # searching for NULL token is an error
+ with self.assertRaises(SystemError):
+ get_base_by_token(Z, 0)
+ with self.assertRaises(SystemError):
+ get_base_by_token(STATIC, 0)
+
+ # share the token with A1
+ C1 = create_type('_testcapi.C1', tokenA1)
+ self.assertTrue(get_token(C1) == tokenA1)
+
+ # find C1 first by shared token
+ class Z(C1, A2): pass
+ found = get_base_by_token(Z, tokenA1)
+ self.assertIs(found, C1)
+ # B1 not found
+ found = get_base_by_token(Z, get_token(B1))
+ self.assertIs(found, None)
+
+ with self.assertRaises(TypeError):
+ _testcapi.pytype_getbasebytoken(
+ 'not a type', id(self), True, False)
def test_gen_get_code(self):
def genf(): yield
diff --git a/Lib/test/test_capi/test_watchers.py b/Lib/test/test_capi/test_watchers.py
index 709b5e1c4b716a..f21d2627c6094b 100644
--- a/Lib/test/test_capi/test_watchers.py
+++ b/Lib/test/test_capi/test_watchers.py
@@ -1,4 +1,5 @@
import unittest
+import contextvars
from contextlib import contextmanager, ExitStack
from test.support import (
@@ -571,5 +572,87 @@ def test_allocate_too_many_watchers(self):
_testcapi.allocate_too_many_func_watchers()
+class TestContextObjectWatchers(unittest.TestCase):
+ @contextmanager
+ def context_watcher(self, which_watcher):
+ wid = _testcapi.add_context_watcher(which_watcher)
+ try:
+ yield wid
+ finally:
+ _testcapi.clear_context_watcher(wid)
+
+ def assert_event_counts(self, exp_enter_0, exp_exit_0,
+ exp_enter_1, exp_exit_1):
+ self.assertEqual(
+ exp_enter_0, _testcapi.get_context_watcher_num_enter_events(0))
+ self.assertEqual(
+ exp_exit_0, _testcapi.get_context_watcher_num_exit_events(0))
+ self.assertEqual(
+ exp_enter_1, _testcapi.get_context_watcher_num_enter_events(1))
+ self.assertEqual(
+ exp_exit_1, _testcapi.get_context_watcher_num_exit_events(1))
+
+ def test_context_object_events_dispatched(self):
+ # verify that all counts are zero before any watchers are registered
+ self.assert_event_counts(0, 0, 0, 0)
+
+ # verify that all counts remain zero when a context object is
+ # entered and exited with no watchers registered
+ ctx = contextvars.copy_context()
+ ctx.run(self.assert_event_counts, 0, 0, 0, 0)
+ self.assert_event_counts(0, 0, 0, 0)
+
+ # verify counts are as expected when first watcher is registered
+ with self.context_watcher(0):
+ self.assert_event_counts(0, 0, 0, 0)
+ ctx.run(self.assert_event_counts, 1, 0, 0, 0)
+ self.assert_event_counts(1, 1, 0, 0)
+
+ # again with second watcher registered
+ with self.context_watcher(1):
+ self.assert_event_counts(1, 1, 0, 0)
+ ctx.run(self.assert_event_counts, 2, 1, 1, 0)
+ self.assert_event_counts(2, 2, 1, 1)
+
+ # verify counts are reset and don't change after both watchers are cleared
+ ctx.run(self.assert_event_counts, 0, 0, 0, 0)
+ self.assert_event_counts(0, 0, 0, 0)
+
+ def test_enter_error(self):
+ with self.context_watcher(2):
+ with catch_unraisable_exception() as cm:
+ ctx = contextvars.copy_context()
+ ctx.run(int, 0)
+ self.assertEqual(
+ cm.unraisable.err_msg,
+ "Exception ignored in "
+ f"Py_CONTEXT_EVENT_EXIT watcher callback for {ctx!r}"
+ )
+ self.assertEqual(str(cm.unraisable.exc_value), "boom!")
+
+ def test_exit_error(self):
+ ctx = contextvars.copy_context()
+ def _in_context(stack):
+ stack.enter_context(self.context_watcher(2))
+
+ with catch_unraisable_exception() as cm:
+ with ExitStack() as stack:
+ ctx.run(_in_context, stack)
+ self.assertEqual(str(cm.unraisable.exc_value), "boom!")
+
+ def test_clear_out_of_range_watcher_id(self):
+ with self.assertRaisesRegex(ValueError, r"Invalid context watcher ID -1"):
+ _testcapi.clear_context_watcher(-1)
+ with self.assertRaisesRegex(ValueError, r"Invalid context watcher ID 8"):
+ _testcapi.clear_context_watcher(8) # CONTEXT_MAX_WATCHERS = 8
+
+ def test_clear_unassigned_watcher_id(self):
+ with self.assertRaisesRegex(ValueError, r"No context watcher set for ID 1"):
+ _testcapi.clear_context_watcher(1)
+
+ def test_allocate_too_many_watchers(self):
+ with self.assertRaisesRegex(RuntimeError, r"no more context watcher IDs available"):
+ _testcapi.allocate_too_many_context_watchers()
+
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_cext/extension.c b/Lib/test/test_cext/extension.c
index eb23dbe20353ba..b76abe1d74c628 100644
--- a/Lib/test/test_cext/extension.c
+++ b/Lib/test/test_cext/extension.c
@@ -37,7 +37,13 @@ static PyMethodDef _testcext_methods[] = {
static int
-_testcext_exec(PyObject *module)
+_testcext_exec(
+#ifdef __STDC_VERSION__
+ PyObject *module
+#else
+ PyObject *Py_UNUSED(module)
+#endif
+ )
{
#ifdef __STDC_VERSION__
if (PyModule_AddIntMacro(module, __STDC_VERSION__) < 0) {
@@ -53,7 +59,7 @@ _testcext_exec(PyObject *module)
}
static PyModuleDef_Slot _testcext_slots[] = {
- {Py_mod_exec, _testcext_exec},
+ {Py_mod_exec, (void*)_testcext_exec},
{0, NULL}
};
diff --git a/Lib/test/test_cext/setup.py b/Lib/test/test_cext/setup.py
index 19edc5e663c55d..e97749b45ea6f3 100644
--- a/Lib/test/test_cext/setup.py
+++ b/Lib/test/test_cext/setup.py
@@ -31,6 +31,8 @@
else:
# MSVC compiler flags
CFLAGS = [
+ # Display warnings level 1 to 4
+ '/W4',
# Treat all compiler warnings as compiler errors
'/WX',
]
diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py
index 736eff35c1d5f2..b81d847c824273 100644
--- a/Lib/test/test_compile.py
+++ b/Lib/test/test_compile.py
@@ -1527,6 +1527,45 @@ async def name_4():
pass
[[]]
+class TestBooleanExpression(unittest.TestCase):
+ class Value:
+ def __init__(self):
+ self.called = 0
+
+ def __bool__(self):
+ self.called += 1
+ return self.value
+
+ class Yes(Value):
+ value = True
+
+ class No(Value):
+ value = False
+
+ def test_short_circuit_and(self):
+ v = [self.Yes(), self.No(), self.Yes()]
+ res = v[0] and v[1] and v[0]
+ self.assertIs(res, v[1])
+ self.assertEqual([e.called for e in v], [1, 1, 0])
+
+ def test_short_circuit_or(self):
+ v = [self.No(), self.Yes(), self.No()]
+ res = v[0] or v[1] or v[0]
+ self.assertIs(res, v[1])
+ self.assertEqual([e.called for e in v], [1, 1, 0])
+
+ def test_compound(self):
+ # See gh-124285
+ v = [self.No(), self.Yes(), self.Yes(), self.Yes()]
+ res = v[0] and v[1] or v[2] or v[3]
+ self.assertIs(res, v[2])
+ self.assertEqual([e.called for e in v], [1, 0, 1, 0])
+
+ v = [self.No(), self.No(), self.Yes(), self.Yes(), self.No()]
+ res = v[0] or v[1] and v[2] or v[3] or v[4]
+ self.assertIs(res, v[3])
+ self.assertEqual([e.called for e in v], [1, 1, 0, 1, 0])
+
@requires_debug_ranges()
class TestSourcePositions(unittest.TestCase):
# Ensure that compiled code snippets have correct line and column numbers
diff --git a/Lib/test/test_compiler_codegen.py b/Lib/test/test_compiler_codegen.py
index d82fb85ed259ab..8a15c400a449e1 100644
--- a/Lib/test/test_compiler_codegen.py
+++ b/Lib/test/test_compiler_codegen.py
@@ -152,5 +152,8 @@ def g():
def test_syntax_error__return_not_in_function(self):
snippet = "return 42"
- with self.assertRaisesRegex(SyntaxError, "'return' outside function"):
+ with self.assertRaisesRegex(SyntaxError, "'return' outside function") as cm:
self.codegen_test(snippet, None)
+ self.assertIsNone(cm.exception.text)
+ self.assertEqual(cm.exception.offset, 1)
+ self.assertEqual(cm.exception.end_offset, 10)
diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py
index 36c3abca80f894..cf6519598037e9 100644
--- a/Lib/test/test_contextlib.py
+++ b/Lib/test/test_contextlib.py
@@ -444,12 +444,10 @@ class FileContextTestCase(unittest.TestCase):
def testWithOpen(self):
tfn = tempfile.mktemp()
try:
- f = None
with open(tfn, "w", encoding="utf-8") as f:
self.assertFalse(f.closed)
f.write("Booh\n")
self.assertTrue(f.closed)
- f = None
with self.assertRaises(ZeroDivisionError):
with open(tfn, "r", encoding="utf-8") as f:
self.assertFalse(f.closed)
diff --git a/Lib/test/test_cppext/setup.py b/Lib/test/test_cppext/setup.py
index f1848f2fd42a46..d97b238b8d1477 100644
--- a/Lib/test/test_cppext/setup.py
+++ b/Lib/test/test_cppext/setup.py
@@ -22,6 +22,8 @@
else:
# MSVC compiler flags
CPPFLAGS = [
+ # Display warnings level 1 to 4
+ '/W4',
# Treat all compiler warnings as compiler errors
'/WX',
]
diff --git a/Lib/test/test_ctypes/test_struct_fields.py b/Lib/test/test_ctypes/test_struct_fields.py
index 7d7a518c0138f9..b5e165f3bae929 100644
--- a/Lib/test/test_ctypes/test_struct_fields.py
+++ b/Lib/test/test_ctypes/test_struct_fields.py
@@ -4,7 +4,9 @@
Py_TPFLAGS_IMMUTABLETYPE)
-class StructFieldsTestCase(unittest.TestCase):
+NOTHING = object()
+
+class FieldsTestBase:
# Structure/Union classes must get 'finalized' sooner or
# later, when one of these things happen:
#
@@ -14,42 +16,47 @@ class StructFieldsTestCase(unittest.TestCase):
# 4. The type is subclassed
#
# When they are finalized, assigning _fields_ is no longer allowed.
+
+ def assert_final_fields(self, cls, expected=NOTHING):
+ self.assertRaises(AttributeError, setattr, cls, "_fields_", [])
+ self.assertEqual(getattr(cls, "_fields_", NOTHING), expected)
+
def test_1_A(self):
- class X(Structure):
+ class X(self.cls):
pass
self.assertEqual(sizeof(X), 0) # not finalized
X._fields_ = [] # finalized
- self.assertRaises(AttributeError, setattr, X, "_fields_", [])
+ self.assert_final_fields(X, expected=[])
def test_1_B(self):
- class X(Structure):
+ class X(self.cls):
_fields_ = [] # finalized
- self.assertRaises(AttributeError, setattr, X, "_fields_", [])
+ self.assert_final_fields(X, expected=[])
def test_2(self):
- class X(Structure):
+ class X(self.cls):
pass
X()
- self.assertRaises(AttributeError, setattr, X, "_fields_", [])
+ self.assert_final_fields(X)
def test_3(self):
- class X(Structure):
+ class X(self.cls):
pass
- class Y(Structure):
+ class Y(self.cls):
_fields_ = [("x", X)] # finalizes X
- self.assertRaises(AttributeError, setattr, X, "_fields_", [])
+ self.assert_final_fields(X)
def test_4(self):
- class X(Structure):
+ class X(self.cls):
pass
class Y(X):
pass
- self.assertRaises(AttributeError, setattr, X, "_fields_", [])
+ self.assert_final_fields(X)
Y._fields_ = []
- self.assertRaises(AttributeError, setattr, X, "_fields_", [])
+ self.assert_final_fields(X)
def test_5(self):
- class X(Structure):
+ class X(self.cls):
_fields_ = (("char", c_char * 5),)
x = X(b'#' * 5)
@@ -59,14 +66,8 @@ class X(Structure):
def test_6(self):
self.assertRaises(TypeError, CField)
- def test_cfield_type_flags(self):
- self.assertTrue(CField.__flags__ & Py_TPFLAGS_IMMUTABLETYPE)
-
- def test_cfield_inheritance_hierarchy(self):
- self.assertEqual(CField.mro(), [CField, object])
-
def test_gh99275(self):
- class BrokenStructure(Structure):
+ class BrokenStructure(self.cls):
def __init_subclass__(cls, **kwargs):
cls._fields_ = [] # This line will fail, `stginfo` is not ready
@@ -77,26 +78,28 @@ class Subclass(BrokenStructure): ...
# __set__ and __get__ should raise a TypeError in case their self
# argument is not a ctype instance.
def test___set__(self):
- class MyCStruct(Structure):
+ class MyCStruct(self.cls):
_fields_ = (("field", c_int),)
self.assertRaises(TypeError,
MyCStruct.field.__set__, 'wrong type self', 42)
- class MyCUnion(Union):
- _fields_ = (("field", c_int),)
- self.assertRaises(TypeError,
- MyCUnion.field.__set__, 'wrong type self', 42)
-
def test___get__(self):
- class MyCStruct(Structure):
+ class MyCStruct(self.cls):
_fields_ = (("field", c_int),)
self.assertRaises(TypeError,
MyCStruct.field.__get__, 'wrong type self', 42)
- class MyCUnion(Union):
- _fields_ = (("field", c_int),)
- self.assertRaises(TypeError,
- MyCUnion.field.__get__, 'wrong type self', 42)
+class StructFieldsTestCase(unittest.TestCase, FieldsTestBase):
+ cls = Structure
+
+ def test_cfield_type_flags(self):
+ self.assertTrue(CField.__flags__ & Py_TPFLAGS_IMMUTABLETYPE)
+
+ def test_cfield_inheritance_hierarchy(self):
+ self.assertEqual(CField.mro(), [CField, object])
+
+class UnionFieldsTestCase(unittest.TestCase, FieldsTestBase):
+ cls = Union
if __name__ == "__main__":
diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py
index 6934e88d9d338c..69e86162e0c11a 100644
--- a/Lib/test/test_dataclasses/__init__.py
+++ b/Lib/test/test_dataclasses/__init__.py
@@ -17,7 +17,7 @@
from typing import ClassVar, Any, List, Union, Tuple, Dict, Generic, TypeVar, Optional, Protocol, DefaultDict
from typing import get_type_hints
from collections import deque, OrderedDict, namedtuple, defaultdict
-from functools import total_ordering
+from functools import total_ordering, wraps
import typing # Needed for the string "typing.ClassVar[int]" to work as an annotation.
import dataclasses # Needed for the string "dataclasses.InitVar[int]" to work as an annotation.
@@ -4869,5 +4869,129 @@ class A:
self.assertEqual(fs[0].name, 'x')
+class TestZeroArgumentSuperWithSlots(unittest.TestCase):
+ def test_zero_argument_super(self):
+ @dataclass(slots=True)
+ class A:
+ def foo(self):
+ super()
+
+ A().foo()
+
+ def test_dunder_class_with_old_property(self):
+ @dataclass(slots=True)
+ class A:
+ def _get_foo(slf):
+ self.assertIs(__class__, type(slf))
+ self.assertIs(__class__, slf.__class__)
+ return __class__
+
+ def _set_foo(slf, value):
+ self.assertIs(__class__, type(slf))
+ self.assertIs(__class__, slf.__class__)
+
+ def _del_foo(slf):
+ self.assertIs(__class__, type(slf))
+ self.assertIs(__class__, slf.__class__)
+
+ foo = property(_get_foo, _set_foo, _del_foo)
+
+ a = A()
+ self.assertIs(a.foo, A)
+ a.foo = 4
+ del a.foo
+
+ def test_dunder_class_with_new_property(self):
+ @dataclass(slots=True)
+ class A:
+ @property
+ def foo(slf):
+ return slf.__class__
+
+ @foo.setter
+ def foo(slf, value):
+ self.assertIs(__class__, type(slf))
+
+ @foo.deleter
+ def foo(slf):
+ self.assertIs(__class__, type(slf))
+
+ a = A()
+ self.assertIs(a.foo, A)
+ a.foo = 4
+ del a.foo
+
+ # Test the parts of a property individually.
+ def test_slots_dunder_class_property_getter(self):
+ @dataclass(slots=True)
+ class A:
+ @property
+ def foo(slf):
+ return __class__
+
+ a = A()
+ self.assertIs(a.foo, A)
+
+ def test_slots_dunder_class_property_setter(self):
+ @dataclass(slots=True)
+ class A:
+ foo = property()
+ @foo.setter
+ def foo(slf, val):
+ self.assertIs(__class__, type(slf))
+
+ a = A()
+ a.foo = 4
+
+ def test_slots_dunder_class_property_deleter(self):
+ @dataclass(slots=True)
+ class A:
+ foo = property()
+ @foo.deleter
+ def foo(slf):
+ self.assertIs(__class__, type(slf))
+
+ a = A()
+ del a.foo
+
+ def test_wrapped(self):
+ def mydecorator(f):
+ @wraps(f)
+ def wrapper(*args, **kwargs):
+ return f(*args, **kwargs)
+ return wrapper
+
+ @dataclass(slots=True)
+ class A:
+ @mydecorator
+ def foo(self):
+ super()
+
+ A().foo()
+
+ def test_remembered_class(self):
+ # Apply the dataclass decorator manually (not when the class
+ # is created), so that we can keep a reference to the
+ # undecorated class.
+ class A:
+ def cls(self):
+ return __class__
+
+ self.assertIs(A().cls(), A)
+
+ B = dataclass(slots=True)(A)
+ self.assertIs(B().cls(), B)
+
+ # This is undesirable behavior, but is a function of how
+ # modifying __class__ in the closure works. I'm not sure this
+ # should be tested or not: I don't really want to guarantee
+ # this behavior, but I don't want to lose the point that this
+ # is how it works.
+
+ # The underlying class is "broken" by changing its __class__
+ # in A.foo() to B. This normally isn't a problem, because no
+ # one will be keeping a reference to the underlying class A.
+ self.assertIs(A().cls(), B)
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/test/test_eof.py b/Lib/test/test_eof.py
index be4fd73bfdc36b..e377383450e19d 100644
--- a/Lib/test/test_eof.py
+++ b/Lib/test/test_eof.py
@@ -1,6 +1,7 @@
"""test script for a few new invalid token catches"""
import sys
+from codecs import BOM_UTF8
from test import support
from test.support import os_helper
from test.support import script_helper
@@ -11,67 +12,158 @@ class EOFTestCase(unittest.TestCase):
def test_EOF_single_quote(self):
expect = "unterminated string literal (detected at line 1) (, line 1)"
for quote in ("'", "\""):
- try:
+ with self.assertRaises(SyntaxError) as cm:
eval(f"""{quote}this is a test\
""")
- except SyntaxError as msg:
- self.assertEqual(str(msg), expect)
- self.assertEqual(msg.offset, 1)
- else:
- raise support.TestFailed
+ self.assertEqual(str(cm.exception), expect)
+ self.assertEqual(cm.exception.offset, 1)
def test_EOFS(self):
- expect = ("unterminated triple-quoted string literal (detected at line 1) (, line 1)")
- try:
- eval("""'''this is a test""")
- except SyntaxError as msg:
- self.assertEqual(str(msg), expect)
- self.assertEqual(msg.offset, 1)
- else:
- raise support.TestFailed
+ expect = ("unterminated triple-quoted string literal (detected at line 3) (, line 1)")
+ with self.assertRaises(SyntaxError) as cm:
+ eval("""ä = '''thîs is \na \ntest""")
+ self.assertEqual(str(cm.exception), expect)
+ self.assertEqual(cm.exception.text, "ä = '''thîs is ")
+ self.assertEqual(cm.exception.offset, 5)
+
+ with self.assertRaises(SyntaxError) as cm:
+ eval("""ä = '''thîs is \na \ntest""".encode())
+ self.assertEqual(str(cm.exception), expect)
+ self.assertEqual(cm.exception.text, "ä = '''thîs is ")
+ self.assertEqual(cm.exception.offset, 5)
+
+ with self.assertRaises(SyntaxError) as cm:
+ eval(BOM_UTF8 + """ä = '''thîs is \na \ntest""".encode())
+ self.assertEqual(str(cm.exception), expect)
+ self.assertEqual(cm.exception.text, "ä = '''thîs is ")
+ self.assertEqual(cm.exception.offset, 5)
+
+ with self.assertRaises(SyntaxError) as cm:
+ eval("""# coding: latin1\nä = '''thîs is \na \ntest""".encode('latin1'))
+ self.assertEqual(str(cm.exception), "unterminated triple-quoted string literal (detected at line 4) (, line 2)")
+ self.assertEqual(cm.exception.text, "ä = '''thîs is ")
+ self.assertEqual(cm.exception.offset, 5)
def test_EOFS_with_file(self):
expect = ("(, line 1)")
with os_helper.temp_dir() as temp_dir:
- file_name = script_helper.make_script(temp_dir, 'foo', """'''this is \na \ntest""")
- rc, out, err = script_helper.assert_python_failure(file_name)
- self.assertIn(b'unterminated triple-quoted string literal (detected at line 3)', err)
+ file_name = script_helper.make_script(temp_dir, 'foo',
+ """ä = '''thîs is \na \ntest""")
+ rc, out, err = script_helper.assert_python_failure('-X', 'utf8', file_name)
+ err = err.decode().splitlines()
+ self.assertEqual(err[-3:], [
+ " ä = '''thîs is ",
+ ' ^',
+ 'SyntaxError: unterminated triple-quoted string literal (detected at line 3)'])
+
+ file_name = script_helper.make_script(temp_dir, 'foo',
+ """ä = '''thîs is \na \ntest""".encode())
+ rc, out, err = script_helper.assert_python_failure('-X', 'utf8', file_name)
+ err = err.decode().splitlines()
+ self.assertEqual(err[-3:], [
+ " ä = '''thîs is ",
+ ' ^',
+ 'SyntaxError: unterminated triple-quoted string literal (detected at line 3)'])
+
+ file_name = script_helper.make_script(temp_dir, 'foo',
+ BOM_UTF8 + """ä = '''thîs is \na \ntest""".encode())
+ rc, out, err = script_helper.assert_python_failure('-X', 'utf8', file_name)
+ err = err.decode().splitlines()
+ self.assertEqual(err[-3:], [
+ " ä = '''thîs is ",
+ ' ^',
+ 'SyntaxError: unterminated triple-quoted string literal (detected at line 3)'])
+
+ file_name = script_helper.make_script(temp_dir, 'foo',
+ """# coding: latin1\nä = '''thîs is \na \ntest""".encode('latin1'))
+ rc, out, err = script_helper.assert_python_failure('-X', 'utf8', file_name)
+ err = err.decode().splitlines()
+ self.assertEqual(err[-3:], [
+ " ä = '''thîs is ",
+ ' ^',
+ 'SyntaxError: unterminated triple-quoted string literal (detected at line 4)'])
@warnings_helper.ignore_warnings(category=SyntaxWarning)
def test_eof_with_line_continuation(self):
expect = "unexpected EOF while parsing (, line 1)"
- try:
+ with self.assertRaises(SyntaxError) as cm:
compile('"\\Xhh" \\', '', 'exec')
- except SyntaxError as msg:
- self.assertEqual(str(msg), expect)
- else:
- raise support.TestFailed
+ self.assertEqual(str(cm.exception), expect)
def test_line_continuation_EOF(self):
"""A continuation at the end of input must be an error; bpo2180."""
expect = 'unexpected EOF while parsing (, line 1)'
- with self.assertRaises(SyntaxError) as excinfo:
- exec('x = 5\\')
- self.assertEqual(str(excinfo.exception), expect)
- with self.assertRaises(SyntaxError) as excinfo:
+ with self.assertRaises(SyntaxError) as cm:
+ exec('ä = 5\\')
+ self.assertEqual(str(cm.exception), expect)
+ self.assertEqual(cm.exception.text, 'ä = 5\\\n')
+ self.assertEqual(cm.exception.offset, 7)
+
+ with self.assertRaises(SyntaxError) as cm:
+ exec('ä = 5\\'.encode())
+ self.assertEqual(str(cm.exception), expect)
+ self.assertEqual(cm.exception.text, 'ä = 5\\\n')
+ self.assertEqual(cm.exception.offset, 7)
+
+ with self.assertRaises(SyntaxError) as cm:
+ exec('# coding:latin1\nä = 5\\'.encode('latin1'))
+ self.assertEqual(str(cm.exception),
+ 'unexpected EOF while parsing (, line 2)')
+ self.assertEqual(cm.exception.text, 'ä = 5\\\n')
+ self.assertEqual(cm.exception.offset, 7)
+
+ with self.assertRaises(SyntaxError) as cm:
+ exec(BOM_UTF8 + 'ä = 5\\'.encode())
+ self.assertEqual(str(cm.exception), expect)
+ self.assertEqual(cm.exception.text, 'ä = 5\\\n')
+ self.assertEqual(cm.exception.offset, 7)
+
+ with self.assertRaises(SyntaxError) as cm:
exec('\\')
- self.assertEqual(str(excinfo.exception), expect)
+ self.assertEqual(str(cm.exception), expect)
@unittest.skipIf(not sys.executable, "sys.executable required")
def test_line_continuation_EOF_from_file_bpo2180(self):
"""Ensure tok_nextc() does not add too many ending newlines."""
with os_helper.temp_dir() as temp_dir:
file_name = script_helper.make_script(temp_dir, 'foo', '\\')
- rc, out, err = script_helper.assert_python_failure(file_name)
- self.assertIn(b'unexpected EOF while parsing', err)
- self.assertIn(b'line 1', err)
- self.assertIn(b'\\', err)
-
- file_name = script_helper.make_script(temp_dir, 'foo', 'y = 6\\')
- rc, out, err = script_helper.assert_python_failure(file_name)
- self.assertIn(b'unexpected EOF while parsing', err)
- self.assertIn(b'line 1', err)
- self.assertIn(b'y = 6\\', err)
+ rc, out, err = script_helper.assert_python_failure('-X', 'utf8', file_name)
+ err = err.decode().splitlines()
+ self.assertEqual(err[-2:], [
+ ' \\',
+ 'SyntaxError: unexpected EOF while parsing'])
+ self.assertEqual(err[-3][-8:], ', line 1', err)
+
+ file_name = script_helper.make_script(temp_dir, 'foo', 'ä = 6\\')
+ rc, out, err = script_helper.assert_python_failure('-X', 'utf8', file_name)
+ err = err.decode().splitlines()
+ self.assertEqual(err[-3:], [
+ ' ä = 6\\',
+ ' ^',
+ 'SyntaxError: unexpected EOF while parsing'])
+ self.assertEqual(err[-4][-8:], ', line 1', err)
+
+ file_name = script_helper.make_script(temp_dir, 'foo',
+ '# coding:latin1\n'
+ 'ä = 7\\'.encode('latin1'))
+ rc, out, err = script_helper.assert_python_failure('-X', 'utf8', file_name)
+ err = err.decode().splitlines()
+ self.assertEqual(err[-3:], [
+ ' ä = 7\\',
+ ' ^',
+ 'SyntaxError: unexpected EOF while parsing'])
+ self.assertEqual(err[-4][-8:], ', line 2', err)
+
+ file_name = script_helper.make_script(temp_dir, 'foo',
+ BOM_UTF8 + 'ä = 8\\'.encode())
+ rc, out, err = script_helper.assert_python_failure('-X', 'utf8', file_name)
+ err = err.decode().splitlines()
+ self.assertEqual(err[-3:], [
+ ' ä = 8\\',
+ ' ^',
+ 'SyntaxError: unexpected EOF while parsing'])
+ self.assertEqual(err[-4][-8:], ', line 1', err)
+
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py
index e4f2e3a97b8bb8..ba858c49400911 100644
--- a/Lib/test/test_exceptions.py
+++ b/Lib/test/test_exceptions.py
@@ -7,6 +7,7 @@
import pickle
import weakref
import errno
+from codecs import BOM_UTF8
from textwrap import dedent
from test.support import (captured_stderr, check_impl_detail,
@@ -2011,16 +2012,20 @@ def test_copy_pickle(self):
self.assertEqual(exc.path, orig.path)
+def run_script(source):
+ if isinstance(source, str):
+ with open(TESTFN, 'w', encoding='utf-8') as testfile:
+ testfile.write(dedent(source))
+ else:
+ with open(TESTFN, 'wb') as testfile:
+ testfile.write(source)
+ _rc, _out, err = script_helper.assert_python_failure('-Wd', '-X', 'utf8', TESTFN)
+ return err.decode('utf-8').splitlines()
+
class AssertionErrorTests(unittest.TestCase):
def tearDown(self):
unlink(TESTFN)
- def write_source(self, source):
- with open(TESTFN, 'w') as testfile:
- testfile.write(dedent(source))
- _rc, _out, err = script_helper.assert_python_failure('-Wd', '-X', 'utf8', TESTFN)
- return err.decode('utf-8').splitlines()
-
@force_not_colorized
def test_assertion_error_location(self):
cases = [
@@ -2052,11 +2057,32 @@ def test_assertion_error_location(self):
'AssertionError',
],
),
- ('assert 1 > 2, "message"',
+ ('assert 1 > 2, "messäge"',
+ [
+ ' assert 1 > 2, "messäge"',
+ ' ^^^^^',
+ 'AssertionError: messäge',
+ ],
+ ),
+ ('assert 1 > 2, "messäge"'.encode(),
[
- ' assert 1 > 2, "message"',
+ ' assert 1 > 2, "messäge"',
' ^^^^^',
- 'AssertionError: message',
+ 'AssertionError: messäge',
+ ],
+ ),
+ ('# coding: latin1\nassert 1 > 2, "messäge"'.encode('latin1'),
+ [
+ ' assert 1 > 2, "messäge"',
+ ' ^^^^^',
+ 'AssertionError: messäge',
+ ],
+ ),
+ (BOM_UTF8 + 'assert 1 > 2, "messäge"'.encode(),
+ [
+ ' assert 1 > 2, "messäge"',
+ ' ^^^^^',
+ 'AssertionError: messäge',
],
),
@@ -2094,8 +2120,8 @@ def test_assertion_error_location(self):
),
]
for source, expected in cases:
- with self.subTest(source):
- result = self.write_source(source)
+ with self.subTest(source=source):
+ result = run_script(source)
self.assertEqual(result[-3:], expected)
@force_not_colorized
@@ -2125,12 +2151,14 @@ def test_multiline_not_highlighted(self):
),
]
for source, expected in cases:
- with self.subTest(source):
- result = self.write_source(source)
+ with self.subTest(source=source):
+ result = run_script(source)
self.assertEqual(result[-len(expected):], expected)
class SyntaxErrorTests(unittest.TestCase):
+ maxDiff = None
+
@force_not_colorized
def test_range_of_offsets(self):
cases = [
@@ -2223,45 +2251,106 @@ def test_range_of_offsets(self):
the_exception = exc
def test_encodings(self):
+ self.addCleanup(unlink, TESTFN)
source = (
'# -*- coding: cp437 -*-\n'
'"¢¢¢¢¢¢" + f(4, x for x in range(1))\n'
)
- try:
- with open(TESTFN, 'w', encoding='cp437') as testfile:
- testfile.write(source)
- rc, out, err = script_helper.assert_python_failure('-Wd', '-X', 'utf8', TESTFN)
- err = err.decode('utf-8').splitlines()
-
- self.assertEqual(err[-3], ' "¢¢¢¢¢¢" + f(4, x for x in range(1))')
- self.assertEqual(err[-2], ' ^^^^^^^^^^^^^^^^^^^')
- finally:
- unlink(TESTFN)
+ err = run_script(source.encode('cp437'))
+ self.assertEqual(err[-3], ' "¢¢¢¢¢¢" + f(4, x for x in range(1))')
+ self.assertEqual(err[-2], ' ^^^^^^^^^^^^^^^^^^^')
# Check backwards tokenizer errors
source = '# -*- coding: ascii -*-\n\n(\n'
- try:
- with open(TESTFN, 'w', encoding='ascii') as testfile:
- testfile.write(source)
- rc, out, err = script_helper.assert_python_failure('-Wd', '-X', 'utf8', TESTFN)
- err = err.decode('utf-8').splitlines()
-
- self.assertEqual(err[-3], ' (')
- self.assertEqual(err[-2], ' ^')
- finally:
- unlink(TESTFN)
+ err = run_script(source)
+ self.assertEqual(err[-3], ' (')
+ self.assertEqual(err[-2], ' ^')
def test_non_utf8(self):
# Check non utf-8 characters
- try:
- with open(TESTFN, 'bw') as testfile:
- testfile.write(b"\x89")
- rc, out, err = script_helper.assert_python_failure('-Wd', '-X', 'utf8', TESTFN)
- err = err.decode('utf-8').splitlines()
+ self.addCleanup(unlink, TESTFN)
+ err = run_script(b"\x89")
+ self.assertIn("SyntaxError: Non-UTF-8 code starting with '\\x89' in file", err[-1])
- self.assertIn("SyntaxError: Non-UTF-8 code starting with '\\x89' in file", err[-1])
- finally:
- unlink(TESTFN)
+ def test_string_source(self):
+ def try_compile(source):
+ with self.assertRaises(SyntaxError) as cm:
+ compile(source, '', 'exec')
+ return cm.exception
+
+ exc = try_compile('return "ä"')
+ self.assertEqual(str(exc), "'return' outside function (, line 1)")
+ self.assertIsNone(exc.text)
+ self.assertEqual(exc.offset, 1)
+ self.assertEqual(exc.end_offset, 12)
+
+ exc = try_compile('return "ä"'.encode())
+ self.assertEqual(str(exc), "'return' outside function (, line 1)")
+ self.assertIsNone(exc.text)
+ self.assertEqual(exc.offset, 1)
+ self.assertEqual(exc.end_offset, 12)
+
+ exc = try_compile(BOM_UTF8 + 'return "ä"'.encode())
+ self.assertEqual(str(exc), "'return' outside function (, line 1)")
+ self.assertIsNone(exc.text)
+ self.assertEqual(exc.offset, 1)
+ self.assertEqual(exc.end_offset, 12)
+
+ exc = try_compile('# coding: latin1\nreturn "ä"'.encode('latin1'))
+ self.assertEqual(str(exc), "'return' outside function (, line 2)")
+ self.assertIsNone(exc.text)
+ self.assertEqual(exc.offset, 1)
+ self.assertEqual(exc.end_offset, 12)
+
+ exc = try_compile('return "ä" #' + 'ä'*1000)
+ self.assertEqual(str(exc), "'return' outside function (, line 1)")
+ self.assertIsNone(exc.text)
+ self.assertEqual(exc.offset, 1)
+ self.assertEqual(exc.end_offset, 12)
+
+ exc = try_compile('return "ä" # ' + 'ä'*1000)
+ self.assertEqual(str(exc), "'return' outside function (, line 1)")
+ self.assertIsNone(exc.text)
+ self.assertEqual(exc.offset, 1)
+ self.assertEqual(exc.end_offset, 12)
+
+ def test_file_source(self):
+ self.addCleanup(unlink, TESTFN)
+ err = run_script('return "ä"')
+ self.assertEqual(err[-3:], [
+ ' return "ä"',
+ ' ^^^^^^^^^^',
+ "SyntaxError: 'return' outside function"])
+
+ err = run_script('return "ä"'.encode())
+ self.assertEqual(err[-3:], [
+ ' return "ä"',
+ ' ^^^^^^^^^^',
+ "SyntaxError: 'return' outside function"])
+
+ err = run_script(BOM_UTF8 + 'return "ä"'.encode())
+ self.assertEqual(err[-3:], [
+ ' return "ä"',
+ ' ^^^^^^^^^^',
+ "SyntaxError: 'return' outside function"])
+
+ err = run_script('# coding: latin1\nreturn "ä"'.encode('latin1'))
+ self.assertEqual(err[-3:], [
+ ' return "ä"',
+ ' ^^^^^^^^^^',
+ "SyntaxError: 'return' outside function"])
+
+ err = run_script('return "ä" #' + 'ä'*1000)
+ self.assertEqual(err[-2:], [
+ ' ^^^^^^^^^^^',
+ "SyntaxError: 'return' outside function"])
+ self.assertEqual(err[-3][:100], ' return "ä" #' + 'ä'*84)
+
+ err = run_script('return "ä" # ' + 'ä'*1000)
+ self.assertEqual(err[-2:], [
+ ' ^^^^^^^^^^^',
+ "SyntaxError: 'return' outside function"])
+ self.assertEqual(err[-3][:100], ' return "ä" # ' + 'ä'*83)
def test_attributes_new_constructor(self):
args = ("bad.py", 1, 2, "abcdefg", 1, 100)
diff --git a/Lib/test/test_free_threading/test_list.py b/Lib/test/test_free_threading/test_list.py
index 6ad806d67a80ed..c6b58fcd86f449 100644
--- a/Lib/test/test_free_threading/test_list.py
+++ b/Lib/test/test_free_threading/test_list.py
@@ -3,6 +3,7 @@
from threading import Thread
from unittest import TestCase
+from test import support
from test.support import threading_helper
@@ -13,6 +14,7 @@ def __init__(self, v):
@threading_helper.requires_working_threading()
class TestList(TestCase):
+ @support.requires_resource('cpu')
def test_racing_iter_append(self):
l = []
@@ -42,6 +44,7 @@ def reader_func():
for reader in readers:
reader.join()
+ @support.requires_resource('cpu')
def test_racing_iter_extend(self):
iters = [
lambda x: [x],
diff --git a/Lib/test/test_free_threading/test_monitoring.py b/Lib/test/test_free_threading/test_monitoring.py
index 78303f4206decc..be582455d118ac 100644
--- a/Lib/test/test_free_threading/test_monitoring.py
+++ b/Lib/test/test_free_threading/test_monitoring.py
@@ -7,6 +7,7 @@
import weakref
from sys import monitoring
+from test import support
from test.support import threading_helper
from threading import Thread, _PyRLock
from unittest import TestCase
@@ -43,6 +44,7 @@ def after_test(self):
"""Runs once after the test is done"""
pass
+ @support.requires_resource('cpu')
def test_instrumentation(self):
# Setup a bunch of functions which will need instrumentation...
funcs = []
@@ -218,6 +220,7 @@ def test_register_callback(self):
for ref in self.refs:
self.assertEqual(ref(), None)
+ @support.requires_resource('cpu')
def test_set_local_trace_opcodes(self):
def trace(frame, event, arg):
frame.f_trace_opcodes = True
diff --git a/Lib/test/test_free_threading/test_type.py b/Lib/test/test_free_threading/test_type.py
index 649676db9c08a5..977bfd2c7fd2f7 100644
--- a/Lib/test/test_free_threading/test_type.py
+++ b/Lib/test/test_free_threading/test_type.py
@@ -5,6 +5,7 @@
from threading import Thread
from unittest import TestCase
+from test import support
from test.support import threading_helper
@@ -96,6 +97,7 @@ def reader_func():
self.run_one(writer_func, reader_func)
+ @support.requires_resource('cpu')
def test___class___modification(self):
class Foo:
pass
diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py
index 5d20e3c30bcf10..214e53dde64bbf 100644
--- a/Lib/test/test_generated_cases.py
+++ b/Lib/test/test_generated_cases.py
@@ -523,6 +523,36 @@ def test_pseudo_instruction_with_flags(self):
"""
self.run_cases_test(input, output)
+ def test_pseudo_instruction_as_sequence(self):
+ input = """
+ pseudo(OP, (in -- out1, out2)) = [
+ OP1, OP2
+ ];
+
+ inst(OP1, (--)) {
+ }
+
+ inst(OP2, (--)) {
+ }
+ """
+ output = """
+ TARGET(OP1) {
+ frame->instr_ptr = next_instr;
+ next_instr += 1;
+ INSTRUCTION_STATS(OP1);
+ DISPATCH();
+ }
+
+ TARGET(OP2) {
+ frame->instr_ptr = next_instr;
+ next_instr += 1;
+ INSTRUCTION_STATS(OP2);
+ DISPATCH();
+ }
+ """
+ self.run_cases_test(input, output)
+
+
def test_array_input(self):
input = """
inst(OP, (below, values[oparg*2], above --)) {
diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py
index 3d89d69955bb07..5d0d02480b3929 100644
--- a/Lib/test/test_import/__init__.py
+++ b/Lib/test/test_import/__init__.py
@@ -111,6 +111,24 @@ def require_frozen(module, *, skip=True):
def require_pure_python(module, *, skip=False):
_require_loader(module, SourceFileLoader, skip)
+def create_extension_loader(modname, filename):
+ # Apple extensions must be distributed as frameworks. This requires
+ # a specialist loader.
+ if is_apple_mobile:
+ return AppleFrameworkLoader(modname, filename)
+ else:
+ return ExtensionFileLoader(modname, filename)
+
+def import_extension_from_file(modname, filename, *, put_in_sys_modules=True):
+ loader = create_extension_loader(modname, filename)
+ spec = importlib.util.spec_from_loader(modname, loader)
+ module = importlib.util.module_from_spec(spec)
+ loader.exec_module(module)
+ if put_in_sys_modules:
+ sys.modules[modname] = module
+ return module
+
+
def remove_files(name):
for f in (name + ".py",
name + ".pyc",
@@ -1894,6 +1912,37 @@ def test_absolute_circular_submodule(self):
str(cm.exception),
)
+ @requires_singlephase_init
+ @unittest.skipIf(_testsinglephase is None, "test requires _testsinglephase module")
+ def test_singlephase_circular(self):
+ """Regression test for gh-123950
+
+ Import a single-phase-init module that imports itself
+ from the PyInit_* function (before it's added to sys.modules).
+ Manages its own cache (which is `static`, and so incompatible
+ with multiple interpreters or interpreter reset).
+ """
+ name = '_testsinglephase_circular'
+ helper_name = 'test.test_import.data.circular_imports.singlephase'
+ with uncache(name, helper_name):
+ filename = _testsinglephase.__file__
+ # We don't put the module in sys.modules: that the *inner*
+ # import should do that.
+ mod = import_extension_from_file(name, filename,
+ put_in_sys_modules=False)
+
+ self.assertEqual(mod.helper_mod_name, helper_name)
+ self.assertIn(name, sys.modules)
+ self.assertIn(helper_name, sys.modules)
+
+ self.assertIn(name, sys.modules)
+ self.assertIn(helper_name, sys.modules)
+ self.assertNotIn(name, sys.modules)
+ self.assertNotIn(helper_name, sys.modules)
+ self.assertIs(mod.clear_static_var(), mod)
+ _testinternalcapi.clear_extension('_testsinglephase_circular',
+ mod.__spec__.origin)
+
def test_unwritable_module(self):
self.addCleanup(unload, "test.test_import.data.unwritable")
self.addCleanup(unload, "test.test_import.data.unwritable.x")
@@ -1933,14 +1982,6 @@ def pipe(self):
os.set_blocking(r, False)
return (r, w)
- def create_extension_loader(self, modname, filename):
- # Apple extensions must be distributed as frameworks. This requires
- # a specialist loader.
- if is_apple_mobile:
- return AppleFrameworkLoader(modname, filename)
- else:
- return ExtensionFileLoader(modname, filename)
-
def import_script(self, name, fd, filename=None, check_override=None):
override_text = ''
if check_override is not None:
@@ -2157,11 +2198,7 @@ def test_multi_init_extension_compat(self):
def test_multi_init_extension_non_isolated_compat(self):
modname = '_test_non_isolated'
filename = _testmultiphase.__file__
- loader = self.create_extension_loader(modname, filename)
- spec = importlib.util.spec_from_loader(modname, loader)
- module = importlib.util.module_from_spec(spec)
- loader.exec_module(module)
- sys.modules[modname] = module
+ module = import_extension_from_file(modname, filename)
require_extension(module)
with self.subTest(f'{modname}: isolated'):
@@ -2176,11 +2213,7 @@ def test_multi_init_extension_non_isolated_compat(self):
def test_multi_init_extension_per_interpreter_gil_compat(self):
modname = '_test_shared_gil_only'
filename = _testmultiphase.__file__
- loader = self.create_extension_loader(modname, filename)
- spec = importlib.util.spec_from_loader(modname, loader)
- module = importlib.util.module_from_spec(spec)
- loader.exec_module(module)
- sys.modules[modname] = module
+ module = import_extension_from_file(modname, filename)
require_extension(module)
with self.subTest(f'{modname}: isolated, strict'):
diff --git a/Lib/test/test_import/data/circular_imports/singlephase.py b/Lib/test/test_import/data/circular_imports/singlephase.py
new file mode 100644
index 00000000000000..05618bc72f9327
--- /dev/null
+++ b/Lib/test/test_import/data/circular_imports/singlephase.py
@@ -0,0 +1,13 @@
+"""Circular import involving a single-phase-init extension.
+
+This module is imported from the _testsinglephase_circular module from
+_testsinglephase, and imports that module again.
+"""
+
+import importlib
+import _testsinglephase
+from test.test_import import import_extension_from_file
+
+name = '_testsinglephase_circular'
+filename = _testsinglephase.__file__
+mod = import_extension_from_file(name, filename)
diff --git a/Lib/test/test_importlib/resources/test_files.py b/Lib/test/test_importlib/resources/test_files.py
index 3cdbee302c5e75..933894dce2c045 100644
--- a/Lib/test/test_importlib/resources/test_files.py
+++ b/Lib/test/test_importlib/resources/test_files.py
@@ -134,18 +134,17 @@ def test_implicit_files_submodule(self):
def _compile_importlib(self):
"""
Make a compiled-only copy of the importlib resources package.
+
+ Currently only code is copied, as importlib resources doesn't itself
+ have any resources.
"""
bin_site = self.fixtures.enter_context(os_helper.temp_dir())
c_resources = pathlib.Path(bin_site, 'c_resources')
sources = pathlib.Path(resources.__file__).parent
- shutil.copytree(sources, c_resources, ignore=lambda *_: ['__pycache__'])
-
- for dirpath, _, filenames in os.walk(c_resources):
- for filename in filenames:
- source_path = pathlib.Path(dirpath) / filename
- cfile = source_path.with_suffix('.pyc')
- py_compile.compile(source_path, cfile)
- pathlib.Path.unlink(source_path)
+
+ for source_path in sources.glob('**/*.py'):
+ c_path = c_resources.joinpath(source_path.relative_to(sources)).with_suffix('.pyc')
+ py_compile.compile(source_path, c_path)
self.fixtures.enter_context(import_helper.DirsOnSysPath(bin_site))
def test_implicit_files_with_compiled_importlib(self):
diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py
index 1ca3edac8c8dc9..aa1b8268592ff7 100644
--- a/Lib/test/test_io.py
+++ b/Lib/test/test_io.py
@@ -639,11 +639,9 @@ def test_large_file_ops(self):
def test_with_open(self):
for bufsize in (0, 100):
- f = None
with self.open(os_helper.TESTFN, "wb", bufsize) as f:
f.write(b"xxx")
self.assertEqual(f.closed, True)
- f = None
try:
with self.open(os_helper.TESTFN, "wb", bufsize) as f:
1/0
diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py
index aad779dd9b25e7..b1ac2b94f41b38 100644
--- a/Lib/test/test_ipaddress.py
+++ b/Lib/test/test_ipaddress.py
@@ -2433,6 +2433,8 @@ def testReservedIpv6(self):
self.assertTrue(ipaddress.ip_address('2001:30::').is_global)
self.assertFalse(ipaddress.ip_address('2001:40::').is_global)
self.assertFalse(ipaddress.ip_address('2002::').is_global)
+ # gh-124217: conform with RFC 9637
+ self.assertFalse(ipaddress.ip_address('3fff::').is_global)
# some generic IETF reserved addresses
self.assertEqual(True, ipaddress.ip_address('100::').is_reserved)
diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py
index cf452960729aba..a3eebc97ada23b 100644
--- a/Lib/test/test_math.py
+++ b/Lib/test/test_math.py
@@ -812,11 +812,13 @@ def testHypot(self):
# Test allowable types (those with __float__)
self.assertEqual(hypot(12.0, 5.0), 13.0)
self.assertEqual(hypot(12, 5), 13)
- self.assertEqual(hypot(1, -1), math.sqrt(2))
- self.assertEqual(hypot(1, FloatLike(-1.)), math.sqrt(2))
+ self.assertEqual(hypot(0.75, -1), 1.25)
+ self.assertEqual(hypot(-1, 0.75), 1.25)
+ self.assertEqual(hypot(0.75, FloatLike(-1.)), 1.25)
+ self.assertEqual(hypot(FloatLike(-1.), 0.75), 1.25)
self.assertEqual(hypot(Decimal(12), Decimal(5)), 13)
self.assertEqual(hypot(Fraction(12, 32), Fraction(5, 32)), Fraction(13, 32))
- self.assertEqual(hypot(bool(1), bool(0), bool(1), bool(1)), math.sqrt(3))
+ self.assertEqual(hypot(True, False, True, True, True), 2.0)
# Test corner cases
self.assertEqual(hypot(0.0, 0.0), 0.0) # Max input is zero
@@ -972,9 +974,9 @@ def testDist(self):
self.assertEqual(dist((D(14), D(1)), (D(2), D(-4))), D(13))
self.assertEqual(dist((F(14, 32), F(1, 32)), (F(2, 32), F(-4, 32))),
F(13, 32))
- self.assertEqual(dist((True, True, False, True, False),
- (True, False, True, True, False)),
- sqrt(2.0))
+ self.assertEqual(dist((True, True, False, False, True, True),
+ (True, False, True, False, False, False)),
+ 2.0)
# Test corner cases
self.assertEqual(dist((13.25, 12.5, -3.25),
diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py
index a1cf5384ada5b5..b2a299ed172967 100644
--- a/Lib/test/test_mmap.py
+++ b/Lib/test/test_mmap.py
@@ -1,5 +1,6 @@
from test.support import (
requires, _2G, _4G, gc_collect, cpython_only, is_emscripten, is_apple,
+ in_systemd_nspawn_sync_suppressed,
)
from test.support.import_helper import import_module
from test.support.os_helper import TESTFN, unlink
@@ -839,7 +840,8 @@ def test_flush_return_value(self):
mm.write(b'python')
result = mm.flush()
self.assertIsNone(result)
- if sys.platform.startswith(('linux', 'android')):
+ if (sys.platform.startswith(('linux', 'android'))
+ and not in_systemd_nspawn_sync_suppressed()):
# 'offset' must be a multiple of mmap.PAGESIZE on Linux.
# See bpo-34754 for details.
self.assertRaises(OSError, mm.flush, 1, len(b'python'))
diff --git a/Lib/test/test_ordered_dict.py b/Lib/test/test_ordered_dict.py
index 06a0e81227188c..a9b6a84996e659 100644
--- a/Lib/test/test_ordered_dict.py
+++ b/Lib/test/test_ordered_dict.py
@@ -2,7 +2,9 @@
import contextlib
import copy
import gc
+import operator
import pickle
+import re
from random import randrange, shuffle
import struct
import sys
@@ -740,11 +742,44 @@ def test_ordered_dict_items_result_gc(self):
# when it's mutated and returned from __next__:
self.assertTrue(gc.is_tracked(next(it)))
+
+class _TriggerSideEffectOnEqual:
+ count = 0 # number of calls to __eq__
+ trigger = 1 # count value when to trigger side effect
+
+ def __eq__(self, other):
+ if self.__class__.count == self.__class__.trigger:
+ self.side_effect()
+ self.__class__.count += 1
+ return True
+
+ def __hash__(self):
+ # all instances represent the same key
+ return -1
+
+ def side_effect(self):
+ raise NotImplementedError
+
class PurePythonOrderedDictTests(OrderedDictTests, unittest.TestCase):
module = py_coll
OrderedDict = py_coll.OrderedDict
+ def test_issue119004_attribute_error(self):
+ class Key(_TriggerSideEffectOnEqual):
+ def side_effect(self):
+ del dict1[TODEL]
+
+ TODEL = Key()
+ dict1 = self.OrderedDict(dict.fromkeys((0, TODEL, 4.2)))
+ dict2 = self.OrderedDict(dict.fromkeys((0, Key(), 4.2)))
+ # This causes an AttributeError due to the linked list being changed
+ msg = re.escape("'NoneType' object has no attribute 'key'")
+ self.assertRaisesRegex(AttributeError, msg, operator.eq, dict1, dict2)
+ self.assertEqual(Key.count, 2)
+ self.assertDictEqual(dict1, dict.fromkeys((0, 4.2)))
+ self.assertDictEqual(dict2, dict.fromkeys((0, Key(), 4.2)))
+
class CPythonBuiltinDictTests(unittest.TestCase):
"""Builtin dict preserves insertion order.
@@ -765,8 +800,85 @@ class CPythonBuiltinDictTests(unittest.TestCase):
del method
+class CPythonOrderedDictSideEffects:
+
+ def check_runtime_error_issue119004(self, dict1, dict2):
+ msg = re.escape("OrderedDict mutated during iteration")
+ self.assertRaisesRegex(RuntimeError, msg, operator.eq, dict1, dict2)
+
+ def test_issue119004_change_size_by_clear(self):
+ class Key(_TriggerSideEffectOnEqual):
+ def side_effect(self):
+ dict1.clear()
+
+ dict1 = self.OrderedDict(dict.fromkeys((0, Key(), 4.2)))
+ dict2 = self.OrderedDict(dict.fromkeys((0, Key(), 4.2)))
+ self.check_runtime_error_issue119004(dict1, dict2)
+ self.assertEqual(Key.count, 2)
+ self.assertDictEqual(dict1, {})
+ self.assertDictEqual(dict2, dict.fromkeys((0, Key(), 4.2)))
+
+ def test_issue119004_change_size_by_delete_key(self):
+ class Key(_TriggerSideEffectOnEqual):
+ def side_effect(self):
+ del dict1[TODEL]
+
+ TODEL = Key()
+ dict1 = self.OrderedDict(dict.fromkeys((0, TODEL, 4.2)))
+ dict2 = self.OrderedDict(dict.fromkeys((0, Key(), 4.2)))
+ self.check_runtime_error_issue119004(dict1, dict2)
+ self.assertEqual(Key.count, 2)
+ self.assertDictEqual(dict1, dict.fromkeys((0, 4.2)))
+ self.assertDictEqual(dict2, dict.fromkeys((0, Key(), 4.2)))
+
+ def test_issue119004_change_linked_list_by_clear(self):
+ class Key(_TriggerSideEffectOnEqual):
+ def side_effect(self):
+ dict1.clear()
+ dict1['a'] = dict1['b'] = 'c'
+
+ dict1 = self.OrderedDict(dict.fromkeys((0, Key(), 4.2)))
+ dict2 = self.OrderedDict(dict.fromkeys((0, Key(), 4.2)))
+ self.check_runtime_error_issue119004(dict1, dict2)
+ self.assertEqual(Key.count, 2)
+ self.assertDictEqual(dict1, dict.fromkeys(('a', 'b'), 'c'))
+ self.assertDictEqual(dict2, dict.fromkeys((0, Key(), 4.2)))
+
+ def test_issue119004_change_linked_list_by_delete_key(self):
+ class Key(_TriggerSideEffectOnEqual):
+ def side_effect(self):
+ del dict1[TODEL]
+ dict1['a'] = 'c'
+
+ TODEL = Key()
+ dict1 = self.OrderedDict(dict.fromkeys((0, TODEL, 4.2)))
+ dict2 = self.OrderedDict(dict.fromkeys((0, Key(), 4.2)))
+ self.check_runtime_error_issue119004(dict1, dict2)
+ self.assertEqual(Key.count, 2)
+ self.assertDictEqual(dict1, {0: None, 'a': 'c', 4.2: None})
+ self.assertDictEqual(dict2, dict.fromkeys((0, Key(), 4.2)))
+
+ def test_issue119004_change_size_by_delete_key_in_dict_eq(self):
+ class Key(_TriggerSideEffectOnEqual):
+ trigger = 0
+ def side_effect(self):
+ del dict1[TODEL]
+
+ TODEL = Key()
+ dict1 = self.OrderedDict(dict.fromkeys((0, TODEL, 4.2)))
+ dict2 = self.OrderedDict(dict.fromkeys((0, Key(), 4.2)))
+ self.assertEqual(Key.count, 0)
+ # the side effect is in dict.__eq__ and modifies the length
+ self.assertNotEqual(dict1, dict2)
+ self.assertEqual(Key.count, 2)
+ self.assertDictEqual(dict1, dict.fromkeys((0, 4.2)))
+ self.assertDictEqual(dict2, dict.fromkeys((0, Key(), 4.2)))
+
+
@unittest.skipUnless(c_coll, 'requires the C version of the collections module')
-class CPythonOrderedDictTests(OrderedDictTests, unittest.TestCase):
+class CPythonOrderedDictTests(OrderedDictTests,
+ CPythonOrderedDictSideEffects,
+ unittest.TestCase):
module = c_coll
OrderedDict = c_coll.OrderedDict
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
index 1e570c757fccbc..307f0f11ddc33f 100644
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -2351,9 +2351,13 @@ def test_chmod(self):
@unittest.skipIf(support.is_wasi, "Cannot create invalid FD on WASI.")
class TestInvalidFD(unittest.TestCase):
- singles = ["fchdir", "dup", "fdatasync", "fstat",
- "fstatvfs", "fsync", "tcgetpgrp", "ttyname"]
- singles_fildes = {"fchdir", "fdatasync", "fsync"}
+ singles = ["fchdir", "dup", "fstat", "fstatvfs", "tcgetpgrp", "ttyname"]
+ singles_fildes = {"fchdir"}
+ # systemd-nspawn --suppress-sync=true does not verify fd passed
+ # fdatasync() and fsync(), and always returns success
+ if not support.in_systemd_nspawn_sync_suppressed():
+ singles += ["fdatasync", "fsync"]
+ singles_fildes |= {"fdatasync", "fsync"}
#singles.append("close")
#We omit close because it doesn't raise an exception on some platforms
def get_single(f):
@@ -3173,7 +3177,8 @@ class Win32NtTests(unittest.TestCase):
def test_getfinalpathname_handles(self):
nt = import_helper.import_module('nt')
ctypes = import_helper.import_module('ctypes')
- import ctypes.wintypes
+ # Ruff false positive -- it thinks we're redefining `ctypes` here
+ import ctypes.wintypes # noqa: F811
kernel = ctypes.WinDLL('Kernel32.dll', use_last_error=True)
kernel.GetCurrentProcess.restype = ctypes.wintypes.HANDLE
diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py
index db7d1b1e9cd935..84c0e1073a1054 100644
--- a/Lib/test/test_pdb.py
+++ b/Lib/test/test_pdb.py
@@ -901,6 +901,38 @@ def test_pdb_where_command():
(Pdb) continue
"""
+def test_pdb_restart_command():
+ """Test restart command
+
+ >>> def test_function():
+ ... import pdb; pdb.Pdb(nosigint=True, readrc=False, mode='inline').set_trace()
+ ... x = 1
+
+ >>> with PdbTestInput([ # doctest: +ELLIPSIS
+ ... 'restart',
+ ... 'continue',
+ ... ]):
+ ... test_function()
+ > (2)test_function()
+ -> import pdb; pdb.Pdb(nosigint=True, readrc=False, mode='inline').set_trace()
+ (Pdb) restart
+ *** run/restart command is disabled when pdb is running in inline mode.
+ Use the command line interface to enable restarting your program
+ e.g. "python -m pdb myscript.py"
+ (Pdb) continue
+ """
+
+def test_pdb_commands_with_set_trace():
+ """Test that commands can be passed to Pdb.set_trace()
+
+ >>> def test_function():
+ ... x = 1
+ ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace(commands=['p x', 'c'])
+
+ >>> test_function()
+ 1
+ """
+
# skip this test if sys.flags.no_site = True;
# exit() isn't defined unless there's a site module.
diff --git a/Lib/test/test_perf_profiler.py b/Lib/test/test_perf_profiler.py
index ac1911ca24eafe..b68a55259c62e1 100644
--- a/Lib/test/test_perf_profiler.py
+++ b/Lib/test/test_perf_profiler.py
@@ -479,7 +479,7 @@ def compile_trampolines_for_all_functions():
self.assertIn(line, child_perf_file_contents)
-def _is_perf_vesion_at_least(major, minor):
+def _is_perf_version_at_least(major, minor):
# The output of perf --version looks like "perf version 6.7-3" but
# it can also be perf version "perf version 5.15.143"
try:
@@ -494,7 +494,7 @@ def _is_perf_vesion_at_least(major, minor):
@unittest.skipUnless(perf_command_works(), "perf command doesn't work")
-@unittest.skipUnless(_is_perf_vesion_at_least(6, 6), "perf command may not work due to a perf bug")
+@unittest.skipUnless(_is_perf_version_at_least(6, 6), "perf command may not work due to a perf bug")
class TestPerfProfilerWithDwarf(unittest.TestCase, TestPerfProfilerMixin):
def run_perf(self, script_dir, script, activate_trampoline=True):
if activate_trampoline:
diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py
index 659b9a5a73305d..35016b83a477fc 100644
--- a/Lib/test/test_posix.py
+++ b/Lib/test/test_posix.py
@@ -2142,6 +2142,13 @@ def test_stat(self):
with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"):
os.stat("file", dir_fd=0)
+ def test_ptsname_r(self):
+ self._verify_available("HAVE_PTSNAME_R")
+ if self.mac_ver >= (10, 13, 4):
+ self.assertIn("HAVE_PTSNAME_R", posix._have_functions)
+ else:
+ self.assertNotIn("HAVE_PTSNAME_R", posix._have_functions)
+
def test_access(self):
self._verify_available("HAVE_FACCESSAT")
if self.mac_ver >= (10, 10):
diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py
index e816de3720670f..0f3e9996e77e45 100644
--- a/Lib/test/test_pyrepl/test_pyrepl.py
+++ b/Lib/test/test_pyrepl/test_pyrepl.py
@@ -8,7 +8,7 @@
import subprocess
import sys
import tempfile
-from unittest import TestCase, skipUnless
+from unittest import TestCase, skipUnless, skipIf
from unittest.mock import patch
from test.support import force_not_colorized
from test.support import SHORT_TIMEOUT
@@ -35,6 +35,94 @@
except ImportError:
pty = None
+
+class ReplTestCase(TestCase):
+ def run_repl(
+ self,
+ repl_input: str | list[str],
+ env: dict | None = None,
+ *,
+ cmdline_args: list[str] | None = None,
+ cwd: str | None = None,
+ ) -> tuple[str, int]:
+ temp_dir = None
+ if cwd is None:
+ temp_dir = tempfile.TemporaryDirectory(ignore_cleanup_errors=True)
+ cwd = temp_dir.name
+ try:
+ return self._run_repl(
+ repl_input, env=env, cmdline_args=cmdline_args, cwd=cwd
+ )
+ finally:
+ if temp_dir is not None:
+ temp_dir.cleanup()
+
+ def _run_repl(
+ self,
+ repl_input: str | list[str],
+ *,
+ env: dict | None,
+ cmdline_args: list[str] | None,
+ cwd: str,
+ ) -> tuple[str, int]:
+ assert pty
+ master_fd, slave_fd = pty.openpty()
+ cmd = [sys.executable, "-i", "-u"]
+ if env is None:
+ cmd.append("-I")
+ elif "PYTHON_HISTORY" not in env:
+ env["PYTHON_HISTORY"] = os.path.join(cwd, ".regrtest_history")
+ if cmdline_args is not None:
+ cmd.extend(cmdline_args)
+
+ try:
+ import termios
+ except ModuleNotFoundError:
+ pass
+ else:
+ term_attr = termios.tcgetattr(slave_fd)
+ term_attr[6][termios.VREPRINT] = 0 # pass through CTRL-R
+ term_attr[6][termios.VINTR] = 0 # pass through CTRL-C
+ termios.tcsetattr(slave_fd, termios.TCSANOW, term_attr)
+
+ process = subprocess.Popen(
+ cmd,
+ stdin=slave_fd,
+ stdout=slave_fd,
+ stderr=slave_fd,
+ cwd=cwd,
+ text=True,
+ close_fds=True,
+ env=env if env else os.environ,
+ )
+ os.close(slave_fd)
+ if isinstance(repl_input, list):
+ repl_input = "\n".join(repl_input) + "\n"
+ os.write(master_fd, repl_input.encode("utf-8"))
+
+ output = []
+ while select.select([master_fd], [], [], SHORT_TIMEOUT)[0]:
+ try:
+ data = os.read(master_fd, 1024).decode("utf-8")
+ if not data:
+ break
+ except OSError:
+ break
+ output.append(data)
+ else:
+ os.close(master_fd)
+ process.kill()
+ self.fail(f"Timeout while waiting for output, got: {''.join(output)}")
+
+ os.close(master_fd)
+ try:
+ exit_code = process.wait(timeout=SHORT_TIMEOUT)
+ except subprocess.TimeoutExpired:
+ process.kill()
+ exit_code = process.wait()
+ return "".join(output), exit_code
+
+
class TestCursorPosition(TestCase):
def prepare_reader(self, events):
console = FakeConsole(events)
@@ -968,7 +1056,20 @@ def test_bracketed_paste_single_line(self):
@skipUnless(pty, "requires pty")
-class TestMain(TestCase):
+class TestDumbTerminal(ReplTestCase):
+ def test_dumb_terminal_exits_cleanly(self):
+ env = os.environ.copy()
+ env.update({"TERM": "dumb"})
+ output, exit_code = self.run_repl("exit()\n", env=env)
+ self.assertEqual(exit_code, 0)
+ self.assertIn("warning: can't use pyrepl", output)
+ self.assertNotIn("Exception", output)
+ self.assertNotIn("Traceback", output)
+
+
+@skipUnless(pty, "requires pty")
+@skipIf((os.environ.get("TERM") or "dumb") == "dumb", "can't use pyrepl in dumb terminal")
+class TestMain(ReplTestCase):
def setUp(self):
# Cleanup from PYTHON* variables to isolate from local
# user settings, see #121359. Such variables should be
@@ -1078,15 +1179,6 @@ def test_inspect_keeps_globals_from_inspected_module(self):
}
self._run_repl_globals_test(expectations, as_module=True)
- def test_dumb_terminal_exits_cleanly(self):
- env = os.environ.copy()
- env.update({"TERM": "dumb"})
- output, exit_code = self.run_repl("exit()\n", env=env)
- self.assertEqual(exit_code, 0)
- self.assertIn("warning: can't use pyrepl", output)
- self.assertNotIn("Exception", output)
- self.assertNotIn("Traceback", output)
-
@force_not_colorized
def test_python_basic_repl(self):
env = os.environ.copy()
@@ -1209,80 +1301,6 @@ def test_proper_tracebacklimit(self):
self.assertIn("in x3", output)
self.assertIn("in ", output)
- def run_repl(
- self,
- repl_input: str | list[str],
- env: dict | None = None,
- *,
- cmdline_args: list[str] | None = None,
- cwd: str | None = None,
- ) -> tuple[str, int]:
- temp_dir = None
- if cwd is None:
- temp_dir = tempfile.TemporaryDirectory(ignore_cleanup_errors=True)
- cwd = temp_dir.name
- try:
- return self._run_repl(
- repl_input, env=env, cmdline_args=cmdline_args, cwd=cwd
- )
- finally:
- if temp_dir is not None:
- temp_dir.cleanup()
-
- def _run_repl(
- self,
- repl_input: str | list[str],
- *,
- env: dict | None,
- cmdline_args: list[str] | None,
- cwd: str,
- ) -> tuple[str, int]:
- assert pty
- master_fd, slave_fd = pty.openpty()
- cmd = [sys.executable, "-i", "-u"]
- if env is None:
- cmd.append("-I")
- elif "PYTHON_HISTORY" not in env:
- env["PYTHON_HISTORY"] = os.path.join(cwd, ".regrtest_history")
- if cmdline_args is not None:
- cmd.extend(cmdline_args)
- process = subprocess.Popen(
- cmd,
- stdin=slave_fd,
- stdout=slave_fd,
- stderr=slave_fd,
- cwd=cwd,
- text=True,
- close_fds=True,
- env=env if env else os.environ,
- )
- os.close(slave_fd)
- if isinstance(repl_input, list):
- repl_input = "\n".join(repl_input) + "\n"
- os.write(master_fd, repl_input.encode("utf-8"))
-
- output = []
- while select.select([master_fd], [], [], SHORT_TIMEOUT)[0]:
- try:
- data = os.read(master_fd, 1024).decode("utf-8")
- if not data:
- break
- except OSError:
- break
- output.append(data)
- else:
- os.close(master_fd)
- process.kill()
- self.fail(f"Timeout while waiting for output, got: {''.join(output)}")
-
- os.close(master_fd)
- try:
- exit_code = process.wait(timeout=SHORT_TIMEOUT)
- except subprocess.TimeoutExpired:
- process.kill()
- exit_code = process.wait()
- return "".join(output), exit_code
-
def test_readline_history_file(self):
# skip, if readline module is not available
readline = import_module('readline')
@@ -1305,3 +1323,7 @@ def test_readline_history_file(self):
output, exit_code = self.run_repl("exit\n", env=env)
self.assertEqual(exit_code, 0)
self.assertNotIn("\\040", pathlib.Path(hfile.name).read_text())
+
+ def test_keyboard_interrupt_after_isearch(self):
+ output, exit_code = self.run_repl(["\x12", "\x03", "exit"])
+ self.assertEqual(exit_code, 0)
diff --git a/Lib/test/test_re.py b/Lib/test/test_re.py
index 7e454cb060d2ae..ff95f54026e172 100644
--- a/Lib/test/test_re.py
+++ b/Lib/test/test_re.py
@@ -883,31 +883,137 @@ def test_named_unicode_escapes(self):
self.checkPatternError(br'\N{LESS-THAN SIGN}', r'bad escape \N', 0)
self.checkPatternError(br'[\N{LESS-THAN SIGN}]', r'bad escape \N', 1)
- def test_string_boundaries(self):
+ def test_word_boundaries(self):
# See http://bugs.python.org/issue10713
- self.assertEqual(re.search(r"\b(abc)\b", "abc").group(1),
- "abc")
+ self.assertEqual(re.search(r"\b(abc)\b", "abc").group(1), "abc")
+ self.assertEqual(re.search(r"\b(abc)\b", "abc", re.ASCII).group(1), "abc")
+ self.assertEqual(re.search(br"\b(abc)\b", b"abc").group(1), b"abc")
+ self.assertEqual(re.search(br"\b(abc)\b", b"abc", re.LOCALE).group(1), b"abc")
+ self.assertEqual(re.search(r"\b(ьюя)\b", "ьюя").group(1), "ьюя")
+ self.assertIsNone(re.search(r"\b(ьюя)\b", "ьюя", re.ASCII))
+ # There's a word boundary between a word and a non-word.
+ self.assertTrue(re.match(r".\b", "a="))
+ self.assertTrue(re.match(r".\b", "a=", re.ASCII))
+ self.assertTrue(re.match(br".\b", b"a="))
+ self.assertTrue(re.match(br".\b", b"a=", re.LOCALE))
+ self.assertTrue(re.match(r".\b", "я="))
+ self.assertIsNone(re.match(r".\b", "я=", re.ASCII))
+ # There's a word boundary between a non-word and a word.
+ self.assertTrue(re.match(r".\b", "=a"))
+ self.assertTrue(re.match(r".\b", "=a", re.ASCII))
+ self.assertTrue(re.match(br".\b", b"=a"))
+ self.assertTrue(re.match(br".\b", b"=a", re.LOCALE))
+ self.assertTrue(re.match(r".\b", "=я"))
+ self.assertIsNone(re.match(r".\b", "=я", re.ASCII))
+ # There is no word boundary inside a word.
+ self.assertIsNone(re.match(r".\b", "ab"))
+ self.assertIsNone(re.match(r".\b", "ab", re.ASCII))
+ self.assertIsNone(re.match(br".\b", b"ab"))
+ self.assertIsNone(re.match(br".\b", b"ab", re.LOCALE))
+ self.assertIsNone(re.match(r".\b", "юя"))
+ self.assertIsNone(re.match(r".\b", "юя", re.ASCII))
+ # There is no word boundary between a non-word characters.
+ self.assertIsNone(re.match(r".\b", "=-"))
+ self.assertIsNone(re.match(r".\b", "=-", re.ASCII))
+ self.assertIsNone(re.match(br".\b", b"=-"))
+ self.assertIsNone(re.match(br".\b", b"=-", re.LOCALE))
+ # There is no non-boundary match between a word and a non-word.
+ self.assertIsNone(re.match(r".\B", "a="))
+ self.assertIsNone(re.match(r".\B", "a=", re.ASCII))
+ self.assertIsNone(re.match(br".\B", b"a="))
+ self.assertIsNone(re.match(br".\B", b"a=", re.LOCALE))
+ self.assertIsNone(re.match(r".\B", "я="))
+ self.assertTrue(re.match(r".\B", "я=", re.ASCII))
+ # There is no non-boundary match between a non-word and a word.
+ self.assertIsNone(re.match(r".\B", "=a"))
+ self.assertIsNone(re.match(r".\B", "=a", re.ASCII))
+ self.assertIsNone(re.match(br".\B", b"=a"))
+ self.assertIsNone(re.match(br".\B", b"=a", re.LOCALE))
+ self.assertIsNone(re.match(r".\B", "=я"))
+ self.assertTrue(re.match(r".\B", "=я", re.ASCII))
+ # There's a non-boundary match inside a word.
+ self.assertTrue(re.match(r".\B", "ab"))
+ self.assertTrue(re.match(r".\B", "ab", re.ASCII))
+ self.assertTrue(re.match(br".\B", b"ab"))
+ self.assertTrue(re.match(br".\B", b"ab", re.LOCALE))
+ self.assertTrue(re.match(r".\B", "юя"))
+ self.assertTrue(re.match(r".\B", "юя", re.ASCII))
+ # There's a non-boundary match between a non-word characters.
+ self.assertTrue(re.match(r".\B", "=-"))
+ self.assertTrue(re.match(r".\B", "=-", re.ASCII))
+ self.assertTrue(re.match(br".\B", b"=-"))
+ self.assertTrue(re.match(br".\B", b"=-", re.LOCALE))
# There's a word boundary at the start of a string.
self.assertTrue(re.match(r"\b", "abc"))
+ self.assertTrue(re.match(r"\b", "abc", re.ASCII))
+ self.assertTrue(re.match(br"\b", b"abc"))
+ self.assertTrue(re.match(br"\b", b"abc", re.LOCALE))
+ self.assertTrue(re.match(r"\b", "ьюя"))
+ self.assertIsNone(re.match(r"\b", "ьюя", re.ASCII))
+ # There's a word boundary at the end of a string.
+ self.assertTrue(re.fullmatch(r".+\b", "abc"))
+ self.assertTrue(re.fullmatch(r".+\b", "abc", re.ASCII))
+ self.assertTrue(re.fullmatch(br".+\b", b"abc"))
+ self.assertTrue(re.fullmatch(br".+\b", b"abc", re.LOCALE))
+ self.assertTrue(re.fullmatch(r".+\b", "ьюя"))
+ self.assertIsNone(re.search(r"\b", "ьюя", re.ASCII))
# A non-empty string includes a non-boundary zero-length match.
- self.assertTrue(re.search(r"\B", "abc"))
+ self.assertEqual(re.search(r"\B", "abc").span(), (1, 1))
+ self.assertEqual(re.search(r"\B", "abc", re.ASCII).span(), (1, 1))
+ self.assertEqual(re.search(br"\B", b"abc").span(), (1, 1))
+ self.assertEqual(re.search(br"\B", b"abc", re.LOCALE).span(), (1, 1))
+ self.assertEqual(re.search(r"\B", "ьюя").span(), (1, 1))
+ self.assertEqual(re.search(r"\B", "ьюя", re.ASCII).span(), (0, 0))
# There is no non-boundary match at the start of a string.
- self.assertFalse(re.match(r"\B", "abc"))
+ self.assertIsNone(re.match(r"\B", "abc"))
+ self.assertIsNone(re.match(r"\B", "abc", re.ASCII))
+ self.assertIsNone(re.match(br"\B", b"abc"))
+ self.assertIsNone(re.match(br"\B", b"abc", re.LOCALE))
+ self.assertIsNone(re.match(r"\B", "ьюя"))
+ self.assertTrue(re.match(r"\B", "ьюя", re.ASCII))
+ # There is no non-boundary match at the end of a string.
+ self.assertIsNone(re.fullmatch(r".+\B", "abc"))
+ self.assertIsNone(re.fullmatch(r".+\B", "abc", re.ASCII))
+ self.assertIsNone(re.fullmatch(br".+\B", b"abc"))
+ self.assertIsNone(re.fullmatch(br".+\B", b"abc", re.LOCALE))
+ self.assertIsNone(re.fullmatch(r".+\B", "ьюя"))
+ self.assertTrue(re.fullmatch(r".+\B", "ьюя", re.ASCII))
# However, an empty string contains no word boundaries, and also no
# non-boundaries.
- self.assertIsNone(re.search(r"\B", ""))
+ self.assertIsNone(re.search(r"\b", ""))
+ self.assertIsNone(re.search(r"\b", "", re.ASCII))
+ self.assertIsNone(re.search(br"\b", b""))
+ self.assertIsNone(re.search(br"\b", b"", re.LOCALE))
# This one is questionable and different from the perlre behaviour,
# but describes current behavior.
- self.assertIsNone(re.search(r"\b", ""))
+ self.assertIsNone(re.search(r"\B", ""))
+ self.assertIsNone(re.search(r"\B", "", re.ASCII))
+ self.assertIsNone(re.search(br"\B", b""))
+ self.assertIsNone(re.search(br"\B", b"", re.LOCALE))
# A single word-character string has two boundaries, but no
# non-boundary gaps.
self.assertEqual(len(re.findall(r"\b", "a")), 2)
+ self.assertEqual(len(re.findall(r"\b", "a", re.ASCII)), 2)
+ self.assertEqual(len(re.findall(br"\b", b"a")), 2)
+ self.assertEqual(len(re.findall(br"\b", b"a", re.LOCALE)), 2)
self.assertEqual(len(re.findall(r"\B", "a")), 0)
+ self.assertEqual(len(re.findall(r"\B", "a", re.ASCII)), 0)
+ self.assertEqual(len(re.findall(br"\B", b"a")), 0)
+ self.assertEqual(len(re.findall(br"\B", b"a", re.LOCALE)), 0)
# If there are no words, there are no boundaries
self.assertEqual(len(re.findall(r"\b", " ")), 0)
+ self.assertEqual(len(re.findall(r"\b", " ", re.ASCII)), 0)
+ self.assertEqual(len(re.findall(br"\b", b" ")), 0)
+ self.assertEqual(len(re.findall(br"\b", b" ", re.LOCALE)), 0)
self.assertEqual(len(re.findall(r"\b", " ")), 0)
+ self.assertEqual(len(re.findall(r"\b", " ", re.ASCII)), 0)
+ self.assertEqual(len(re.findall(br"\b", b" ")), 0)
+ self.assertEqual(len(re.findall(br"\b", b" ", re.LOCALE)), 0)
# Can match around the whitespace.
self.assertEqual(len(re.findall(r"\B", " ")), 2)
+ self.assertEqual(len(re.findall(r"\B", " ", re.ASCII)), 2)
+ self.assertEqual(len(re.findall(br"\B", b" ")), 2)
+ self.assertEqual(len(re.findall(br"\B", b" ", re.LOCALE)), 2)
def test_bigcharset(self):
self.assertEqual(re.match("([\u2222\u2223])",
diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py
index 80e1d73b6b2aab..37e54d23b22516 100644
--- a/Lib/test/test_shutil.py
+++ b/Lib/test/test_shutil.py
@@ -1909,7 +1909,10 @@ def test_unzip_zipfile(self):
subprocess.check_output(zip_cmd, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as exc:
details = exc.output.decode(errors="replace")
- if 'unrecognized option: t' in details:
+ if any(message in details for message in [
+ 'unrecognized option: t', # BusyBox
+ 'invalid option -- t', # Android
+ ]):
self.skipTest("unzip doesn't support -t")
msg = "{}\n\n**Unzip Output**\n{}"
self.fail(msg.format(exc, details))
diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py
index da36b8576be1ad..704a0090bdbc0f 100644
--- a/Lib/test/test_signal.py
+++ b/Lib/test/test_signal.py
@@ -123,6 +123,8 @@ def __repr__(self):
self.assertEqual(signal.getsignal(signal.SIGHUP), hup)
self.assertEqual(0, argument.repr_count)
+ @unittest.skipIf(sys.platform.startswith("netbsd"),
+ "gh-124083: strsignal is not supported on NetBSD")
def test_strsignal(self):
self.assertIn("Interrupt", signal.strsignal(signal.SIGINT))
self.assertIn("Terminated", signal.strsignal(signal.SIGTERM))
diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py
index 4bca33b7451f80..d16ad7ef5d4328 100644
--- a/Lib/test/test_stable_abi_ctypes.py
+++ b/Lib/test/test_stable_abi_ctypes.py
@@ -719,6 +719,7 @@ def test_windows_feature_macros(self):
"PyType_FromSpecWithBases",
"PyType_GenericAlloc",
"PyType_GenericNew",
+ "PyType_GetBaseByToken",
"PyType_GetFlags",
"PyType_GetFullyQualifiedName",
"PyType_GetModule",
@@ -898,6 +899,7 @@ def test_windows_feature_macros(self):
"Py_MakePendingCalls",
"Py_NewInterpreter",
"Py_NewRef",
+ "Py_REFCNT",
"Py_ReprEnter",
"Py_ReprLeave",
"Py_SetPath",
diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py
index 5508cc3eec85c8..bdbf8800cfd8f6 100644
--- a/Lib/test/test_struct.py
+++ b/Lib/test/test_struct.py
@@ -96,6 +96,13 @@ def test_new_features(self):
('10s', b'helloworld', b'helloworld', b'helloworld', 0),
('11s', b'helloworld', b'helloworld\0', b'helloworld\0', 1),
('20s', b'helloworld', b'helloworld'+10*b'\0', b'helloworld'+10*b'\0', 1),
+ ('0p', b'helloworld', b'', b'', 1),
+ ('1p', b'helloworld', b'\x00', b'\x00', 1),
+ ('2p', b'helloworld', b'\x01h', b'\x01h', 1),
+ ('10p', b'helloworld', b'\x09helloworl', b'\x09helloworl', 1),
+ ('11p', b'helloworld', b'\x0Ahelloworld', b'\x0Ahelloworld', 0),
+ ('12p', b'helloworld', b'\x0Ahelloworld\0', b'\x0Ahelloworld\0', 1),
+ ('20p', b'helloworld', b'\x0Ahelloworld'+9*b'\0', b'\x0Ahelloworld'+9*b'\0', 1),
('b', 7, b'\7', b'\7', 0),
('b', -7, b'\371', b'\371', 0),
('B', 7, b'\7', b'\7', 0),
@@ -339,6 +346,7 @@ def assertStructError(func, *args, **kwargs):
def test_p_code(self):
# Test p ("Pascal string") code.
for code, input, expected, expectedback in [
+ ('0p', b'abc', b'', b''),
('p', b'abc', b'\x00', b''),
('1p', b'abc', b'\x00', b''),
('2p', b'abc', b'\x01a', b'a'),
@@ -580,6 +588,7 @@ def test__sizeof__(self):
self.check_sizeof('187s', 1)
self.check_sizeof('20p', 1)
self.check_sizeof('0s', 1)
+ self.check_sizeof('0p', 1)
self.check_sizeof('0c', 0)
def test_boundary_error_message(self):
diff --git a/Lib/test/test_super.py b/Lib/test/test_super.py
index 3ffbe03f0c2f11..b0d1f12513d404 100644
--- a/Lib/test/test_super.py
+++ b/Lib/test/test_super.py
@@ -4,6 +4,7 @@
import threading
import unittest
from unittest.mock import patch
+from test import support
from test.support import import_helper, threading_helper
@@ -513,6 +514,11 @@ def test___class___modification_multithreaded(self):
This should be the case anyways as our test suite sets
an audit hook.
"""
+
+ if support.Py_GIL_DISABLED:
+ # gh-124402: On a Free Threaded build, the test takes a few minutes
+ support.requires('cpu')
+
class Foo:
pass
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index 434b3e315e0aa3..52640f8e9e5f7b 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -1042,14 +1042,10 @@ def test_debugmallocstats(self):
# Output of sys._debugmallocstats() depends on configure flags.
# The sysconfig vars are not available on Windows.
if sys.platform != "win32":
- with_freelists = sysconfig.get_config_var("WITH_FREELISTS")
with_pymalloc = sysconfig.get_config_var("WITH_PYMALLOC")
- if with_freelists:
- self.assertIn(b"free PyDictObjects", err)
+ self.assertIn(b"free PyDictObjects", err)
if with_pymalloc:
self.assertIn(b'Small block threshold', err)
- if not with_freelists and not with_pymalloc:
- self.assertFalse(err)
# The function has no parameter
self.assertRaises(TypeError, sys._debugmallocstats, True)
@@ -1728,7 +1724,7 @@ def delx(self): del self.__x
'3P' # PyMappingMethods
'10P' # PySequenceMethods
'2P' # PyBufferProcs
- '6P'
+ '7P'
'1PIP' # Specializer cache
+ typeid # heap type id (free-threaded only)
)
diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py
index b568221212d71f..455fea034198a6 100644
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -3307,6 +3307,41 @@ def format_frame_summary(self, frame_summary, colorize=False):
f' File "{__file__}", line {lno}, in f\n 1/0\n'
)
+ def test_summary_should_show_carets(self):
+ # See: https://github.com/python/cpython/issues/122353
+
+ # statement to execute and to get a ZeroDivisionError for a traceback
+ statement = "abcdef = 1 / 0 and 2.0"
+ colno = statement.index('1 / 0')
+ end_colno = colno + len('1 / 0')
+
+ # Actual line to use when rendering the traceback
+ # and whose AST will be extracted (it will be empty).
+ cached_line = '# this line will be used during rendering'
+ self.addCleanup(unlink, TESTFN)
+ with open(TESTFN, "w") as file:
+ file.write(cached_line)
+ linecache.updatecache(TESTFN, {})
+
+ try:
+ exec(compile(statement, TESTFN, "exec"))
+ except ZeroDivisionError as exc:
+ # This is the simplest way to create a StackSummary
+ # whose FrameSummary items have their column offsets.
+ s = traceback.TracebackException.from_exception(exc).stack
+ self.assertIsInstance(s, traceback.StackSummary)
+ with unittest.mock.patch.object(s, '_should_show_carets',
+ wraps=s._should_show_carets) as ff:
+ self.assertEqual(len(s), 2)
+ self.assertListEqual(
+ s.format_frame_summary(s[1]).splitlines(),
+ [
+ f' File "{TESTFN}", line 1, in ',
+ f' {cached_line}'
+ ]
+ )
+ ff.assert_called_with(colno, end_colno, [cached_line], None)
+
class Unrepresentable:
def __repr__(self) -> str:
raise Exception("Unrepresentable")
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 6e036b600330c1..3ac6b97383fcef 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -7043,6 +7043,25 @@ def h(x: collections.abc.Callable[P, int]): ...
self.assertEqual(get_type_hints(g), {'x': collections.abc.Callable[..., int]})
self.assertEqual(get_type_hints(h), {'x': collections.abc.Callable[P, int]})
+ def test_get_type_hints_format(self):
+ class C:
+ x: undefined
+
+ with self.assertRaises(NameError):
+ get_type_hints(C)
+
+ with self.assertRaises(NameError):
+ get_type_hints(C, format=annotationlib.Format.VALUE)
+
+ annos = get_type_hints(C, format=annotationlib.Format.FORWARDREF)
+ self.assertIsInstance(annos, dict)
+ self.assertEqual(list(annos), ['x'])
+ self.assertIsInstance(annos['x'], annotationlib.ForwardRef)
+ self.assertEqual(annos['x'].__arg__, 'undefined')
+
+ self.assertEqual(get_type_hints(C, format=annotationlib.Format.SOURCE),
+ {'x': 'undefined'})
+
class GetUtilitiesTestCase(TestCase):
def test_get_origin(self):
diff --git a/Lib/test/test_unittest/testmock/testmagicmethods.py b/Lib/test/test_unittest/testmock/testmagicmethods.py
index 5ca753b8f20811..2a8aa11b3284f6 100644
--- a/Lib/test/test_unittest/testmock/testmagicmethods.py
+++ b/Lib/test/test_unittest/testmock/testmagicmethods.py
@@ -331,6 +331,45 @@ def test_magic_methods_fspath(self):
self.assertEqual(os.fspath(mock), expected_path)
mock.__fspath__.assert_called_once()
+ def test_magic_mock_does_not_reset_magic_returns(self):
+ # https://github.com/python/cpython/issues/123934
+ for reset in (True, False):
+ with self.subTest(reset=reset):
+ mm = MagicMock()
+ self.assertIs(type(mm.__str__()), str)
+ mm.__str__.assert_called_once()
+
+ self.assertIs(type(mm.__hash__()), int)
+ mm.__hash__.assert_called_once()
+
+ for _ in range(3):
+ # Repeat reset several times to be sure:
+ mm.reset_mock(return_value=reset)
+
+ self.assertIs(type(mm.__str__()), str)
+ mm.__str__.assert_called_once()
+
+ self.assertIs(type(mm.__hash__()), int)
+ mm.__hash__.assert_called_once()
+
+ def test_magic_mock_resets_manual_mocks(self):
+ mm = MagicMock()
+ mm.__iter__ = MagicMock(return_value=iter([1]))
+ mm.custom = MagicMock(return_value=2)
+ self.assertEqual(list(iter(mm)), [1])
+ self.assertEqual(mm.custom(), 2)
+
+ mm.reset_mock(return_value=True)
+ self.assertEqual(list(iter(mm)), [])
+ self.assertIsInstance(mm.custom(), MagicMock)
+
+ def test_magic_mock_resets_manual_mocks_empty_iter(self):
+ mm = MagicMock()
+ mm.__iter__.return_value = []
+ self.assertEqual(list(iter(mm)), [])
+
+ mm.reset_mock(return_value=True)
+ self.assertEqual(list(iter(mm)), [])
def test_magic_methods_and_spec(self):
class Iterable(object):
diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py
index b9fcc59d49668c..1ef08da326c18c 100644
--- a/Lib/test/test_venv.py
+++ b/Lib/test/test_venv.py
@@ -504,6 +504,21 @@ def test_unicode_in_batch_file(self):
)
self.assertEqual(out.strip(), '0')
+ @unittest.skipUnless(os.name == 'nt' and can_symlink(),
+ 'symlinks on Windows')
+ def test_failed_symlink(self):
+ """
+ Test handling of failed symlinks on Windows.
+ """
+ rmtree(self.env_dir)
+ env_dir = os.path.join(os.path.realpath(self.env_dir), 'venv')
+ with patch('os.symlink') as mock_symlink:
+ mock_symlink.side_effect = OSError()
+ builder = venv.EnvBuilder(clear=True, symlinks=True)
+ _, err = self.run_with_capture(builder.create, env_dir)
+ filepath_regex = r"'[A-Z]:\\\\(?:[^\\\\]+\\\\)*[^\\\\]+'"
+ self.assertRegex(err, rf"Unable to symlink {filepath_regex} to {filepath_regex}")
+
@requireVenvCreate
def test_multiprocessing(self):
"""
diff --git a/Lib/test/test_with.py b/Lib/test/test_with.py
index e8c4ddf979e2ee..839cdec68d573e 100644
--- a/Lib/test/test_with.py
+++ b/Lib/test/test_with.py
@@ -171,7 +171,10 @@ def __exit__(self, *args):
def shouldThrow():
ct = EnterThrows()
self.foo = None
- with ct as self.foo:
+ # Ruff complains that we're redefining `self.foo` here,
+ # but the whole point of the test is to check that `self.foo`
+ # is *not* redefined (because `__enter__` raises)
+ with ct as self.foo: # ruff: noqa: F811
pass
self.assertRaises(RuntimeError, shouldThrow)
self.assertEqual(self.foo, None)
@@ -252,7 +255,6 @@ def testInlineGeneratorBoundSyntax(self):
self.assertAfterWithGeneratorInvariantsNoError(foo)
def testInlineGeneratorBoundToExistingVariable(self):
- foo = None
with mock_contextmanager_generator() as foo:
self.assertInWithGeneratorInvariants(foo)
self.assertAfterWithGeneratorInvariantsNoError(foo)
diff --git a/Lib/traceback.py b/Lib/traceback.py
index 3e708c6f86a4c5..0fe7187a0c6193 100644
--- a/Lib/traceback.py
+++ b/Lib/traceback.py
@@ -698,6 +698,8 @@ def _should_show_carets(self, start_offset, end_offset, all_lines, anchors):
with suppress(SyntaxError, ImportError):
import ast
tree = ast.parse('\n'.join(all_lines))
+ if not tree.body:
+ return False
statement = tree.body[0]
value = None
def _spawns_full_line(value):
diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py
index 1fa90277e08ec3..bb34c7436047ad 100644
--- a/Lib/unittest/mock.py
+++ b/Lib/unittest/mock.py
@@ -628,7 +628,7 @@ def __set_side_effect(self, value):
side_effect = property(__get_side_effect, __set_side_effect)
- def reset_mock(self, visited=None,*, return_value=False, side_effect=False):
+ def reset_mock(self, visited=None, *, return_value=False, side_effect=False):
"Restore the mock object to its initial state."
if visited is None:
visited = []
@@ -2218,6 +2218,17 @@ def mock_add_spec(self, spec, spec_set=False):
self._mock_add_spec(spec, spec_set)
self._mock_set_magics()
+ def reset_mock(self, /, *args, return_value=False, **kwargs):
+ if (
+ return_value
+ and self._mock_name
+ and _is_magic(self._mock_name)
+ ):
+ # Don't reset return values for magic methods,
+ # otherwise `m.__str__` will start
+ # to return `MagicMock` instances, instead of `str` instances.
+ return_value = False
+ super().reset_mock(*args, return_value=return_value, **kwargs)
class MagicProxy(Base):
diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py
index fa69d5846f2fa7..028e9483196694 100644
--- a/Lib/venv/__init__.py
+++ b/Lib/venv/__init__.py
@@ -393,7 +393,7 @@ def setup_python(self, context):
os.symlink(src, dest)
to_unlink.append(dest)
except OSError:
- logger.warning('Unable to symlink %r to %r', src, dst)
+ logger.warning('Unable to symlink %r to %r', src, dest)
do_copies = True
for f in to_unlink:
try:
diff --git a/Mac/BuildScript/build-installer.py b/Mac/BuildScript/build-installer.py
index b97738836d92cc..f5f0ed44884142 100755
--- a/Mac/BuildScript/build-installer.py
+++ b/Mac/BuildScript/build-installer.py
@@ -264,10 +264,10 @@ def library_recipes():
tk_patches = ['backport_gh71383_fix.patch', 'tk868_on_10_8_10_9.patch', 'backport_gh110950_fix.patch']
else:
- tcl_tk_ver='8.6.14'
- tcl_checksum='5880225babf7954c58d4fb0f5cf6279104ce1cd6aa9b71e9a6322540e1c4de66'
+ tcl_tk_ver='8.6.15'
+ tcl_checksum='861e159753f2e2fbd6ec1484103715b0be56be3357522b858d3cbb5f893ffef1'
- tk_checksum='8ffdb720f47a6ca6107eac2dd877e30b0ef7fac14f3a84ebbd0b3612cee41a94'
+ tk_checksum='550969f35379f952b3020f3ab7b9dd5bfd11c1ef7c9b7c6a75f5c49aca793fec'
tk_patches = []
diff --git a/Makefile.pre.in b/Makefile.pre.in
index 97dc1495303567..bf5897364e485e 100644
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -2418,6 +2418,7 @@ LIBSUBDIRS= asyncio \
TESTSUBDIRS= idlelib/idle_test \
test \
test/test_ast \
+ test/test_ast/data \
test/archivetestdata \
test/audiodata \
test/certdata \
diff --git a/Misc/ACKS b/Misc/ACKS
index b031eb7c11f73f..ef0f403950255b 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -1653,6 +1653,7 @@ Scott Schram
Robin Schreiber
Chad J. Schroeder
Simon-Martin Schroeder
+Brian Schubert
Christian Schubert
Sam Schulenburg
Andreas Schwab
@@ -1744,6 +1745,7 @@ Christopher Smith
Eric V. Smith
Ethan H. Smith
Gregory P. Smith
+Malcolm Smith
Mark Smith
Nathaniel J. Smith
Roy Smith
diff --git a/Misc/NEWS.d/next/Build/2024-09-16-09-42-05.gh-issue-124102.Ow254j.rst b/Misc/NEWS.d/next/Build/2024-09-16-09-42-05.gh-issue-124102.Ow254j.rst
new file mode 100644
index 00000000000000..6edc9a6abbced4
--- /dev/null
+++ b/Misc/NEWS.d/next/Build/2024-09-16-09-42-05.gh-issue-124102.Ow254j.rst
@@ -0,0 +1,2 @@
+Update internal documentation under PCbuild, so it now correctly states that
+Windows requires VS2017 or later and Python 3.10 or later
diff --git a/Misc/NEWS.d/next/Build/2024-09-23-11-27-25.gh-issue-123990.d6HrYC.rst b/Misc/NEWS.d/next/Build/2024-09-23-11-27-25.gh-issue-123990.d6HrYC.rst
new file mode 100644
index 00000000000000..2b4f993323297a
--- /dev/null
+++ b/Misc/NEWS.d/next/Build/2024-09-23-11-27-25.gh-issue-123990.d6HrYC.rst
@@ -0,0 +1 @@
+Remove ``WITH_FREELISTS`` macro and ``--without-freelists`` build configuration
diff --git a/Misc/NEWS.d/next/C API/2024-05-21-18-28-44.gh-issue-119333.OTsYVX.rst b/Misc/NEWS.d/next/C API/2024-05-21-18-28-44.gh-issue-119333.OTsYVX.rst
new file mode 100644
index 00000000000000..6fb6013c4d442d
--- /dev/null
+++ b/Misc/NEWS.d/next/C API/2024-05-21-18-28-44.gh-issue-119333.OTsYVX.rst
@@ -0,0 +1,2 @@
+Add :c:func:`PyContext_AddWatcher` and :c:func:`PyContext_ClearWatcher` APIs to
+register callbacks to receive notification on enter and exit of context objects.
diff --git a/Misc/NEWS.d/next/C API/2024-06-08-08-33-40.gh-issue-119771.Oip2dL.rst b/Misc/NEWS.d/next/C API/2024-06-08-08-33-40.gh-issue-119771.Oip2dL.rst
new file mode 100644
index 00000000000000..61619082487c3b
--- /dev/null
+++ b/Misc/NEWS.d/next/C API/2024-06-08-08-33-40.gh-issue-119771.Oip2dL.rst
@@ -0,0 +1,2 @@
+Set :data:`errno` in :c:func:`_Py_c_pow` on overflows. Patch by Sergey B
+Kirpichev.
diff --git a/Misc/NEWS.d/next/C_API/2024-09-12-16-16-24.gh-issue-123880.2-8vcj.rst b/Misc/NEWS.d/next/C_API/2024-09-12-16-16-24.gh-issue-123880.2-8vcj.rst
new file mode 100644
index 00000000000000..8a31c962ec7d93
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2024-09-12-16-16-24.gh-issue-123880.2-8vcj.rst
@@ -0,0 +1,2 @@
+Fixed a bug that prevented circular imports of extension modules that use
+single-phase initialization.
diff --git a/Misc/NEWS.d/next/C_API/2024-09-16-16-21-39.gh-issue-124127.LB8DBU.rst b/Misc/NEWS.d/next/C_API/2024-09-16-16-21-39.gh-issue-124127.LB8DBU.rst
new file mode 100644
index 00000000000000..883f173f8fbbc4
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2024-09-16-16-21-39.gh-issue-124127.LB8DBU.rst
@@ -0,0 +1,3 @@
+In the limited C API 3.14 and newer, :c:func:`Py_REFCNT` is now implemented
+as an opaque function call to hide implementation details. Patch by Victor
+Stinner.
diff --git a/Misc/NEWS.d/next/C_API/2024-09-17-05-23-35.gh-issue-124153.L8TWmx.rst b/Misc/NEWS.d/next/C_API/2024-09-17-05-23-35.gh-issue-124153.L8TWmx.rst
new file mode 100644
index 00000000000000..b8c0b4667cb730
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2024-09-17-05-23-35.gh-issue-124153.L8TWmx.rst
@@ -0,0 +1,2 @@
+Add :c:func:`PyType_GetBaseByToken` and :c:data:`Py_tp_token` slot for easier
+type checking, related to :pep:`489` and :pep:`630`.
diff --git a/Misc/NEWS.d/next/C_API/2024-09-18-18-40-30.gh-issue-124160.Zy-VKi.rst b/Misc/NEWS.d/next/C_API/2024-09-18-18-40-30.gh-issue-124160.Zy-VKi.rst
new file mode 100644
index 00000000000000..26e7aef08ea4f3
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2024-09-18-18-40-30.gh-issue-124160.Zy-VKi.rst
@@ -0,0 +1,2 @@
+Fix crash when importing modules containing state and single-phase
+initialization in a subinterpreter.
diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-03-19-22-21-22.gh-issue-116022.iyHENN.rst b/Misc/NEWS.d/next/Core and Builtins/2024-03-19-22-21-22.gh-issue-116022.iyHENN.rst
new file mode 100644
index 00000000000000..659ffb289129e2
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2024-03-19-22-21-22.gh-issue-116022.iyHENN.rst
@@ -0,0 +1 @@
+Improve the :meth:`~object.__repr__` output of :class:`~ast.AST` nodes.
diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-09-23-23-06-19.gh-issue-124285.mahGTg.rst b/Misc/NEWS.d/next/Core and Builtins/2024-09-23-23-06-19.gh-issue-124285.mahGTg.rst
new file mode 100644
index 00000000000000..a6dec66a743f92
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2024-09-23-23-06-19.gh-issue-124285.mahGTg.rst
@@ -0,0 +1,2 @@
+Fix bug where ``bool(a)`` can be invoked more than once during the
+evaluation of a compound boolean expression.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-08-23-11-26-54.gh-issue-122298.ZMyln4.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-08-23-11-26-54.gh-issue-122298.ZMyln4.rst
index e7645bfc5f323d..722f69616b6b1b 100644
--- a/Misc/NEWS.d/next/Core_and_Builtins/2024-08-23-11-26-54.gh-issue-122298.ZMyln4.rst
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-08-23-11-26-54.gh-issue-122298.ZMyln4.rst
@@ -1,3 +1,3 @@
Restore printout of GC stats when ``gc.set_debug(gc.DEBUG_STATS)`` is
-called. This featue was accidentally removed when implementing incremental
+called. This feature was accidentally removed when implementing incremental
GC.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-09-17-22-06-01.gh-issue-124188.aFqNAB.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-09-17-22-06-01.gh-issue-124188.aFqNAB.rst
new file mode 100644
index 00000000000000..0c2935fbe000bc
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-09-17-22-06-01.gh-issue-124188.aFqNAB.rst
@@ -0,0 +1,2 @@
+Fix reading and decoding a line from the source file witn non-UTF-8 encoding
+for syntax errors raised in the compiler.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-09-19-13-17-31.gh-issue-122878.4iFpsB.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-09-19-13-17-31.gh-issue-122878.4iFpsB.rst
new file mode 100644
index 00000000000000..85dd0fd769be68
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-09-19-13-17-31.gh-issue-122878.4iFpsB.rst
@@ -0,0 +1 @@
+Use the ``pager`` binary, if available (e.g. on Debian and derivatives), to display REPL ``help()``.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-09-23-13-25-27.gh-issue-65961.LDqXV2.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-09-23-13-25-27.gh-issue-65961.LDqXV2.rst
new file mode 100644
index 00000000000000..d380027f3c5776
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-09-23-13-25-27.gh-issue-65961.LDqXV2.rst
@@ -0,0 +1 @@
+Deprecate the setting and using ``__package__`` and ``__cached__``.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-09-23-15-23-14.gh-issue-123856.yrgJ9m.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-09-23-15-23-14.gh-issue-123856.yrgJ9m.rst
new file mode 100644
index 00000000000000..b5f423f3ff1c96
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-09-23-15-23-14.gh-issue-123856.yrgJ9m.rst
@@ -0,0 +1,2 @@
+Fix PyREPL failure when a keyboard interrupt is triggered after using a
+history search
diff --git a/Misc/NEWS.d/next/Documentation/2024-09-19-19-33-25.gh-issue-116622.M65UZ6.rst b/Misc/NEWS.d/next/Documentation/2024-09-19-19-33-25.gh-issue-116622.M65UZ6.rst
new file mode 100644
index 00000000000000..f047a8c6caa698
--- /dev/null
+++ b/Misc/NEWS.d/next/Documentation/2024-09-19-19-33-25.gh-issue-116622.M65UZ6.rst
@@ -0,0 +1 @@
+Add an Android platform guide, and flag modules not available on Android.
diff --git a/Misc/NEWS.d/next/IDLE/2024-09-21-23-12-18.gh-issue-112938.OeiDru.rst b/Misc/NEWS.d/next/IDLE/2024-09-21-23-12-18.gh-issue-112938.OeiDru.rst
new file mode 100644
index 00000000000000..0cd058eeffb1d5
--- /dev/null
+++ b/Misc/NEWS.d/next/IDLE/2024-09-21-23-12-18.gh-issue-112938.OeiDru.rst
@@ -0,0 +1 @@
+Fix uninteruptable hang when Shell gets rapid continuous output.
diff --git a/Misc/NEWS.d/next/Library/2020-12-22-18-08-12.bpo-41843.q9Nh2r.rst b/Misc/NEWS.d/next/Library/2020-12-22-18-08-12.bpo-41843.q9Nh2r.rst
new file mode 100644
index 00000000000000..4e525f7ed6a757
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2020-12-22-18-08-12.bpo-41843.q9Nh2r.rst
@@ -0,0 +1,2 @@
+Solaris now uses :func:`os.sendfile` fast-copy syscall for more efficient
+:mod:`shutil` file copy related functions.
diff --git a/Misc/NEWS.d/next/Library/2021-08-24-19-37-46.bpo-44864.KzxaDh.rst b/Misc/NEWS.d/next/Library/2021-08-24-19-37-46.bpo-44864.KzxaDh.rst
new file mode 100644
index 00000000000000..9610fa90ef0a98
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-08-24-19-37-46.bpo-44864.KzxaDh.rst
@@ -0,0 +1 @@
+Do not translate user-provided strings in :class:`argparse.ArgumentParser`.
diff --git a/Misc/NEWS.d/next/Library/2023-12-14-13-43-27.gh-issue-113008.jWYn8T.rst b/Misc/NEWS.d/next/Library/2023-12-14-13-43-27.gh-issue-113008.jWYn8T.rst
new file mode 100644
index 00000000000000..0f2a44299717c0
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-12-14-13-43-27.gh-issue-113008.jWYn8T.rst
@@ -0,0 +1 @@
+Correct argparse usage output for required, mutually exclusive groups containing a positional argument
diff --git a/Misc/NEWS.d/next/Library/2024-06-08-03-29-01.gh-issue-120254.h682ke.rst b/Misc/NEWS.d/next/Library/2024-06-08-03-29-01.gh-issue-120254.h682ke.rst
new file mode 100644
index 00000000000000..33ef1c91591c54
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-06-08-03-29-01.gh-issue-120254.h682ke.rst
@@ -0,0 +1 @@
+Added ``commands`` argument to :func:`pdb.set_trace` which allows users to send debugger commands from the source file.
diff --git a/Misc/NEWS.d/next/Library/2024-07-03-14-23-04.gh-issue-119004.L5MoUu.rst b/Misc/NEWS.d/next/Library/2024-07-03-14-23-04.gh-issue-119004.L5MoUu.rst
new file mode 100644
index 00000000000000..899bd163d36644
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-07-03-14-23-04.gh-issue-119004.L5MoUu.rst
@@ -0,0 +1,2 @@
+Fix a crash in :ref:`OrderedDict.__eq__ `
+when operands are mutated during the check. Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2024-07-23-12-38-14.gh-issue-122145.sTO8nX.rst b/Misc/NEWS.d/next/Library/2024-07-23-12-38-14.gh-issue-122145.sTO8nX.rst
new file mode 100644
index 00000000000000..a4282f12d9742a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-07-23-12-38-14.gh-issue-122145.sTO8nX.rst
@@ -0,0 +1,3 @@
+Fix an issue when reporting tracebacks corresponding to Python code
+emitting an empty AST body.
+Patch by Nikita Sobolev and Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2024-08-22-09-37-48.gh-issue-123213.owmXnP.rst b/Misc/NEWS.d/next/Library/2024-08-22-09-37-48.gh-issue-123213.owmXnP.rst
index 6bbd194b916ec4..5a31a00f2758f4 100644
--- a/Misc/NEWS.d/next/Library/2024-08-22-09-37-48.gh-issue-123213.owmXnP.rst
+++ b/Misc/NEWS.d/next/Library/2024-08-22-09-37-48.gh-issue-123213.owmXnP.rst
@@ -1,3 +1,3 @@
:meth:`xml.etree.ElementTree.Element.extend` and
:class:`~xml.etree.ElementTree.Element` assignment no longer hide the internal
-exception if an erronous generator is passed. Patch by Bar Harel.
+exception if an erroneous generator is passed. Patch by Bar Harel.
diff --git a/Misc/NEWS.d/next/Library/2024-09-06-01-35-11.gh-issue-123756.Ozbhke.rst b/Misc/NEWS.d/next/Library/2024-09-06-01-35-11.gh-issue-123756.Ozbhke.rst
new file mode 100644
index 00000000000000..258dd591fce767
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-09-06-01-35-11.gh-issue-123756.Ozbhke.rst
@@ -0,0 +1 @@
+Added a new argument ``mode`` to :class:`pdb.Pdb`. Only allow :mod:`pdb` from command line to use ``restart`` command.
diff --git a/Misc/NEWS.d/next/Library/2024-09-13-10-34-19.gh-issue-123934.yMe7mL.rst b/Misc/NEWS.d/next/Library/2024-09-13-10-34-19.gh-issue-123934.yMe7mL.rst
new file mode 100644
index 00000000000000..641c21331e3e54
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-09-13-10-34-19.gh-issue-123934.yMe7mL.rst
@@ -0,0 +1,2 @@
+Fix :class:`unittest.mock.MagicMock` resetting magic methods return values
+after ``.reset_mock(return_value=True)`` was called.
diff --git a/Misc/NEWS.d/next/Library/2024-09-16-12-31-48.gh-issue-123978.z3smEu.rst b/Misc/NEWS.d/next/Library/2024-09-16-12-31-48.gh-issue-123978.z3smEu.rst
new file mode 100644
index 00000000000000..e5b3229122b509
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-09-16-12-31-48.gh-issue-123978.z3smEu.rst
@@ -0,0 +1 @@
+Remove broken :func:`time.thread_time` and :func:`time.thread_time_ns` on NetBSD.
diff --git a/Misc/NEWS.d/next/Library/2024-09-18-17-45-52.gh-issue-124212.n6kIby.rst b/Misc/NEWS.d/next/Library/2024-09-18-17-45-52.gh-issue-124212.n6kIby.rst
new file mode 100644
index 00000000000000..7848f26511e282
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-09-18-17-45-52.gh-issue-124212.n6kIby.rst
@@ -0,0 +1 @@
+Fix invalid variable in :mod:`venv` handling of failed symlink on Windows
diff --git a/Misc/NEWS.d/next/Library/2024-09-19-03-46-59.gh-issue-87041.9Ox7Bv.rst b/Misc/NEWS.d/next/Library/2024-09-19-03-46-59.gh-issue-87041.9Ox7Bv.rst
new file mode 100644
index 00000000000000..47a5f0c7ba520f
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-09-19-03-46-59.gh-issue-87041.9Ox7Bv.rst
@@ -0,0 +1 @@
+Fix a bug in :mod:`argparse` where lengthy subparser argument help is incorrectly indented.
diff --git a/Misc/NEWS.d/next/Library/2024-09-19-10-36-18.gh-issue-81691.Hyhp_U.rst b/Misc/NEWS.d/next/Library/2024-09-19-10-36-18.gh-issue-81691.Hyhp_U.rst
new file mode 100644
index 00000000000000..8f0108502efde6
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-09-19-10-36-18.gh-issue-81691.Hyhp_U.rst
@@ -0,0 +1,3 @@
+Fix handling of multiple ``"--"`` (double dashes) in :mod:`argparse`. Only
+the first one has now been removed, all subsequent ones are now taken
+literally.
diff --git a/Misc/NEWS.d/next/Library/2024-09-19-11-47-39.gh-issue-124248.g7rufd.rst b/Misc/NEWS.d/next/Library/2024-09-19-11-47-39.gh-issue-124248.g7rufd.rst
new file mode 100644
index 00000000000000..1bd333f485a2ab
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-09-19-11-47-39.gh-issue-124248.g7rufd.rst
@@ -0,0 +1,2 @@
+Fixed potential crash when using :mod:`struct` to process zero-width
+'Pascal string' fields (``0p``).
diff --git a/Misc/NEWS.d/next/Library/2024-09-19-16-00-22.gh-issue-111513.6jHm02.rst b/Misc/NEWS.d/next/Library/2024-09-19-16-00-22.gh-issue-111513.6jHm02.rst
new file mode 100644
index 00000000000000..c6b85f9cd72255
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-09-19-16-00-22.gh-issue-111513.6jHm02.rst
@@ -0,0 +1 @@
+Improve the error message that may be raised by :meth:`datetime.date.fromtimestamp`.
diff --git a/Misc/NEWS.d/next/Library/2024-09-19-20-15-00.gh-issue-124217.j0KlQB.rst b/Misc/NEWS.d/next/Library/2024-09-19-20-15-00.gh-issue-124217.j0KlQB.rst
new file mode 100644
index 00000000000000..46f9866f8d427c
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-09-19-20-15-00.gh-issue-124217.j0KlQB.rst
@@ -0,0 +1 @@
+Add RFC 9637 reserved IPv6 block ``3fff::/20`` in :mod:`ipaddress` module.
diff --git a/Misc/NEWS.d/next/Library/2024-09-20-12-23-11.gh-issue-53780.mrV1zi.rst b/Misc/NEWS.d/next/Library/2024-09-20-12-23-11.gh-issue-53780.mrV1zi.rst
new file mode 100644
index 00000000000000..fb700c722c8a8b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-09-20-12-23-11.gh-issue-53780.mrV1zi.rst
@@ -0,0 +1 @@
+:mod:`argparse` now ignores the first ``"--"`` (double dash) between an option and command.
diff --git a/Misc/NEWS.d/next/Library/2024-09-20-18-23-19.gh-issue-100980.8nVAB6.rst b/Misc/NEWS.d/next/Library/2024-09-20-18-23-19.gh-issue-100980.8nVAB6.rst
new file mode 100644
index 00000000000000..2279c205caeced
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-09-20-18-23-19.gh-issue-100980.8nVAB6.rst
@@ -0,0 +1,3 @@
+The :attr:`~ctypes.Structure._fields_` attribute of
+:class:`ctypes.Structure` and :class:`~ctypes.Union` is no longer set if
+the setattr operation raises an error.
diff --git a/Misc/NEWS.d/next/Library/2024-09-21-19-02-37.gh-issue-59317.OAhNZZ.rst b/Misc/NEWS.d/next/Library/2024-09-21-19-02-37.gh-issue-59317.OAhNZZ.rst
new file mode 100644
index 00000000000000..0b1df9e3b7dea8
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-09-21-19-02-37.gh-issue-59317.OAhNZZ.rst
@@ -0,0 +1,2 @@
+Fix parsing positional argument with :ref:`nargs` equal to ``'?'`` or ``'*'``
+if it is preceded by an option and another positional argument.
diff --git a/Misc/NEWS.d/next/Library/2024-09-21-22-32-21.gh-issue-72795.naLmkX.rst b/Misc/NEWS.d/next/Library/2024-09-21-22-32-21.gh-issue-72795.naLmkX.rst
new file mode 100644
index 00000000000000..15c0918097367f
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-09-21-22-32-21.gh-issue-72795.naLmkX.rst
@@ -0,0 +1,4 @@
+Positional arguments with :ref:`nargs` equal to ``'*'`` or
+:data:`!argparse.REMAINDER` are no longer required. This allows to use
+positional argument with ``nargs='*'`` and without ``default`` in mutually
+exclusive group and improves error message about required arguments.
diff --git a/Misc/NEWS.d/next/Library/2024-09-21-23-56-41.gh-issue-63143.YKu-LQ.rst b/Misc/NEWS.d/next/Library/2024-09-21-23-56-41.gh-issue-63143.YKu-LQ.rst
new file mode 100644
index 00000000000000..cb031fd601a9bd
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-09-21-23-56-41.gh-issue-63143.YKu-LQ.rst
@@ -0,0 +1,3 @@
+Fix parsing mutually exclusive arguments in :mod:`argparse`. Arguments with
+the value identical to the default value (e.g. booleans, small integers,
+empty or 1-character strings) are no longer considered "not present".
diff --git a/Misc/NEWS.d/next/Library/2024-09-23-18-26-17.gh-issue-90562.Yj566G.rst b/Misc/NEWS.d/next/Library/2024-09-23-18-26-17.gh-issue-90562.Yj566G.rst
new file mode 100644
index 00000000000000..7a389fefc6c54b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-09-23-18-26-17.gh-issue-90562.Yj566G.rst
@@ -0,0 +1,3 @@
+Modify dataclasses to support zero-argument super() when ``slots=True`` is
+specified. This works by modifying all references to ``__class__`` to point
+to the newly created class.
diff --git a/Misc/NEWS.d/next/Library/2024-09-24-19-32-14.gh-issue-123014.zVcfkZ.rst b/Misc/NEWS.d/next/Library/2024-09-24-19-32-14.gh-issue-123014.zVcfkZ.rst
new file mode 100644
index 00000000000000..53dbabd9480ddb
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-09-24-19-32-14.gh-issue-123014.zVcfkZ.rst
@@ -0,0 +1,3 @@
+:func:`os.pidfd_open` and :func:`signal.pidfd_send_signal` are now
+unavailable when building against Android API levels older than 31, since
+the underlying system calls may cause a crash.
diff --git a/Misc/NEWS.d/next/Tests/2024-09-17-22-21-58.gh-issue-124190.3fWhiX.rst b/Misc/NEWS.d/next/Tests/2024-09-17-22-21-58.gh-issue-124190.3fWhiX.rst
new file mode 100644
index 00000000000000..819b1ca49235fc
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2024-09-17-22-21-58.gh-issue-124190.3fWhiX.rst
@@ -0,0 +1 @@
+Add capability to ignore entire files or directories in check warning CI tool
diff --git a/Misc/NEWS.d/next/Tests/2024-09-18-18-39-21.gh-issue-124213.AQq_xg.rst b/Misc/NEWS.d/next/Tests/2024-09-18-18-39-21.gh-issue-124213.AQq_xg.rst
new file mode 100644
index 00000000000000..021fbefb635af1
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2024-09-18-18-39-21.gh-issue-124213.AQq_xg.rst
@@ -0,0 +1,3 @@
+Detect whether the test suite is running inside a systemd-nspawn container
+with ``--suppress-sync=true`` option, and skip the ``test_os``
+and ``test_mmap`` tests that are failing in this scenario.
diff --git a/Misc/NEWS.d/next/Windows/2024-09-20-11-18-50.gh-issue-124254.iPin-L.rst b/Misc/NEWS.d/next/Windows/2024-09-20-11-18-50.gh-issue-124254.iPin-L.rst
new file mode 100644
index 00000000000000..b93e356edb501d
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2024-09-20-11-18-50.gh-issue-124254.iPin-L.rst
@@ -0,0 +1 @@
+Ensures experimental free-threaded binaries remain installed when updating.
diff --git a/Misc/NEWS.d/next/macOS/2024-09-07-12-14-54.gh-issue-123797.yFDeug.rst b/Misc/NEWS.d/next/macOS/2024-09-07-12-14-54.gh-issue-123797.yFDeug.rst
new file mode 100644
index 00000000000000..f126bd0d39bf59
--- /dev/null
+++ b/Misc/NEWS.d/next/macOS/2024-09-07-12-14-54.gh-issue-123797.yFDeug.rst
@@ -0,0 +1 @@
+Check for runtime availability of ``ptsname_r`` function on macos.
diff --git a/Misc/NEWS.d/next/macOS/2024-09-24-10-48-46.gh-issue-124448.bFMrS6.rst b/Misc/NEWS.d/next/macOS/2024-09-24-10-48-46.gh-issue-124448.bFMrS6.rst
new file mode 100644
index 00000000000000..6d57aa1ee190d6
--- /dev/null
+++ b/Misc/NEWS.d/next/macOS/2024-09-24-10-48-46.gh-issue-124448.bFMrS6.rst
@@ -0,0 +1 @@
+Update bundled Tcl/Tk in macOS installer to 8.6.15.
diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml
index 6036fc96fdd995..fe0a5e44f8fb15 100644
--- a/Misc/stable_abi.toml
+++ b/Misc/stable_abi.toml
@@ -2508,6 +2508,8 @@
[function.Py_TYPE]
added = '3.14'
+[function.Py_REFCNT]
+ added = '3.14'
[function.PyIter_NextItem]
added = '3.14'
[function.PyLong_FromInt32]
@@ -2527,4 +2529,10 @@
[function.PyLong_AsUInt64]
added = '3.14'
[const.Py_tp_vectorcall]
- added = '3.14'
\ No newline at end of file
+ added = '3.14'
+[function.PyType_GetBaseByToken]
+ added = '3.14'
+[const.Py_tp_token]
+ added = '3.14'
+[const.Py_TP_USE_SPEC]
+ added = '3.14'
diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c
index 2b23be7b753e34..951e6914ba67a4 100644
--- a/Modules/_ctypes/_ctypes.c
+++ b/Modules/_ctypes/_ctypes.c
@@ -500,7 +500,7 @@ CType_Type_dealloc(PyObject *self)
{
StgInfo *info = _PyStgInfo_FromType_NoState(self);
if (!info) {
- PyErr_WriteUnraisable(self);
+ PyErr_WriteUnraisable(NULL); // NULL avoids segfault here
}
if (info) {
PyMem_Free(info->ffi_type_pointer.elements);
@@ -560,6 +560,7 @@ static PyMethodDef ctype_methods[] = {
};
static PyType_Slot ctype_type_slots[] = {
+ {Py_tp_token, Py_TP_USE_SPEC},
{Py_tp_traverse, CType_Type_traverse},
{Py_tp_clear, CType_Type_clear},
{Py_tp_dealloc, CType_Type_dealloc},
@@ -569,7 +570,7 @@ static PyType_Slot ctype_type_slots[] = {
{0, NULL},
};
-static PyType_Spec pyctype_type_spec = {
+PyType_Spec pyctype_type_spec = {
.name = "_ctypes.CType_Type",
.basicsize = -(Py_ssize_t)sizeof(StgInfo),
.flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE |
@@ -1066,32 +1067,31 @@ CType_Type_repeat(PyObject *self, Py_ssize_t length)
return PyCArrayType_from_ctype(st, self, length);
}
-
static int
-PyCStructType_setattro(PyObject *self, PyObject *key, PyObject *value)
+_structunion_setattro(PyObject *self, PyObject *key, PyObject *value, int is_struct)
{
/* XXX Should we disallow deleting _fields_? */
- if (-1 == PyType_Type.tp_setattro(self, key, value))
- return -1;
+ if (PyUnicode_Check(key)
+ && _PyUnicode_EqualToASCIIString(key, "_fields_"))
+ {
+ if (PyCStructUnionType_update_stginfo(self, value, is_struct) < 0) {
+ return -1;
+ }
+ }
- if (value && PyUnicode_Check(key) &&
- _PyUnicode_EqualToASCIIString(key, "_fields_"))
- return PyCStructUnionType_update_stginfo(self, value, 1);
- return 0;
+ return PyType_Type.tp_setattro(self, key, value);
}
+static int
+PyCStructType_setattro(PyObject *self, PyObject *key, PyObject *value)
+{
+ return _structunion_setattro(self, key, value, 1);
+}
static int
UnionType_setattro(PyObject *self, PyObject *key, PyObject *value)
{
- /* XXX Should we disallow deleting _fields_? */
- if (-1 == PyType_Type.tp_setattro(self, key, value))
- return -1;
-
- if (PyUnicode_Check(key) &&
- _PyUnicode_EqualToASCIIString(key, "_fields_"))
- return PyCStructUnionType_update_stginfo(self, value, 0);
- return 0;
+ return _structunion_setattro(self, key, value, 0);
}
static PyType_Slot pycstruct_type_slots[] = {
diff --git a/Modules/_ctypes/ctypes.h b/Modules/_ctypes/ctypes.h
index 2eb1b6cae4d81b..738dcd1aaf8a01 100644
--- a/Modules/_ctypes/ctypes.h
+++ b/Modules/_ctypes/ctypes.h
@@ -108,6 +108,7 @@ get_module_state_by_def(PyTypeObject *cls)
}
+extern PyType_Spec pyctype_type_spec;
extern PyType_Spec carg_spec;
extern PyType_Spec cfield_spec;
extern PyType_Spec cthunk_spec;
@@ -490,16 +491,23 @@ PyStgInfo_FromAny(ctypes_state *state, PyObject *obj, StgInfo **result)
/* A variant of PyStgInfo_FromType that doesn't need the state,
* so it can be called from finalization functions when the module
- * state is torn down. Does no checks; cannot fail.
- * This inlines the current implementation PyObject_GetTypeData,
- * so it might break in the future.
+ * state is torn down.
*/
static inline StgInfo *
_PyStgInfo_FromType_NoState(PyObject *type)
{
- size_t type_basicsize =_Py_SIZE_ROUND_UP(PyType_Type.tp_basicsize,
- ALIGNOF_MAX_ALIGN_T);
- return (StgInfo *)((char *)type + type_basicsize);
+ PyTypeObject *PyCType_Type;
+ if (PyType_GetBaseByToken(Py_TYPE(type), &pyctype_type_spec, &PyCType_Type) < 0) {
+ return NULL;
+ }
+ if (PyCType_Type == NULL) {
+ PyErr_Format(PyExc_TypeError, "expected a ctypes type, got '%N'", type);
+ return NULL;
+ }
+
+ StgInfo *info = PyObject_GetTypeData(type, PyCType_Type);
+ Py_DECREF(PyCType_Type);
+ return info;
}
// Initialize StgInfo on a newly created type
diff --git a/Modules/_io/fileio.c b/Modules/_io/fileio.c
index 5d9d87d6118a75..8dae465fd20f8b 100644
--- a/Modules/_io/fileio.c
+++ b/Modules/_io/fileio.c
@@ -74,8 +74,13 @@ typedef struct {
signed int seekable : 2; /* -1 means unknown */
unsigned int closefd : 1;
char finalizing;
- unsigned int blksize;
- Py_off_t estimated_size;
+ /* Stat result which was grabbed at file open, useful for optimizing common
+ File I/O patterns to be more efficient. This is only guidance / an
+ estimate, as it is subject to Time-Of-Check to Time-Of-Use (TOCTOU)
+ issues / bugs. Both the underlying file descriptor and file may be
+ modified outside of the fileio object / Python (ex. gh-90102, GH-121941,
+ gh-109523). */
+ struct _Py_stat_struct *stat_atopen;
PyObject *weakreflist;
PyObject *dict;
} fileio;
@@ -199,8 +204,7 @@ fileio_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
self->writable = 0;
self->appending = 0;
self->seekable = -1;
- self->blksize = 0;
- self->estimated_size = -1;
+ self->stat_atopen = NULL;
self->closefd = 1;
self->weakreflist = NULL;
}
@@ -256,7 +260,6 @@ _io_FileIO___init___impl(fileio *self, PyObject *nameobj, const char *mode,
#elif !defined(MS_WINDOWS)
int *atomic_flag_works = NULL;
#endif
- struct _Py_stat_struct fdfstat;
int fstat_result;
int async_err = 0;
@@ -454,9 +457,14 @@ _io_FileIO___init___impl(fileio *self, PyObject *nameobj, const char *mode,
#endif
}
- self->blksize = DEFAULT_BUFFER_SIZE;
+ PyMem_Free(self->stat_atopen);
+ self->stat_atopen = PyMem_New(struct _Py_stat_struct, 1);
+ if (self->stat_atopen == NULL) {
+ PyErr_NoMemory();
+ goto error;
+ }
Py_BEGIN_ALLOW_THREADS
- fstat_result = _Py_fstat_noraise(self->fd, &fdfstat);
+ fstat_result = _Py_fstat_noraise(self->fd, self->stat_atopen);
Py_END_ALLOW_THREADS
if (fstat_result < 0) {
/* Tolerate fstat() errors other than EBADF. See Issue #25717, where
@@ -471,25 +479,21 @@ _io_FileIO___init___impl(fileio *self, PyObject *nameobj, const char *mode,
#endif
goto error;
}
+
+ PyMem_Free(self->stat_atopen);
+ self->stat_atopen = NULL;
}
else {
#if defined(S_ISDIR) && defined(EISDIR)
/* On Unix, open will succeed for directories.
In Python, there should be no file objects referring to
directories, so we need a check. */
- if (S_ISDIR(fdfstat.st_mode)) {
+ if (S_ISDIR(self->stat_atopen->st_mode)) {
errno = EISDIR;
PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, nameobj);
goto error;
}
#endif /* defined(S_ISDIR) */
-#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
- if (fdfstat.st_blksize > 1)
- self->blksize = fdfstat.st_blksize;
-#endif /* HAVE_STRUCT_STAT_ST_BLKSIZE */
- if (fdfstat.st_size < PY_SSIZE_T_MAX) {
- self->estimated_size = (Py_off_t)fdfstat.st_size;
- }
}
#if defined(MS_WINDOWS) || defined(__CYGWIN__)
@@ -521,6 +525,10 @@ _io_FileIO___init___impl(fileio *self, PyObject *nameobj, const char *mode,
internal_close(self);
_PyErr_ChainExceptions1(exc);
}
+ if (self->stat_atopen != NULL) {
+ PyMem_Free(self->stat_atopen);
+ self->stat_atopen = NULL;
+ }
done:
#ifdef MS_WINDOWS
@@ -553,6 +561,10 @@ fileio_dealloc(fileio *self)
if (_PyIOBase_finalize((PyObject *) self) < 0)
return;
_PyObject_GC_UNTRACK(self);
+ if (self->stat_atopen != NULL) {
+ PyMem_Free(self->stat_atopen);
+ self->stat_atopen = NULL;
+ }
if (self->weakreflist != NULL)
PyObject_ClearWeakRefs((PyObject *) self);
(void)fileio_clear(self);
@@ -725,20 +737,27 @@ _io_FileIO_readall_impl(fileio *self)
return err_closed();
}
- end = self->estimated_size;
+ if (self->stat_atopen != NULL && self->stat_atopen->st_size < _PY_READ_MAX) {
+ end = (Py_off_t)self->stat_atopen->st_size;
+ }
+ else {
+ end = -1;
+ }
if (end <= 0) {
/* Use a default size and resize as needed. */
bufsize = SMALLCHUNK;
}
else {
- /* This is probably a real file, so we try to allocate a
- buffer one byte larger than the rest of the file. If the
- calculation is right then we should get EOF without having
- to enlarge the buffer. */
+ /* This is probably a real file. */
if (end > _PY_READ_MAX - 1) {
bufsize = _PY_READ_MAX;
}
else {
+ /* In order to detect end of file, need a read() of at
+ least 1 byte which returns size 0. Oversize the buffer
+ by 1 byte so the I/O can be completed with two read()
+ calls (one for all data, one for EOF) without needing
+ to resize the buffer. */
bufsize = (size_t)end + 1;
}
@@ -1094,11 +1113,13 @@ _io_FileIO_truncate_impl(fileio *self, PyTypeObject *cls, PyObject *posobj)
return NULL;
}
- /* Sometimes a large file is truncated. While estimated_size is used as a
- estimate, that it is much larger than the actual size can result in a
- significant over allocation and sometimes a MemoryError / running out of
- memory. */
- self->estimated_size = pos;
+ /* Since the file was truncated, its size at open is no longer accurate
+ as an estimate. Clear out the stat result, and rely on dynamic resize
+ code if a readall is requested. */
+ if (self->stat_atopen != NULL) {
+ PyMem_Free(self->stat_atopen);
+ self->stat_atopen = NULL;
+ }
return posobj;
}
@@ -1229,16 +1250,27 @@ get_mode(fileio *self, void *closure)
return PyUnicode_FromString(mode_string(self));
}
+static PyObject *
+get_blksize(fileio *self, void *closure)
+{
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+ if (self->stat_atopen != NULL && self->stat_atopen->st_blksize > 1) {
+ return PyLong_FromLong(self->stat_atopen->st_blksize);
+ }
+#endif /* HAVE_STRUCT_STAT_ST_BLKSIZE */
+ return PyLong_FromLong(DEFAULT_BUFFER_SIZE);
+}
+
static PyGetSetDef fileio_getsetlist[] = {
{"closed", (getter)get_closed, NULL, "True if the file is closed"},
{"closefd", (getter)get_closefd, NULL,
"True if the file descriptor will be closed by close()."},
{"mode", (getter)get_mode, NULL, "String giving the file mode"},
+ {"_blksize", (getter)get_blksize, NULL, "Stat st_blksize if available"},
{NULL},
};
static PyMemberDef fileio_members[] = {
- {"_blksize", Py_T_UINT, offsetof(fileio, blksize), 0},
{"_finalizing", Py_T_BOOL, offsetof(fileio, finalizing), 0},
{"__weaklistoffset__", Py_T_PYSSIZET, offsetof(fileio, weakreflist), Py_READONLY},
{"__dictoffset__", Py_T_PYSSIZET, offsetof(fileio, dict), Py_READONLY},
diff --git a/Modules/_struct.c b/Modules/_struct.c
index f744193469e2dc..2ae5060ba34163 100644
--- a/Modules/_struct.c
+++ b/Modules/_struct.c
@@ -1669,9 +1669,16 @@ s_unpack_internal(PyStructObject *soself, const char *startfrom,
if (e->format == 's') {
v = PyBytes_FromStringAndSize(res, code->size);
} else if (e->format == 'p') {
- Py_ssize_t n = *(unsigned char*)res;
- if (n >= code->size)
- n = code->size - 1;
+ Py_ssize_t n;
+ if (code->size == 0) {
+ n = 0;
+ }
+ else {
+ n = *(unsigned char*)res;
+ if (n >= code->size) {
+ n = code->size - 1;
+ }
+ }
v = PyBytes_FromStringAndSize(res + 1, n);
} else {
v = e->unpack(state, res, e);
@@ -1982,8 +1989,12 @@ s_pack_internal(PyStructObject *soself, PyObject *const *args, int offset,
n = PyByteArray_GET_SIZE(v);
p = PyByteArray_AS_STRING(v);
}
- if (n > (code->size - 1))
+ if (code->size == 0) {
+ n = 0;
+ }
+ else if (n > (code->size - 1)) {
n = code->size - 1;
+ }
if (n > 0)
memcpy(res + 1, p, n);
if (n > 255)
diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c
index b3fb9ec056c6c2..cc88147dfcd7fb 100644
--- a/Modules/_testcapi/heaptype.c
+++ b/Modules/_testcapi/heaptype.c
@@ -410,6 +410,118 @@ pyobject_getitemdata(PyObject *self, PyObject *o)
}
+static PyObject *
+create_type_with_token(PyObject *module, PyObject *args)
+{
+ const char *name;
+ PyObject *py_token;
+ if (!PyArg_ParseTuple(args, "sO", &name, &py_token)) {
+ return NULL;
+ }
+ void *token = PyLong_AsVoidPtr(py_token);
+ if (token == Py_TP_USE_SPEC) {
+ // Py_TP_USE_SPEC requires the spec that at least outlives the class
+ static PyType_Slot slots[] = {
+ {Py_tp_token, Py_TP_USE_SPEC},
+ {0},
+ };
+ static PyType_Spec spec = {
+ .name = "_testcapi.DefaultTokenTest",
+ .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ .slots = slots,
+ };
+ PyObject *type = PyType_FromMetaclass(NULL, NULL, &spec, NULL);
+ if (!type) {
+ return NULL;
+ }
+ token = PyType_GetSlot((PyTypeObject *)type, Py_tp_token);
+ assert(!PyErr_Occurred());
+ Py_DECREF(type);
+ if (token != &spec) {
+ PyErr_SetString(PyExc_AssertionError,
+ "failed to convert token from Py_TP_USE_SPEC");
+ return NULL;
+ }
+ }
+ // Test non-NULL token that must also outlive the class
+ PyType_Slot slots[] = {
+ {Py_tp_token, token},
+ {0},
+ };
+ PyType_Spec spec = {
+ .name = name,
+ .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ .slots = slots,
+ };
+ return PyType_FromMetaclass(NULL, module, &spec, NULL);
+}
+
+static PyObject *
+get_tp_token(PyObject *self, PyObject *type)
+{
+ void *token = PyType_GetSlot((PyTypeObject *)type, Py_tp_token);
+ if (PyErr_Occurred()) {
+ return NULL;
+ }
+ return PyLong_FromVoidPtr(token);
+}
+
+static PyObject *
+pytype_getbasebytoken(PyObject *self, PyObject *args)
+{
+ PyTypeObject *type;
+ PyObject *py_token, *use_mro, *need_result;
+ if (!PyArg_ParseTuple(args, "OOOO",
+ &type, &py_token, &use_mro, &need_result)) {
+ return NULL;
+ }
+
+ PyObject *mro_save = NULL;
+ if (use_mro != Py_True) {
+ // Test internal detail: PyType_GetBaseByToken works even with
+ // types that are only partially initialized (or torn down):
+ // if tp_mro=NULL we fall back to tp_bases.
+ assert(PyType_Check(type));
+ mro_save = type->tp_mro;
+ type->tp_mro = NULL;
+ }
+
+ void *token = PyLong_AsVoidPtr(py_token);
+ PyObject *result;
+ int ret;
+ if (need_result == Py_True) {
+ ret = PyType_GetBaseByToken(type, token, (PyTypeObject **)&result);
+ }
+ else {
+ result = NULL;
+ ret = PyType_GetBaseByToken(type, token, NULL);
+ }
+
+ if (use_mro != Py_True) {
+ type->tp_mro = mro_save;
+ }
+ if (ret < 0) {
+ assert(result == NULL);
+ return NULL;
+ }
+ PyObject *py_ret = PyLong_FromLong(ret);
+ if (py_ret == NULL) {
+ goto error;
+ }
+ PyObject *tuple = PyTuple_New(2);
+ if (tuple == NULL) {
+ goto error;
+ }
+ PyTuple_SET_ITEM(tuple, 0, py_ret);
+ PyTuple_SET_ITEM(tuple, 1, result ? result : Py_None);
+ return tuple;
+error:
+ Py_XDECREF(py_ret);
+ Py_XDECREF(result);
+ return NULL;
+}
+
+
static PyMethodDef TestMethods[] = {
{"pytype_fromspec_meta", pytype_fromspec_meta, METH_O},
{"test_type_from_ephemeral_spec", test_type_from_ephemeral_spec, METH_NOARGS},
@@ -423,6 +535,9 @@ static PyMethodDef TestMethods[] = {
{"make_immutable_type_with_base", make_immutable_type_with_base, METH_O},
{"make_type_with_base", make_type_with_base, METH_O},
{"pyobject_getitemdata", pyobject_getitemdata, METH_O},
+ {"create_type_with_token", create_type_with_token, METH_VARARGS},
+ {"get_tp_token", get_tp_token, METH_O},
+ {"pytype_getbasebytoken", pytype_getbasebytoken, METH_VARARGS},
{NULL},
};
@@ -1287,6 +1402,8 @@ _PyTestCapi_Init_Heaptype(PyObject *m) {
&PyType_Type, m, &HeapCTypeMetaclassNullNew_spec, (PyObject *) &PyType_Type);
ADD("HeapCTypeMetaclassNullNew", HeapCTypeMetaclassNullNew);
+ ADD("Py_TP_USE_SPEC", PyLong_FromVoidPtr(Py_TP_USE_SPEC));
+
PyObject *HeapCCollection = PyType_FromMetaclass(
NULL, m, &HeapCCollection_spec, NULL);
if (HeapCCollection == NULL) {
diff --git a/Modules/_testcapi/watchers.c b/Modules/_testcapi/watchers.c
index 1eb0db2c2e6576..689863d098ad8a 100644
--- a/Modules/_testcapi/watchers.c
+++ b/Modules/_testcapi/watchers.c
@@ -8,6 +8,7 @@
#define Py_BUILD_CORE
#include "pycore_function.h" // FUNC_MAX_WATCHERS
#include "pycore_code.h" // CODE_MAX_WATCHERS
+#include "pycore_context.h" // CONTEXT_MAX_WATCHERS
/*[clinic input]
module _testcapi
@@ -622,6 +623,147 @@ allocate_too_many_func_watchers(PyObject *self, PyObject *args)
Py_RETURN_NONE;
}
+// Test contexct object watchers
+#define NUM_CONTEXT_WATCHERS 2
+static int context_watcher_ids[NUM_CONTEXT_WATCHERS] = {-1, -1};
+static int num_context_object_enter_events[NUM_CONTEXT_WATCHERS] = {0, 0};
+static int num_context_object_exit_events[NUM_CONTEXT_WATCHERS] = {0, 0};
+
+static int
+handle_context_watcher_event(int which_watcher, PyContextEvent event, PyContext *ctx) {
+ if (event == Py_CONTEXT_EVENT_ENTER) {
+ num_context_object_enter_events[which_watcher]++;
+ }
+ else if (event == Py_CONTEXT_EVENT_EXIT) {
+ num_context_object_exit_events[which_watcher]++;
+ }
+ else {
+ return -1;
+ }
+ return 0;
+}
+
+static int
+first_context_watcher_callback(PyContextEvent event, PyContext *ctx) {
+ return handle_context_watcher_event(0, event, ctx);
+}
+
+static int
+second_context_watcher_callback(PyContextEvent event, PyContext *ctx) {
+ return handle_context_watcher_event(1, event, ctx);
+}
+
+static int
+noop_context_event_handler(PyContextEvent event, PyContext *ctx) {
+ return 0;
+}
+
+static int
+error_context_event_handler(PyContextEvent event, PyContext *ctx) {
+ PyErr_SetString(PyExc_RuntimeError, "boom!");
+ return -1;
+}
+
+static PyObject *
+add_context_watcher(PyObject *self, PyObject *which_watcher)
+{
+ int watcher_id;
+ assert(PyLong_Check(which_watcher));
+ long which_l = PyLong_AsLong(which_watcher);
+ if (which_l == 0) {
+ watcher_id = PyContext_AddWatcher(first_context_watcher_callback);
+ context_watcher_ids[0] = watcher_id;
+ num_context_object_enter_events[0] = 0;
+ num_context_object_exit_events[0] = 0;
+ }
+ else if (which_l == 1) {
+ watcher_id = PyContext_AddWatcher(second_context_watcher_callback);
+ context_watcher_ids[1] = watcher_id;
+ num_context_object_enter_events[1] = 0;
+ num_context_object_exit_events[1] = 0;
+ }
+ else if (which_l == 2) {
+ watcher_id = PyContext_AddWatcher(error_context_event_handler);
+ }
+ else {
+ PyErr_Format(PyExc_ValueError, "invalid watcher %d", which_l);
+ return NULL;
+ }
+ if (watcher_id < 0) {
+ return NULL;
+ }
+ return PyLong_FromLong(watcher_id);
+}
+
+static PyObject *
+clear_context_watcher(PyObject *self, PyObject *watcher_id)
+{
+ assert(PyLong_Check(watcher_id));
+ long watcher_id_l = PyLong_AsLong(watcher_id);
+ if (PyContext_ClearWatcher(watcher_id_l) < 0) {
+ return NULL;
+ }
+ // reset static events counters
+ if (watcher_id_l >= 0) {
+ for (int i = 0; i < NUM_CONTEXT_WATCHERS; i++) {
+ if (watcher_id_l == context_watcher_ids[i]) {
+ context_watcher_ids[i] = -1;
+ num_context_object_enter_events[i] = 0;
+ num_context_object_exit_events[i] = 0;
+ }
+ }
+ }
+ Py_RETURN_NONE;
+}
+
+static PyObject *
+get_context_watcher_num_enter_events(PyObject *self, PyObject *watcher_id)
+{
+ assert(PyLong_Check(watcher_id));
+ long watcher_id_l = PyLong_AsLong(watcher_id);
+ assert(watcher_id_l >= 0 && watcher_id_l < NUM_CONTEXT_WATCHERS);
+ return PyLong_FromLong(num_context_object_enter_events[watcher_id_l]);
+}
+
+static PyObject *
+get_context_watcher_num_exit_events(PyObject *self, PyObject *watcher_id)
+{
+ assert(PyLong_Check(watcher_id));
+ long watcher_id_l = PyLong_AsLong(watcher_id);
+ assert(watcher_id_l >= 0 && watcher_id_l < NUM_CONTEXT_WATCHERS);
+ return PyLong_FromLong(num_context_object_exit_events[watcher_id_l]);
+}
+
+static PyObject *
+allocate_too_many_context_watchers(PyObject *self, PyObject *args)
+{
+ int watcher_ids[CONTEXT_MAX_WATCHERS + 1];
+ int num_watchers = 0;
+ for (unsigned long i = 0; i < sizeof(watcher_ids) / sizeof(int); i++) {
+ int watcher_id = PyContext_AddWatcher(noop_context_event_handler);
+ if (watcher_id == -1) {
+ break;
+ }
+ watcher_ids[i] = watcher_id;
+ num_watchers++;
+ }
+ PyObject *exc = PyErr_GetRaisedException();
+ for (int i = 0; i < num_watchers; i++) {
+ if (PyContext_ClearWatcher(watcher_ids[i]) < 0) {
+ PyErr_WriteUnraisable(Py_None);
+ break;
+ }
+ }
+ if (exc) {
+ PyErr_SetRaisedException(exc);
+ return NULL;
+ }
+ else if (PyErr_Occurred()) {
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
/*[clinic input]
_testcapi.set_func_defaults_via_capi
func: object
@@ -689,6 +831,16 @@ static PyMethodDef test_methods[] = {
_TESTCAPI_SET_FUNC_KWDEFAULTS_VIA_CAPI_METHODDEF
{"allocate_too_many_func_watchers", allocate_too_many_func_watchers,
METH_NOARGS, NULL},
+
+ // Code object watchers.
+ {"add_context_watcher", add_context_watcher, METH_O, NULL},
+ {"clear_context_watcher", clear_context_watcher, METH_O, NULL},
+ {"get_context_watcher_num_enter_events",
+ get_context_watcher_num_enter_events, METH_O, NULL},
+ {"get_context_watcher_num_exit_events",
+ get_context_watcher_num_exit_events, METH_O, NULL},
+ {"allocate_too_many_context_watchers",
+ (PyCFunction) allocate_too_many_context_watchers, METH_NOARGS, NULL},
{NULL},
};
diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c
index f4c3af7ed43b76..6170d127385152 100644
--- a/Modules/_testinternalcapi.c
+++ b/Modules/_testinternalcapi.c
@@ -682,13 +682,13 @@ set_eval_frame_default(PyObject *self, PyObject *Py_UNUSED(args))
static PyObject *
record_eval(PyThreadState *tstate, struct _PyInterpreterFrame *f, int exc)
{
- if (PyFunction_Check(f->f_funcobj)) {
+ if (PyStackRef_FunctionCheck(f->f_funcobj)) {
+ PyFunctionObject *func = _PyFrame_GetFunction(f);
PyObject *module = _get_current_module();
assert(module != NULL);
module_state *state = get_module_state(module);
Py_DECREF(module);
- int res = PyList_Append(state->record_list,
- ((PyFunctionObject *)f->f_funcobj)->func_name);
+ int res = PyList_Append(state->record_list, func->func_name);
if (res < 0) {
return NULL;
}
diff --git a/Modules/_testsinglephase.c b/Modules/_testsinglephase.c
index 066e0dbfb63fbf..2c59085d15b5be 100644
--- a/Modules/_testsinglephase.c
+++ b/Modules/_testsinglephase.c
@@ -1,7 +1,7 @@
/* Testing module for single-phase initialization of extension modules
-This file contains 8 distinct modules, meaning each as its own name
+This file contains several distinct modules, meaning each as its own name
and its own init function (PyInit_...). The default import system will
only find the one matching the filename: _testsinglephase. To load the
others you must do so manually. For example:
@@ -12,9 +12,13 @@ filename = _testsinglephase.__file__
loader = importlib.machinery.ExtensionFileLoader(name, filename)
spec = importlib.util.spec_from_file_location(name, filename, loader=loader)
mod = importlib._bootstrap._load(spec)
+loader.exec_module(module)
+sys.modules[modname] = module
```
-Here are the 8 modules:
+(The last two lines are just for completeness.)
+
+Here are the modules:
* _testsinglephase
* def: _testsinglephase_basic,
@@ -163,6 +167,11 @@ Here are the 8 modules:
* functions: none
* import system: same as _testsinglephase_with_state
+* _testsinglephase_circular
+ Regression test for gh-123880.
+ Does not have the common attributes & methods.
+ See test_singlephase_circular test.test_import.SinglephaseInitTests.
+
Module state:
* fields
@@ -740,3 +749,53 @@ PyInit__testsinglephase_with_state_check_cache_first(void)
}
return PyModule_Create(&_testsinglephase_with_state_check_cache_first);
}
+
+
+/****************************************/
+/* the _testsinglephase_circular module */
+/****************************************/
+
+static PyObject *static_module_circular;
+
+static PyObject *
+circularmod_clear_static_var(PyObject *self, PyObject *arg)
+{
+ PyObject *result = static_module_circular;
+ static_module_circular = NULL;
+ return result;
+}
+
+static struct PyModuleDef _testsinglephase_circular = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "_testsinglephase_circular",
+ .m_doc = PyDoc_STR("Test module _testsinglephase_circular"),
+ .m_methods = (PyMethodDef[]) {
+ {"clear_static_var", circularmod_clear_static_var, METH_NOARGS,
+ "Clear the static variable and return its previous value."},
+ {NULL, NULL} /* sentinel */
+ }
+};
+
+PyMODINIT_FUNC
+PyInit__testsinglephase_circular(void)
+{
+ if (!static_module_circular) {
+ static_module_circular = PyModule_Create(&_testsinglephase_circular);
+ if (!static_module_circular) {
+ return NULL;
+ }
+ }
+ static const char helper_mod_name[] = (
+ "test.test_import.data.circular_imports.singlephase");
+ PyObject *helper_mod = PyImport_ImportModule(helper_mod_name);
+ Py_XDECREF(helper_mod);
+ if (!helper_mod) {
+ return NULL;
+ }
+ if(PyModule_AddStringConstant(static_module_circular,
+ "helper_mod_name",
+ helper_mod_name) < 0) {
+ return NULL;
+ }
+ return Py_NewRef(static_module_circular);
+}
diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h
index 4b9dbac9af031f..749fe54598cc39 100644
--- a/Modules/clinic/posixmodule.c.h
+++ b/Modules/clinic/posixmodule.c.h
@@ -2367,7 +2367,7 @@ os__path_isjunction(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P
#endif /* defined(MS_WINDOWS) */
PyDoc_STRVAR(os__path_splitroot_ex__doc__,
-"_path_splitroot_ex($module, /, path)\n"
+"_path_splitroot_ex($module, /, p)\n"
"--\n"
"\n"
"Split a pathname into drive, root and tail.\n"
@@ -2393,7 +2393,7 @@ os__path_splitroot_ex(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
PyObject *ob_item[NUM_KEYWORDS];
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
- .ob_item = { &_Py_ID(path), },
+ .ob_item = { _Py_LATIN1_CHR('p'), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -2402,7 +2402,7 @@ os__path_splitroot_ex(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"path", NULL};
+ static const char * const _keywords[] = {"p", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "_path_splitroot_ex",
@@ -5954,7 +5954,7 @@ os_wait(PyObject *module, PyObject *Py_UNUSED(ignored))
#endif /* defined(HAVE_WAIT) */
-#if (defined(__linux__) && defined(__NR_pidfd_open))
+#if (defined(__linux__) && defined(__NR_pidfd_open) && !(defined(__ANDROID__) && __ANDROID_API__ < 31))
PyDoc_STRVAR(os_pidfd_open__doc__,
"pidfd_open($module, /, pid, flags=0)\n"
@@ -6013,7 +6013,7 @@ os_pidfd_open(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObjec
return return_value;
}
-#endif /* (defined(__linux__) && defined(__NR_pidfd_open)) */
+#endif /* (defined(__linux__) && defined(__NR_pidfd_open) && !(defined(__ANDROID__) && __ANDROID_API__ < 31)) */
#if defined(HAVE_SETNS)
@@ -12837,4 +12837,4 @@ os__create_environ(PyObject *module, PyObject *Py_UNUSED(ignored))
#ifndef OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF
#define OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF
#endif /* !defined(OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF) */
-/*[clinic end generated code: output=2fafa0d2814948f8 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=b93bbaaa8eb5b0ce input=a9049054013a1b77]*/
diff --git a/Modules/clinic/signalmodule.c.h b/Modules/clinic/signalmodule.c.h
index 1d3a143dfd8d39..986c0289f2bfcb 100644
--- a/Modules/clinic/signalmodule.c.h
+++ b/Modules/clinic/signalmodule.c.h
@@ -670,7 +670,7 @@ signal_pthread_kill(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
#endif /* defined(HAVE_PTHREAD_KILL) */
-#if (defined(__linux__) && defined(__NR_pidfd_send_signal))
+#if (defined(__linux__) && defined(__NR_pidfd_send_signal) && !(defined(__ANDROID__) && __ANDROID_API__ < 31))
PyDoc_STRVAR(signal_pidfd_send_signal__doc__,
"pidfd_send_signal($module, pidfd, signalnum, siginfo=None, flags=0, /)\n"
@@ -723,7 +723,7 @@ signal_pidfd_send_signal(PyObject *module, PyObject *const *args, Py_ssize_t nar
return return_value;
}
-#endif /* (defined(__linux__) && defined(__NR_pidfd_send_signal)) */
+#endif /* (defined(__linux__) && defined(__NR_pidfd_send_signal) && !(defined(__ANDROID__) && __ANDROID_API__ < 31)) */
#ifndef SIGNAL_ALARM_METHODDEF
#define SIGNAL_ALARM_METHODDEF
@@ -776,4 +776,4 @@ signal_pidfd_send_signal(PyObject *module, PyObject *const *args, Py_ssize_t nar
#ifndef SIGNAL_PIDFD_SEND_SIGNAL_METHODDEF
#define SIGNAL_PIDFD_SEND_SIGNAL_METHODDEF
#endif /* !defined(SIGNAL_PIDFD_SEND_SIGNAL_METHODDEF) */
-/*[clinic end generated code: output=6d8e17a32cef668f input=a9049054013a1b77]*/
+/*[clinic end generated code: output=c57b4b98fad6f4b8 input=a9049054013a1b77]*/
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index f24ab81cbcb77b..334350285f3b6f 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -125,6 +125,7 @@
# define HAVE_PWRITEV_RUNTIME __builtin_available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *)
# define HAVE_MKFIFOAT_RUNTIME __builtin_available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
# define HAVE_MKNODAT_RUNTIME __builtin_available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
+# define HAVE_PTSNAME_R_RUNTIME __builtin_available(macOS 10.13.4, iOS 11.3, tvOS 11.3, watchOS 4.3, *)
# define HAVE_POSIX_SPAWN_SETSID_RUNTIME __builtin_available(macOS 10.15, *)
@@ -206,6 +207,10 @@
# define HAVE_MKNODAT_RUNTIME (mknodat != NULL)
# endif
+# ifdef HAVE_PTSNAME_R
+# define HAVE_PTSNAME_R_RUNTIME (ptsname_r != NULL)
+# endif
+
#endif
#ifdef HAVE_FUTIMESAT
@@ -231,6 +236,7 @@
# define HAVE_PWRITEV_RUNTIME 1
# define HAVE_MKFIFOAT_RUNTIME 1
# define HAVE_MKNODAT_RUNTIME 1
+# define HAVE_PTSNAME_R_RUNTIME 1
#endif
@@ -5497,7 +5503,7 @@ os__path_isjunction_impl(PyObject *module, path_t *path)
/*[clinic input]
os._path_splitroot_ex
- path: path_t(make_wide=True, nonstrict=True)
+ p as path: path_t(make_wide=True, nonstrict=True)
Split a pathname into drive, root and tail.
@@ -5506,7 +5512,7 @@ The tail contains anything after the root.
static PyObject *
os__path_splitroot_ex_impl(PyObject *module, path_t *path)
-/*[clinic end generated code: output=4b0072b6cdf4b611 input=6eb76e9173412c92]*/
+/*[clinic end generated code: output=4b0072b6cdf4b611 input=4556b615c7cc13f2]*/
{
Py_ssize_t drvsize, rootsize;
PyObject *drv = NULL, *root = NULL, *tail = NULL, *result = NULL;
@@ -8635,6 +8641,19 @@ os_unlockpt_impl(PyObject *module, int fd)
#endif /* HAVE_UNLOCKPT */
#if defined(HAVE_PTSNAME) || defined(HAVE_PTSNAME_R)
+static PyObject *
+py_ptsname(int fd)
+{
+ // POSIX manpage: Upon failure, ptsname() shall return a null pointer
+ // and may set errno. Always initialize errno to avoid undefined behavior.
+ errno = 0;
+ char *name = ptsname(fd);
+ if (name == NULL) {
+ return posix_error();
+ }
+ return PyUnicode_DecodeFSDefault(name);
+}
+
/*[clinic input]
os.ptsname
@@ -8656,22 +8675,22 @@ os_ptsname_impl(PyObject *module, int fd)
int ret;
char name[MAXPATHLEN+1];
- ret = ptsname_r(fd, name, sizeof(name));
+ if (HAVE_PTSNAME_R_RUNTIME) {
+ ret = ptsname_r(fd, name, sizeof(name));
+ }
+ else {
+ // fallback to ptsname() if ptsname_r() is not available in runtime.
+ return py_ptsname(fd);
+ }
if (ret != 0) {
errno = ret;
return posix_error();
}
-#else
- char *name;
-
- name = ptsname(fd);
- /* POSIX manpage: Upon failure, ptsname() shall return a null pointer and may set errno.
- *MAY* set errno? Hmm... */
- if (name == NULL)
- return posix_error();
-#endif /* HAVE_PTSNAME_R */
return PyUnicode_DecodeFSDefault(name);
+#else
+ return py_ptsname(fd);
+#endif /* HAVE_PTSNAME_R */
}
#endif /* defined(HAVE_PTSNAME) || defined(HAVE_PTSNAME_R) */
@@ -10102,7 +10121,10 @@ os_wait_impl(PyObject *module)
}
#endif /* HAVE_WAIT */
-#if defined(__linux__) && defined(__NR_pidfd_open)
+
+// This system call always crashes on older Android versions.
+#if defined(__linux__) && defined(__NR_pidfd_open) && \
+ !(defined(__ANDROID__) && __ANDROID_API__ < 31)
/*[clinic input]
os.pidfd_open
pid: pid_t
@@ -17751,6 +17773,9 @@ PROBE(probe_futimens, HAVE_FUTIMENS_RUNTIME)
PROBE(probe_utimensat, HAVE_UTIMENSAT_RUNTIME)
#endif
+#ifdef HAVE_PTSNAME_R
+PROBE(probe_ptsname_r, HAVE_PTSNAME_R_RUNTIME)
+#endif
@@ -17891,6 +17916,10 @@ static const struct have_function {
{ "HAVE_UTIMENSAT", probe_utimensat },
#endif
+#ifdef HAVE_PTSNAME_R
+ { "HAVE_PTSNAME_R", probe_ptsname_r },
+#endif
+
#ifdef MS_WINDOWS
{ "MS_WINDOWS", NULL },
#endif
diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c
index 73bfcb756657b8..0e53a36bca55f0 100644
--- a/Modules/signalmodule.c
+++ b/Modules/signalmodule.c
@@ -1299,7 +1299,9 @@ signal_pthread_kill_impl(PyObject *module, unsigned long thread_id,
#endif /* #if defined(HAVE_PTHREAD_KILL) */
-#if defined(__linux__) && defined(__NR_pidfd_send_signal)
+// This system call always crashes on older Android versions.
+#if defined(__linux__) && defined(__NR_pidfd_send_signal) && \
+ !(defined(__ANDROID__) && __ANDROID_API__ < 31)
/*[clinic input]
signal.pidfd_send_signal
diff --git a/Modules/timemodule.c b/Modules/timemodule.c
index cfbb26b0259504..ee59fb73ac1e31 100644
--- a/Modules/timemodule.c
+++ b/Modules/timemodule.c
@@ -1288,9 +1288,14 @@ py_process_time(time_module_state *state, PyTime_t *tp,
/* clock_gettime */
// gh-115714: Don't use CLOCK_PROCESS_CPUTIME_ID on WASI.
+/* CLOCK_PROF is defined on NetBSD, but not supported.
+ * CLOCK_PROCESS_CPUTIME_ID is broken on NetBSD for the same reason as
+ * CLOCK_THREAD_CPUTIME_ID (see comment below).
+ */
#if defined(HAVE_CLOCK_GETTIME) \
&& (defined(CLOCK_PROCESS_CPUTIME_ID) || defined(CLOCK_PROF)) \
- && !defined(__wasi__)
+ && !defined(__wasi__) \
+ && !defined(__NetBSD__)
struct timespec ts;
if (HAVE_CLOCK_GETTIME_RUNTIME) {
@@ -1483,9 +1488,16 @@ _PyTime_GetThreadTimeWithInfo(PyTime_t *tp, _Py_clock_info_t *info)
return 0;
}
+/* CLOCK_THREAD_CPUTIME_ID is broken on NetBSD: the result of clock_gettime()
+ * includes the sleeping time, that defeats the purpose of the clock.
+ * Also, clock_getres() does not support it.
+ * https://github.com/python/cpython/issues/123978
+ * https://gnats.netbsd.org/57512
+ */
#elif defined(HAVE_CLOCK_GETTIME) && \
- defined(CLOCK_PROCESS_CPUTIME_ID) && \
- !defined(__EMSCRIPTEN__) && !defined(__wasi__)
+ defined(CLOCK_THREAD_CPUTIME_ID) && \
+ !defined(__EMSCRIPTEN__) && !defined(__wasi__) && \
+ !defined(__NetBSD__)
#define HAVE_THREAD_TIME
#if defined(__APPLE__) && _Py__has_attribute(availability)
@@ -1509,19 +1521,15 @@ _PyTime_GetThreadTimeWithInfo(PyTime_t *tp, _Py_clock_info_t *info)
return -1;
}
if (info) {
+ struct timespec res;
info->implementation = function;
info->monotonic = 1;
info->adjustable = 0;
- #if defined(__NetBSD__)
- info->resolution = 1e-9;
- #else
- struct timespec res;
if (clock_getres(clk_id, &res)) {
PyErr_SetFromErrno(PyExc_OSError);
return -1;
}
info->resolution = res.tv_sec + res.tv_nsec * 1e-9;
- #endif
}
if (_PyTime_FromTimespec(tp, &ts) < 0) {
diff --git a/Objects/complexobject.c b/Objects/complexobject.c
index 4a8dac6c53f529..787235c63a6be1 100644
--- a/Objects/complexobject.c
+++ b/Objects/complexobject.c
@@ -173,6 +173,8 @@ _Py_c_pow(Py_complex a, Py_complex b)
}
r.real = len*cos(phase);
r.imag = len*sin(phase);
+
+ _Py_ADJUST_ERANGE2(r.real, r.imag);
}
return r;
}
@@ -567,12 +569,12 @@ complex_pow(PyObject *v, PyObject *w, PyObject *z)
// a faster and more accurate algorithm.
if (b.imag == 0.0 && b.real == floor(b.real) && fabs(b.real) <= 100.0) {
p = c_powi(a, (long)b.real);
+ _Py_ADJUST_ERANGE2(p.real, p.imag);
}
else {
p = _Py_c_pow(a, b);
}
- _Py_ADJUST_ERANGE2(p.real, p.imag);
if (errno == EDOM) {
PyErr_SetString(PyExc_ZeroDivisionError,
"zero to a negative or complex power");
diff --git a/Objects/dictobject.c b/Objects/dictobject.c
index db21961bad266b..f38ab1b2865e99 100644
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -406,14 +406,12 @@ unicode_get_hash(PyObject *o)
void
_PyDict_DebugMallocStats(FILE *out)
{
-#ifdef WITH_FREELISTS
_PyDebugAllocatorStats(out, "free PyDictObject",
_Py_FREELIST_SIZE(dicts),
sizeof(PyDictObject));
_PyDebugAllocatorStats(out, "free PyDictKeysObject",
_Py_FREELIST_SIZE(dictkeys),
sizeof(PyDictKeysObject));
-#endif
}
#define DK_MASK(dk) (DK_SIZE(dk)-1)
diff --git a/Objects/floatobject.c b/Objects/floatobject.c
index 68fd3e54632950..dc3d8a3e5d0f4b 100644
--- a/Objects/floatobject.c
+++ b/Objects/floatobject.c
@@ -235,15 +235,10 @@ static void
float_dealloc(PyObject *op)
{
assert(PyFloat_Check(op));
-#ifdef WITH_FREELISTS
- if (PyFloat_CheckExact(op)) {
+ if (PyFloat_CheckExact(op))
_PyFloat_ExactDealloc(op);
- }
else
-#endif
- {
Py_TYPE(op)->tp_free(op);
- }
}
double
@@ -1975,12 +1970,10 @@ _PyFloat_FiniType(PyInterpreterState *interp)
void
_PyFloat_DebugMallocStats(FILE *out)
{
-#ifdef WITH_FREELISTS
_PyDebugAllocatorStats(out,
"free PyFloatObject",
_Py_FREELIST_SIZE(floats),
sizeof(PyFloatObject));
-#endif
}
diff --git a/Objects/frameobject.c b/Objects/frameobject.c
index fa34ac28981293..41674ccf3d2561 100644
--- a/Objects/frameobject.c
+++ b/Objects/frameobject.c
@@ -1634,7 +1634,7 @@ frame_dealloc(PyFrameObject *f)
/* Kill all local variables including specials, if we own them */
if (f->f_frame == frame && frame->owner == FRAME_OWNED_BY_FRAME_OBJECT) {
PyStackRef_CLEAR(frame->f_executable);
- Py_CLEAR(frame->f_funcobj);
+ PyStackRef_CLEAR(frame->f_funcobj);
Py_CLEAR(frame->f_locals);
_PyStackRef *locals = _PyFrame_GetLocalsArray(frame);
_PyStackRef *sp = frame->stackpointer;
@@ -1790,7 +1790,7 @@ static void
init_frame(_PyInterpreterFrame *frame, PyFunctionObject *func, PyObject *locals)
{
PyCodeObject *code = (PyCodeObject *)func->func_code;
- _PyFrame_Initialize(frame, (PyFunctionObject*)Py_NewRef(func),
+ _PyFrame_Initialize(frame, PyStackRef_FromPyObjectNew(func),
Py_XNewRef(locals), code, 0, NULL);
}
@@ -1860,15 +1860,17 @@ frame_init_get_vars(_PyInterpreterFrame *frame)
// here:
PyCodeObject *co = _PyFrame_GetCode(frame);
int lasti = _PyInterpreterFrame_LASTI(frame);
- if (!(lasti < 0 &&
- _PyFrame_GetBytecode(frame)->op.code == COPY_FREE_VARS &&
- PyFunction_Check(frame->f_funcobj))) {
+ if (!(lasti < 0
+ && _PyFrame_GetBytecode(frame)->op.code == COPY_FREE_VARS
+ && PyStackRef_FunctionCheck(frame->f_funcobj)))
+ {
/* Free vars are initialized */
return;
}
/* Free vars have not been initialized -- Do that */
- PyObject *closure = ((PyFunctionObject *)frame->f_funcobj)->func_closure;
+ PyFunctionObject *func = _PyFrame_GetFunction(frame);
+ PyObject *closure = func->func_closure;
int offset = PyUnstable_Code_GetFirstFree(co);
for (int i = 0; i < co->co_nfreevars; ++i) {
PyObject *o = PyTuple_GET_ITEM(closure, i);
diff --git a/Objects/genobject.c b/Objects/genobject.c
index 5dc8f926557b52..41cf8fdcc9dee8 100644
--- a/Objects/genobject.c
+++ b/Objects/genobject.c
@@ -58,10 +58,7 @@ gen_traverse(PyGenObject *gen, visitproc visit, void *arg)
else {
// We still need to visit the code object when the frame is cleared to
// ensure that it's kept alive if the reference is deferred.
- int err = _PyGC_VisitStackRef(&gen->gi_iframe.f_executable, visit, arg);
- if (err) {
- return err;
- }
+ _Py_VISIT_STACKREF(gen->gi_iframe.f_executable);
}
/* No need to visit cr_origin, because it's just tuples/str/int, so can't
participate in a reference cycle. */
diff --git a/Objects/listobject.c b/Objects/listobject.c
index 067d1a18d3bb75..8abe15d6674140 100644
--- a/Objects/listobject.c
+++ b/Objects/listobject.c
@@ -200,12 +200,10 @@ list_preallocate_exact(PyListObject *self, Py_ssize_t size)
void
_PyList_DebugMallocStats(FILE *out)
{
-#ifdef WITH_FREELISTS
_PyDebugAllocatorStats(out,
"free PyListObject",
_Py_FREELIST_SIZE(lists),
sizeof(PyListObject));
-#endif
}
PyObject *
diff --git a/Objects/object.c b/Objects/object.c
index 4b8b6c29266812..8a819dd336e421 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -816,7 +816,6 @@ PyObject_Bytes(PyObject *v)
return PyBytes_FromObject(v);
}
-#ifdef WITH_FREELISTS
static void
clear_freelist(struct _Py_freelist *freelist, int is_finalization,
freefunc dofree)
@@ -841,12 +840,9 @@ free_object(void *obj)
Py_DECREF(tp);
}
-#endif
-
void
_PyObject_ClearFreeLists(struct _Py_freelists *freelists, int is_finalization)
{
-#ifdef WITH_FREELISTS
// In the free-threaded build, freelists are per-PyThreadState and cleared in PyThreadState_Clear()
// In the default build, freelists are per-interpreter and cleared in finalize_interp_types()
clear_freelist(&freelists->floats, is_finalization, free_object);
@@ -866,7 +862,6 @@ _PyObject_ClearFreeLists(struct _Py_freelists *freelists, int is_finalization)
// stacks during GC, so emptying the free-list is counterproductive.
clear_freelist(&freelists->object_stack_chunks, 1, PyMem_RawFree);
}
-#endif
}
/*
@@ -3044,7 +3039,17 @@ Py_GetConstantBorrowed(unsigned int constant_id)
// Py_TYPE() implementation for the stable ABI
#undef Py_TYPE
-PyTypeObject* Py_TYPE(PyObject *ob)
+PyTypeObject*
+Py_TYPE(PyObject *ob)
{
return _Py_TYPE(ob);
}
+
+
+// Py_REFCNT() implementation for the stable ABI
+#undef Py_REFCNT
+Py_ssize_t
+Py_REFCNT(PyObject *ob)
+{
+ return _Py_REFCNT(ob);
+}
diff --git a/Objects/odictobject.c b/Objects/odictobject.c
index a9b801e70c9810..e151023dd764bf 100644
--- a/Objects/odictobject.c
+++ b/Objects/odictobject.c
@@ -796,6 +796,7 @@ _odict_clear_nodes(PyODictObject *od)
_odictnode_DEALLOC(node);
node = next;
}
+ od->od_state++;
}
/* There isn't any memory management of nodes past this point. */
@@ -806,24 +807,40 @@ _odict_keys_equal(PyODictObject *a, PyODictObject *b)
{
_ODictNode *node_a, *node_b;
+ // keep operands' state to detect undesired mutations
+ const size_t state_a = a->od_state;
+ const size_t state_b = b->od_state;
+
node_a = _odict_FIRST(a);
node_b = _odict_FIRST(b);
while (1) {
- if (node_a == NULL && node_b == NULL)
+ if (node_a == NULL && node_b == NULL) {
/* success: hit the end of each at the same time */
return 1;
- else if (node_a == NULL || node_b == NULL)
+ }
+ else if (node_a == NULL || node_b == NULL) {
/* unequal length */
return 0;
+ }
else {
- int res = PyObject_RichCompareBool(
- (PyObject *)_odictnode_KEY(node_a),
- (PyObject *)_odictnode_KEY(node_b),
- Py_EQ);
- if (res < 0)
+ PyObject *key_a = Py_NewRef(_odictnode_KEY(node_a));
+ PyObject *key_b = Py_NewRef(_odictnode_KEY(node_b));
+ int res = PyObject_RichCompareBool(key_a, key_b, Py_EQ);
+ Py_DECREF(key_a);
+ Py_DECREF(key_b);
+ if (res < 0) {
return res;
- else if (res == 0)
+ }
+ else if (a->od_state != state_a || b->od_state != state_b) {
+ PyErr_SetString(PyExc_RuntimeError,
+ "OrderedDict mutated during iteration");
+ return -1;
+ }
+ else if (res == 0) {
+ // This check comes after the check on the state
+ // in order for the exception to be set correctly.
return 0;
+ }
/* otherwise it must match, so move on to the next one */
node_a = _odictnode_NEXT(node_a);
diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c
index bd6e568191167a..f14f10ab9c0a46 100644
--- a/Objects/tupleobject.c
+++ b/Objects/tupleobject.c
@@ -1146,7 +1146,6 @@ maybe_freelist_push(PyTupleObject *op)
void
_PyTuple_DebugMallocStats(FILE *out)
{
-#ifdef WITH_FREELISTS
for (int i = 0; i < PyTuple_MAXSAVESIZE; i++) {
int len = i + 1;
char buf[128];
@@ -1155,5 +1154,4 @@ _PyTuple_DebugMallocStats(FILE *out)
_PyDebugAllocatorStats(out, buf, _Py_FREELIST_SIZE(tuples[i]),
_PyObject_VAR_SIZE(&PyTuple_Type, len));
}
-#endif
}
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 46e35ad4d0eca4..3ef3264f8aad66 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -3926,6 +3926,7 @@ type_new_alloc(type_new_ctx *ctx)
et->ht_name = Py_NewRef(ctx->name);
et->ht_module = NULL;
et->_ht_tpname = NULL;
+ et->ht_token = NULL;
#ifdef Py_GIL_DISABLED
_PyType_AssignId(et);
@@ -4984,6 +4985,11 @@ PyType_FromMetaclass(
}
}
break;
+ case Py_tp_token:
+ {
+ res->ht_token = slot->pfunc == Py_TP_USE_SPEC ? spec : slot->pfunc;
+ }
+ break;
default:
{
/* Copy other slots directly */
@@ -5144,8 +5150,15 @@ PyType_GetSlot(PyTypeObject *type, int slot)
PyErr_BadInternalCall();
return NULL;
}
+ int slot_offset = pyslot_offsets[slot].slot_offset;
- parent_slot = *(void**)((char*)type + pyslot_offsets[slot].slot_offset);
+ if (slot_offset >= (int)sizeof(PyTypeObject)) {
+ if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
+ return NULL;
+ }
+ }
+
+ parent_slot = *(void**)((char*)type + slot_offset);
if (parent_slot == NULL) {
return NULL;
}
@@ -5274,6 +5287,129 @@ _PyType_GetModuleByDef2(PyTypeObject *left, PyTypeObject *right,
return module;
}
+
+static PyTypeObject *
+get_base_by_token_recursive(PyTypeObject *type, void *token)
+{
+ assert(PyType_GetSlot(type, Py_tp_token) != token);
+ PyObject *bases = lookup_tp_bases(type);
+ assert(bases != NULL);
+ Py_ssize_t n = PyTuple_GET_SIZE(bases);
+ for (Py_ssize_t i = 0; i < n; i++) {
+ PyTypeObject *base = _PyType_CAST(PyTuple_GET_ITEM(bases, i));
+ if (!_PyType_HasFeature(base, Py_TPFLAGS_HEAPTYPE)) {
+ continue;
+ }
+ if (((PyHeapTypeObject*)base)->ht_token == token) {
+ return base;
+ }
+ base = get_base_by_token_recursive(base, token);
+ if (base != NULL) {
+ return base;
+ }
+ }
+ return NULL;
+}
+
+static inline PyTypeObject *
+get_base_by_token_from_mro(PyTypeObject *type, void *token)
+{
+ // Bypass lookup_tp_mro() as PyType_IsSubtype() does
+ PyObject *mro = type->tp_mro;
+ assert(mro != NULL);
+ assert(PyTuple_Check(mro));
+ // mro_invoke() ensures that the type MRO cannot be empty.
+ assert(PyTuple_GET_SIZE(mro) >= 1);
+ // Also, the first item in the MRO is the type itself, which is supposed
+ // to be already checked by the caller. We skip it in the loop.
+ assert(PyTuple_GET_ITEM(mro, 0) == (PyObject *)type);
+ assert(PyType_GetSlot(type, Py_tp_token) != token);
+
+ Py_ssize_t n = PyTuple_GET_SIZE(mro);
+ for (Py_ssize_t i = 1; i < n; i++) {
+ PyTypeObject *base = _PyType_CAST(PyTuple_GET_ITEM(mro, i));
+ if (!_PyType_HasFeature(base, Py_TPFLAGS_HEAPTYPE)) {
+ continue;
+ }
+ if (((PyHeapTypeObject*)base)->ht_token == token) {
+ return base;
+ }
+ }
+ return NULL;
+}
+
+static int
+check_base_by_token(PyTypeObject *type, void *token) {
+ // Chain the branches, which will be optimized exclusive here
+ if (token == NULL) {
+ PyErr_Format(PyExc_SystemError,
+ "PyType_GetBaseByToken called with token=NULL");
+ return -1;
+ }
+ else if (!PyType_Check(type)) {
+ PyErr_Format(PyExc_TypeError,
+ "expected a type, got a '%T' object", type);
+ return -1;
+ }
+ else if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
+ return 0;
+ }
+ else if (((PyHeapTypeObject*)type)->ht_token == token) {
+ return 1;
+ }
+ else if (type->tp_mro != NULL) {
+ // This will not be inlined
+ return get_base_by_token_from_mro(type, token) ? 1 : 0;
+ }
+ else {
+ return get_base_by_token_recursive(type, token) ? 1 : 0;
+ }
+}
+
+int
+PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result)
+{
+ if (result == NULL) {
+ // If the `result` is checked only once here, the subsequent
+ // branches will become trivial to optimize.
+ return check_base_by_token(type, token);
+ }
+ if (token == NULL || !PyType_Check(type)) {
+ *result = NULL;
+ return check_base_by_token(type, token);
+ }
+
+ // Chain the branches, which will be optimized exclusive here
+ PyTypeObject *base;
+ if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
+ // No static type has a heaptype superclass,
+ // which is ensured by type_ready_mro().
+ *result = NULL;
+ return 0;
+ }
+ else if (((PyHeapTypeObject*)type)->ht_token == token) {
+ *result = (PyTypeObject *)Py_NewRef(type);
+ return 1;
+ }
+ else if (type->tp_mro != NULL) {
+ // Expect this to be inlined
+ base = get_base_by_token_from_mro(type, token);
+ }
+ else {
+ base = get_base_by_token_recursive(type, token);
+ }
+
+ if (base != NULL) {
+ *result = (PyTypeObject *)Py_NewRef(base);
+ return 1;
+ }
+ else {
+ *result = NULL;
+ return 0;
+ }
+}
+
+
void *
PyObject_GetTypeData(PyObject *obj, PyTypeObject *cls)
{
@@ -5966,6 +6102,7 @@ type_dealloc(PyObject *self)
#ifdef Py_GIL_DISABLED
_PyType_ReleaseId(et);
#endif
+ et->ht_token = NULL;
Py_TYPE(type)->tp_free((PyObject *)type);
}
diff --git a/Objects/typeslots.inc b/Objects/typeslots.inc
index ffb85ff56adff1..642160fe0bd8bc 100644
--- a/Objects/typeslots.inc
+++ b/Objects/typeslots.inc
@@ -81,3 +81,4 @@
{-1, offsetof(PyTypeObject, tp_finalize)},
{offsetof(PyAsyncMethods, am_send), offsetof(PyTypeObject, tp_as_async)},
{-1, offsetof(PyTypeObject, tp_vectorcall)},
+{-1, offsetof(PyHeapTypeObject, ht_token)},
diff --git a/Objects/typeslots.py b/Objects/typeslots.py
index 8ab05f91be12b0..c7f8a33bb1e74e 100755
--- a/Objects/typeslots.py
+++ b/Objects/typeslots.py
@@ -13,7 +13,11 @@ def generate_typeslots(out=sys.stdout):
continue
member = m.group(1)
- if member.startswith("tp_"):
+ if member == "tp_token":
+ # The heap type structure (ht_*) is an implementation detail;
+ # the public slot for it has a familiar `tp_` prefix
+ member = '{-1, offsetof(PyHeapTypeObject, ht_token)}'
+ elif member.startswith("tp_"):
member = f'{{-1, offsetof(PyTypeObject, {member})}}'
elif member.startswith("am_"):
member = (f'{{offsetof(PyAsyncMethods, {member}),'+
diff --git a/Objects/typevarobject.c b/Objects/typevarobject.c
index 3c96850589d378..d3656155fae330 100644
--- a/Objects/typevarobject.c
+++ b/Objects/typevarobject.c
@@ -372,10 +372,10 @@ caller(void)
if (f == NULL) {
Py_RETURN_NONE;
}
- if (f == NULL || f->f_funcobj == NULL) {
+ if (f == NULL || PyStackRef_IsNull(f->f_funcobj)) {
Py_RETURN_NONE;
}
- PyObject *r = PyFunction_GetModule(f->f_funcobj);
+ PyObject *r = PyFunction_GetModule(PyStackRef_AsPyObjectBorrow(f->f_funcobj));
if (!r) {
PyErr_Clear();
Py_RETURN_NONE;
diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c
index 2494c989544ca0..e9589cfe44f3bf 100644
--- a/Objects/unicodeobject.c
+++ b/Objects/unicodeobject.c
@@ -2694,11 +2694,6 @@ unicode_fromformat_write_wcstr(_PyUnicodeWriter *writer, const wchar_t *str,
#define F_SIZE 3
#define F_PTRDIFF 4
#define F_INTMAX 5
-static const char * const formats[] = {"%d", "%ld", "%lld", "%zd", "%td", "%jd"};
-static const char * const formats_o[] = {"%o", "%lo", "%llo", "%zo", "%to", "%jo"};
-static const char * const formats_u[] = {"%u", "%lu", "%llu", "%zu", "%tu", "%ju"};
-static const char * const formats_x[] = {"%x", "%lx", "%llx", "%zx", "%tx", "%jx"};
-static const char * const formats_X[] = {"%X", "%lX", "%llX", "%zX", "%tX", "%jX"};
static const char*
unicode_fromformat_arg(_PyUnicodeWriter *writer,
@@ -2840,47 +2835,44 @@ unicode_fromformat_arg(_PyUnicodeWriter *writer,
case 'd': case 'i':
case 'o': case 'u': case 'x': case 'X':
{
- /* used by sprintf */
char buffer[MAX_INTMAX_CHARS];
- const char *fmt = NULL;
- switch (*f) {
- case 'o': fmt = formats_o[sizemod]; break;
- case 'u': fmt = formats_u[sizemod]; break;
- case 'x': fmt = formats_x[sizemod]; break;
- case 'X': fmt = formats_X[sizemod]; break;
- default: fmt = formats[sizemod]; break;
- }
- int issigned = (*f == 'd' || *f == 'i');
+
+ // Fill buffer using sprinf, with one of many possible format
+ // strings, like "%llX" for `long long` in hexadecimal.
+ // The type/size is in `sizemod`; the format is in `*f`.
+
+ // Use macros with nested switches to keep the sprintf format strings
+ // as compile-time literals, avoiding warnings and maybe allowing
+ // optimizations.
+
+ // `SPRINT` macro does one sprintf
+ // Example usage: SPRINT("l", "X", unsigned long) expands to
+ // sprintf(buffer, "%" "l" "X", va_arg(*vargs, unsigned long))
+ #define SPRINT(SIZE_SPEC, FMT_CHAR, TYPE) \
+ sprintf(buffer, "%" SIZE_SPEC FMT_CHAR, va_arg(*vargs, TYPE))
+
+ // One inner switch to handle all format variants
+ #define DO_SPRINTS(SIZE_SPEC, SIGNED_TYPE, UNSIGNED_TYPE) \
+ switch (*f) { \
+ case 'o': len = SPRINT(SIZE_SPEC, "o", UNSIGNED_TYPE); break; \
+ case 'u': len = SPRINT(SIZE_SPEC, "u", UNSIGNED_TYPE); break; \
+ case 'x': len = SPRINT(SIZE_SPEC, "x", UNSIGNED_TYPE); break; \
+ case 'X': len = SPRINT(SIZE_SPEC, "X", UNSIGNED_TYPE); break; \
+ default: len = SPRINT(SIZE_SPEC, "d", SIGNED_TYPE); break; \
+ }
+
+ // Outer switch to handle all the sizes/types
switch (sizemod) {
- case F_LONG:
- len = issigned ?
- sprintf(buffer, fmt, va_arg(*vargs, long)) :
- sprintf(buffer, fmt, va_arg(*vargs, unsigned long));
- break;
- case F_LONGLONG:
- len = issigned ?
- sprintf(buffer, fmt, va_arg(*vargs, long long)) :
- sprintf(buffer, fmt, va_arg(*vargs, unsigned long long));
- break;
- case F_SIZE:
- len = issigned ?
- sprintf(buffer, fmt, va_arg(*vargs, Py_ssize_t)) :
- sprintf(buffer, fmt, va_arg(*vargs, size_t));
- break;
- case F_PTRDIFF:
- len = sprintf(buffer, fmt, va_arg(*vargs, ptrdiff_t));
- break;
- case F_INTMAX:
- len = issigned ?
- sprintf(buffer, fmt, va_arg(*vargs, intmax_t)) :
- sprintf(buffer, fmt, va_arg(*vargs, uintmax_t));
- break;
- default:
- len = issigned ?
- sprintf(buffer, fmt, va_arg(*vargs, int)) :
- sprintf(buffer, fmt, va_arg(*vargs, unsigned int));
- break;
+ case F_LONG: DO_SPRINTS("l", long, unsigned long); break;
+ case F_LONGLONG: DO_SPRINTS("ll", long long, unsigned long long); break;
+ case F_SIZE: DO_SPRINTS("z", Py_ssize_t, size_t); break;
+ case F_PTRDIFF: DO_SPRINTS("t", ptrdiff_t, ptrdiff_t); break;
+ case F_INTMAX: DO_SPRINTS("j", intmax_t, uintmax_t); break;
+ default: DO_SPRINTS("", int, unsigned int); break;
}
+ #undef SPRINT
+ #undef DO_SPRINTS
+
assert(len >= 0);
int sign = (buffer[0] == '-');
diff --git a/PC/pyconfig.h.in b/PC/pyconfig.h.in
index f44e41c2e72f84..503f3193e2803e 100644
--- a/PC/pyconfig.h.in
+++ b/PC/pyconfig.h.in
@@ -531,9 +531,6 @@ Py_NO_ENABLE_SHARED to find out. Also support MS_NO_COREDLL for b/w compat */
/* Define if you want to compile in mimalloc memory allocator. */
#define WITH_MIMALLOC 1
-/* Define if you want to compile in object freelists optimization */
-#define WITH_FREELISTS 1
-
/* Define if you have clock. */
/* #define HAVE_CLOCK */
diff --git a/PC/python3dll.c b/PC/python3dll.c
index 1845334b244d8c..6b8208ab90bd95 100755
--- a/PC/python3dll.c
+++ b/PC/python3dll.c
@@ -81,6 +81,7 @@ EXPORT_FUNC(Py_Main)
EXPORT_FUNC(Py_MakePendingCalls)
EXPORT_FUNC(Py_NewInterpreter)
EXPORT_FUNC(Py_NewRef)
+EXPORT_FUNC(Py_REFCNT)
EXPORT_FUNC(Py_ReprEnter)
EXPORT_FUNC(Py_ReprLeave)
EXPORT_FUNC(Py_SetPath)
@@ -651,6 +652,7 @@ EXPORT_FUNC(PyType_FromSpec)
EXPORT_FUNC(PyType_FromSpecWithBases)
EXPORT_FUNC(PyType_GenericAlloc)
EXPORT_FUNC(PyType_GenericNew)
+EXPORT_FUNC(PyType_GetBaseByToken)
EXPORT_FUNC(PyType_GetFlags)
EXPORT_FUNC(PyType_GetFullyQualifiedName)
EXPORT_FUNC(PyType_GetModule)
diff --git a/PCbuild/build.bat b/PCbuild/build.bat
index 6c76f09a071312..abe649553756a7 100644
--- a/PCbuild/build.bat
+++ b/PCbuild/build.bat
@@ -8,7 +8,7 @@ echo.version(s) of Microsoft Visual Studio to be installed (see readme.txt).
echo.
echo.After the flags recognized by this script, up to 9 arguments to be passed
echo.directly to MSBuild may be passed. If the argument contains an '=', the
-echo.entire argument must be quoted (e.g. `%~nx0 "/p:PlatformToolset=v100"`).
+echo.entire argument must be quoted (e.g. `%~nx0 "/p:PlatformToolset=v141"`).
echo.Alternatively you can put extra flags for MSBuild in a file named
echo.`msbuild.rsp` in the `PCbuild` directory, one flag per line. This file
echo.will be picked automatically by MSBuild. Flags put in this file does not
diff --git a/PCbuild/env.bat b/PCbuild/env.bat
index 2820e304582cff..cf4638b7aa63a7 100644
--- a/PCbuild/env.bat
+++ b/PCbuild/env.bat
@@ -4,8 +4,8 @@ rem command window. However, most builds of Python will ignore the version
rem of the tools on PATH and use PlatformToolset instead. Ideally, both sets of
rem tools should be the same version to avoid potential conflicts.
rem
-rem To build Python with an earlier toolset, pass "/p:PlatformToolset=v100" (or
-rem 'v110', 'v120' or 'v140') to the build script.
+rem To build Python with an earlier toolset, pass "/p:PlatformToolset=v141" (or
+rem 'v142', 'v143') to the build script.
echo Build environments: x86, amd64, x86_amd64
echo.
@@ -20,8 +20,7 @@ call "%VSTOOLS%" %_ARGS%
exit /B 0
:skip_vswhere
-if not defined VSTOOLS set VSTOOLS=%VS140COMNTOOLS%
-if not defined VSTOOLS set VSTOOLS=%VS120COMNTOOLS%
-if not defined VSTOOLS set VSTOOLS=%VS110COMNTOOLS%
-if not defined VSTOOLS set VSTOOLS=%VS100COMNTOOLS%
+if not defined VSTOOLS set VSTOOLS=%VS143COMNTOOLS%
+if not defined VSTOOLS set VSTOOLS=%VS142COMNTOOLS%
+if not defined VSTOOLS set VSTOOLS=%VS141COMNTOOLS%
call "%VSTOOLS%..\..\VC\vcvarsall.bat" %_ARGS%
diff --git a/PCbuild/find_msbuild.bat b/PCbuild/find_msbuild.bat
index ce7e71efa31f6c..82dd34beede6ee 100644
--- a/PCbuild/find_msbuild.bat
+++ b/PCbuild/find_msbuild.bat
@@ -39,16 +39,6 @@
@if defined MSBUILD @if exist %MSBUILD% (set _Py_MSBuild_Source=Visual Studio installation) & goto :found
:skip_vswhere
-@rem VS 2015 and earlier register MSBuild separately, so we can find it.
-@reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSBuild\ToolsVersions\14.0" /v MSBuildToolsPath /reg:32 >nul 2>nul
-@if NOT ERRORLEVEL 1 @for /F "tokens=1,2*" %%i in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSBuild\ToolsVersions\14.0" /v MSBuildToolsPath /reg:32') DO @(
- @if "%%i"=="MSBuildToolsPath" @if exist "%%k\msbuild.exe" @(set MSBUILD="%%k\msbuild.exe")
-)
-@if exist %MSBUILD% (set _Py_MSBuild_Source=registry) & goto :found
-
-
-@exit /b 1
-
:found
@pushd %MSBUILD% >nul 2>nul
@if not ERRORLEVEL 1 @(
diff --git a/PCbuild/get_externals.bat b/PCbuild/get_externals.bat
index a1a67966182863..137c94789e1809 100644
--- a/PCbuild/get_externals.bat
+++ b/PCbuild/get_externals.bat
@@ -45,7 +45,7 @@ if "%ORG%"=="" (set ORG=python)
call "%PCBUILD%\find_python.bat" "%PYTHON%"
if NOT DEFINED PYTHON (
- where /Q git || echo Python 3.6 could not be found or installed, and git.exe is not on your PATH && exit /B 1
+ where /Q git || echo Python 3.10 or later could not be found or installed, and git.exe is not on your PATH && exit /B 1
)
echo.Fetching external libraries...
diff --git a/PCbuild/python.props b/PCbuild/python.props
index c8ecdb4515ae9a..6e90178f4ea8ab 100644
--- a/PCbuild/python.props
+++ b/PCbuild/python.props
@@ -6,7 +6,7 @@
Release