diff --git a/Doc/deprecations/pending-removal-in-3.20.rst b/Doc/deprecations/pending-removal-in-3.20.rst index 011565dfbb090d4..d771764502c9103 100644 --- a/Doc/deprecations/pending-removal-in-3.20.rst +++ b/Doc/deprecations/pending-removal-in-3.20.rst @@ -53,3 +53,12 @@ Pending removal in Python 3.20 * Creating instances of abstract AST nodes (such as :class:`ast.AST` or :class:`!ast.expr`) is deprecated and will raise an error in Python 3.20. + +* :mod:`typing`: + + * It is deprecated to call :func:`isinstance` and :func:`issubclass` checks on + protocol classes that were not explicitly decorated with :func:`!runtime_checkable` + but that inherit from a runtime-checkable protocol class. + This will raise a :exc:`TypeError` in Python 3.20. + + (Contributed by Bartosz Sławecki in :gh:`132604`.) diff --git a/Doc/library/difflib.rst b/Doc/library/difflib.rst index 2176f1b49d0f20f..374af16b2b6f98b 100644 --- a/Doc/library/difflib.rst +++ b/Doc/library/difflib.rst @@ -13,49 +13,86 @@ -------------- -This module provides classes and functions for comparing sequences. It -can be used for example, for comparing files, and can produce information -about file differences in various formats, including HTML and context and unified -diffs. For comparing directories and files, see also, the :mod:`filecmp` module. - - -.. class:: SequenceMatcher - :noindex: - - This is a flexible class for comparing pairs of sequences of any type, so long - as the sequence elements are :term:`hashable`. The basic algorithm predates, and is a - little fancier than, an algorithm published in the late 1980's by Ratcliff and - Obershelp under the hyperbolic name "gestalt pattern matching." The idea is to - find the longest contiguous matching subsequence that contains no "junk" - elements; these "junk" elements are ones that are uninteresting in some - sense, such as blank lines or whitespace. (Handling junk is an - extension to the Ratcliff and Obershelp algorithm.) The same - idea is then applied recursively to the pieces of the sequences to the left and - to the right of the matching subsequence. This does not yield minimal edit - sequences, but does tend to yield matches that "look right" to people. - - **Timing:** The basic Ratcliff-Obershelp algorithm is cubic time in the worst - case and quadratic time in the expected case. :class:`SequenceMatcher` is - quadratic time for the worst case and has expected-case behavior dependent in a - complicated way on how many elements the sequences have in common; best case - time is linear. - - **Junk**: :class:`SequenceMatcher` accepts an ``isjunk`` predicate and an - ``autojunk`` flag. Items that are considered as junk will not be considered - to find similar content blocks. This can produce better results for humans - (typically breaking on whitespace) and faster (because it reduces the number - of possible combinations). But it can also cause pathological cases where - too many items considered junk cause an unexpectedly large (but correct) - diff result. - You should consider tuning them or turning them off depending on your data. - Moreover, only the second sequence is inspected for junk. This causes the diff - output to not be symmetrical. - When ``autojunk=True``, it will consider as junk the items that account for more - than 1% of the sequence, if it is at least 200 items long. +This module provides classes and functions for comparing sequences. +Most of them compare sequences of text lines (for example lists of strings, +or :term:`file objects `) and +produce :dfn:`diffs` -- reports on the differences. +Diffs can be produced in in various formats, including HTML and context +and unified diffs -- formats produced by tools like +:manpage:`diff ` and :manpage:`git diff `. - .. versionchanged:: 3.2 - Added the *autojunk* parameter. +Comparisons are done using a matching algorithm implemented in +:class:`SequenceMatcher` -- a flexible class for comparing pairs of sequences +of any type, not just text, so long as the sequence elements are +:term:`hashable`. + + +.. _difflib-junk: + +Junk heuristic +-------------- + +:mod:`!difflib` uses a :dfn:`junk` heuristic: some items are deemed to be +:dfn:`junk`, and ignored when searching for similarities. +Ideally, these are uninteresting or common items, such as blank lines +or whitespace. + +This heuristic can speed the algorithm up (because it reduces the number of +possible combinations) and it can produce results that are more understandable +for humans (typically breaking on whitespace). +But it can also cause pathological cases: + +- Inappropriately chosen junk items can cause an unexpectedly **large** (but + still correct) result. +- The default heuristic is **asymmetric**: only the second sequence is + inspected when determining what is considered junk, so comparing A to B can + give different results than comparing B to A and reversing the result. + +By default, if the second input sequence is at least 200 items long, items +that account for more than 1% it are considered *junk*. + +Depending on your data, you should consider turning this heuristic off +(setting :class:`~difflib.SequenceMatcher`'s *autojunk* argument to to ``False``) +or tuning it (using the *isjunk* argument, perhaps to one of the +:ref:`predefined functions `). + + +The :mod:`!difflib` algorithm +----------------------------- + +The algorithm used in :class:`SequenceMatcher` predates, and is a little +fancier than, an algorithm published in the late 1980s by Ratcliff and +Obershelp under the hyperbolic name "gestalt pattern matching." +The idea is to find the longest contiguous subsequence common to both inputs, +then recursively handle the pieces of the sequences to the left and to the +right of the matching subsequence. + +.. seealso:: + + `Pattern Matching: The Gestalt Approach `_ + Discussion of a similar algorithm by John W. Ratcliff and D. E. Metzener. This + was published in Dr. Dobb's Journal in July, 1988. + +As an extension to the Ratcliff and Obershelp algorithm, :mod:`!difflib` +searches for the longest *junk-free* contiguous subsequence. +See the :ref:`difflib-junk` section for details. + +.. impl-detail:: Timing + The basic Ratcliff-Obershelp algorithm is cubic time in the worst + case and quadratic time in the expected case. + :mod:`difflib`'s algorithm is quadratic time for the worst case and has + expected-case behavior dependent in a complicated way on how many elements + the sequences have in common; + best case time is linear. + + +.. _difflib-diff-generation: + +Diff generation +--------------- + +.. _differ-objects: .. class:: Differ @@ -82,6 +119,47 @@ diffs. For comparing directories and files, see also, the :mod:`filecmp` module. and were not present in either input sequence. These lines can be confusing if the sequences contain whitespace characters, such as spaces, tabs or line breaks. + Note that :class:`Differ`\ -generated deltas make no claim to be **minimal** + diffs. To the contrary, minimal diffs are often counter-intuitive for humans, + because they synch up anywhere possible, sometimes at accidental matches + 100 pages apart. + Restricting synch points to contiguous matches preserves some notion of + locality, at the occasional cost of producing a longer diff. + + The :class:`Differ` class has this constructor: + + .. method:: __init__(linejunk=None, charjunk=None) + + Optional keyword parameters *linejunk* and *charjunk* are for filter functions + (or ``None``): + + *linejunk*: A function that accepts a single string argument, and returns true + if the string is junk. The default is ``None``, meaning that no line is + considered junk. + + *charjunk*: A function that accepts a single character argument (a string of + length 1), and returns true if the character is junk. The default is ``None``, + meaning that no character is considered junk. + + These junk-filtering functions speed up matching to find + differences and do not cause any differing lines or characters to + be ignored. Read the description of the + :meth:`~SequenceMatcher.find_longest_match` method's *isjunk* + parameter for an explanation. + + :class:`Differ` objects are used (deltas generated) via a single method: + + + .. method:: Differ.compare(a, b) + + Compare two sequences of lines, and generate the delta (a sequence of lines). + + Each sequence must contain individual single-line strings ending with + newlines. Such sequences can be obtained from the + :meth:`~io.IOBase.readlines` method of file-like objects. The generated + delta also consists of newline-terminated strings, ready to be + printed as-is via the :meth:`~io.IOBase.writelines` method of a + file-like object. .. class:: HtmlDiff @@ -349,6 +427,12 @@ diffs. For comparing directories and files, see also, the :mod:`filecmp` module. .. versionadded:: 3.5 + +.. _difflib-isjunk-functions: + +Junk definition functions +------------------------- + .. function:: IS_LINE_JUNK(line) Return ``True`` for ignorable lines. The line *line* is ignorable if *line* is @@ -363,21 +447,11 @@ diffs. For comparing directories and files, see also, the :mod:`filecmp` module. parameter *charjunk* in :func:`ndiff`. -.. seealso:: - - `Pattern Matching: The Gestalt Approach `_ - Discussion of a similar algorithm by John W. Ratcliff and D. E. Metzener. This - was published in Dr. Dobb's Journal in July, 1988. - - .. _sequence-matcher: SequenceMatcher objects ----------------------- -The :class:`SequenceMatcher` class has this constructor: - - .. class:: SequenceMatcher(isjunk=None, a='', b='', autojunk=True) Optional argument *isjunk* must be ``None`` (the default) or a one-argument @@ -588,10 +662,13 @@ are always at least as large as :meth:`~SequenceMatcher.ratio`: 1.0 +Examples +-------- + .. _sequencematcher-examples: SequenceMatcher examples ------------------------- +........................ This example compares two strings, considering blanks to be "junk": @@ -639,59 +716,10 @@ If you want to know how to change the first sequence into the second, use built with :class:`SequenceMatcher`. -.. _differ-objects: - -Differ objects --------------- - -Note that :class:`Differ`\ -generated deltas make no claim to be **minimal** -diffs. To the contrary, minimal diffs are often counter-intuitive, because they -synch up anywhere possible, sometimes accidental matches 100 pages apart. -Restricting synch points to contiguous matches preserves some notion of -locality, at the occasional cost of producing a longer diff. - -The :class:`Differ` class has this constructor: - - -.. class:: Differ(linejunk=None, charjunk=None) - :noindex: - - Optional keyword parameters *linejunk* and *charjunk* are for filter functions - (or ``None``): - - *linejunk*: A function that accepts a single string argument, and returns true - if the string is junk. The default is ``None``, meaning that no line is - considered junk. - - *charjunk*: A function that accepts a single character argument (a string of - length 1), and returns true if the character is junk. The default is ``None``, - meaning that no character is considered junk. - - These junk-filtering functions speed up matching to find - differences and do not cause any differing lines or characters to - be ignored. Read the description of the - :meth:`~SequenceMatcher.find_longest_match` method's *isjunk* - parameter for an explanation. - - :class:`Differ` objects are used (deltas generated) via a single method: - - - .. method:: Differ.compare(a, b) - - Compare two sequences of lines, and generate the delta (a sequence of lines). - - Each sequence must contain individual single-line strings ending with - newlines. Such sequences can be obtained from the - :meth:`~io.IOBase.readlines` method of file-like objects. The delta - generated also consists of newline-terminated strings, ready to be - printed as-is via the :meth:`~io.IOBase.writelines` method of a - file-like object. - - .. _differ-examples: Differ example --------------- +.............. This example compares two texts. First we set up the texts, sequences of individual single-line strings ending with newlines (such sequences can also be @@ -758,14 +786,14 @@ As a single multi-line string it looks like this:: .. _difflib-interface: A command-line interface to difflib ------------------------------------ +................................... This example shows how to use difflib to create a ``diff``-like utility. .. literalinclude:: ../includes/diff.py ndiff example -------------- +............. This example shows how to use :func:`difflib.ndiff`. diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index 355621db2e18a18..a2668a38c6b4a2f 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -1790,13 +1790,13 @@ always available. Unless explicitly noted otherwise, all variables are read-only callable or ``None`` to clear the filter. The filter function is called for every potentially lazy import to - determine whether it should actually be lazy. It must have the following + determine whether it should actually be lazy. It should have the following signature:: def filter(importing_module: str, imported_module: str, fromlist: tuple[str, ...] | None) -> bool - Where: + The function is called with three positional arguments: * *importing_module* is the name of the module doing the import * *imported_module* is the resolved name of the module being imported diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 250558dd1341a49..daac31fd7f4b17c 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -2317,6 +2317,13 @@ New deprecations :func:`issubclass`, but warnings were not previously emitted if it was merely imported or accessed from the :mod:`!typing` module. + * It is deprecated to call :func:`isinstance` and :func:`issubclass` checks on + protocol classes that were not explicitly decorated with :func:`!runtime_checkable` + but that inherit from a runtime-checkable protocol class. + This will raise a :exc:`TypeError` in Python 3.20. + + (Contributed by Bartosz Sławecki in :gh:`132604`.) + * :mod:`webbrowser`: diff --git a/Grammar/python.gram b/Grammar/python.gram index ac1b4a64f38c75a..2713ba9466a0b1d 100644 --- a/Grammar/python.gram +++ b/Grammar/python.gram @@ -568,7 +568,6 @@ real_number[expr_ty]: imaginary_number[expr_ty]: | imag=NUMBER { _PyPegen_ensure_imaginary(p, imag) } - | '+' imag=NUMBER { _PyPegen_ensure_imaginary(p, imag) } capture_pattern[pattern_ty]: | target=pattern_capture_target { _PyAST_MatchAs(NULL, target->v.Name.id, EXTRA) } diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index 3836aaad326f62e..f26fba175b63cd7 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -590,7 +590,7 @@ async def shutdown_asyncgens(self): return_exceptions=True) for result, agen in zip(results, closing_agens): - if isinstance(result, Exception): + if isinstance(result, BaseException): self.call_exception_handler({ 'message': f'an error occurred during closing of ' f'asynchronous generator {agen!r}', diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index 1622f19f0213f3c..18afdca23163a1e 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -1042,6 +1042,60 @@ async def iter_one(): asyncio.create_task(iter_one()) return status + def test_shutdown_asyncgens_reports_base_exceptions(self): + # gh-150866: shutdown_asyncgens silently swallowed exceptions that + # don't inherit from Exception raised during aclose() because the + # check was isinstance(result, Exception), but CancelledError inherits + # from BaseException. + self.loop._process_events = mock.Mock() + self.loop._write_to_self = mock.Mock() + + class MyBaseException(BaseException): + pass + + async def agen_cancel(): + try: + yield 1 + finally: + raise asyncio.CancelledError("agen got cancelled during cleanup") + + async def agen_base(): + try: + yield 1 + finally: + raise MyBaseException("base exc during cleanup") + + async def agen_value_error(): + try: + yield 1 + finally: + raise ValueError("agen failed during cleanup") + + caught = [] + + def handler(loop, context): + caught.append(context['exception']) + + async def main(): + loop = asyncio.get_running_loop() + loop.set_exception_handler(handler) + + g1 = agen_cancel() + g2 = agen_base() + g3 = agen_value_error() + await g1.__anext__() + await g2.__anext__() + await g3.__anext__() + + await loop.shutdown_asyncgens() + + self.loop.run_until_complete(main()) + self.assertEqual(len(caught), 3) + self.assertEqual( + {type(exc) for exc in caught}, + {asyncio.CancelledError, MyBaseException, ValueError}, + ) + def test_asyncgen_finalization_by_gc(self): # Async generators should be finalized when garbage collected. self.loop._process_events = mock.Mock() diff --git a/Lib/test/test_patma.py b/Lib/test/test_patma.py index e3aaea84ea7ce84..0825d9d980c5e7f 100644 --- a/Lib/test/test_patma.py +++ b/Lib/test/test_patma.py @@ -2835,14 +2835,6 @@ def test_patma_264(self): self.assertEqual(y, 0) def test_patma_265(self): - x = 0.25 - 1.75j - match x: - case 0.25 - +1.75j: - y = 0 - self.assertEqual(x, 0.25 - 1.75j) - self.assertEqual(y, 0) - - def test_patma_266(self): x = 0 match x: case +1e1000: @@ -3329,6 +3321,34 @@ def test_mapping_pattern_duplicate_key_edge_case3(self): pass """) + def test_duplicate_sign_in_complex_1(self): + self.assert_syntax_error(""" + match ...: + case 0 ++ 0j: + pass + """) + + def test_duplicate_sign_in_complex_2(self): + self.assert_syntax_error(""" + match ...: + case 0 -+ 0j: + pass + """) + + def test_duplicate_sign_in_complex_3(self): + self.assert_syntax_error(""" + match ...: + case 0 +- 0j: + pass + """) + + def test_duplicate_sign_in_complex_4(self): + self.assert_syntax_error(""" + match ...: + case 0 -- 0j: + pass + """) + class TestTypeErrors(unittest.TestCase): def test_accepts_positional_subpatterns_0(self): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-27-16-29-10.gh-issue-154775._ISRIk.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-27-16-29-10.gh-issue-154775._ISRIk.rst new file mode 100644 index 000000000000000..b0035c7e1bc0640 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-27-16-29-10.gh-issue-154775._ISRIk.rst @@ -0,0 +1,2 @@ +When matching a complex literal in :keyword:`case` statements, an extraneous +``+`` sign (for example, ``1++1j`` or ``1-+1j``) is no longer allowed. diff --git a/Misc/NEWS.d/next/Library/2026-06-03-18-41-42.gh-issue-150866.gXGyX-.rst b/Misc/NEWS.d/next/Library/2026-06-03-18-41-42.gh-issue-150866.gXGyX-.rst new file mode 100644 index 000000000000000..57af6e954384b10 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-03-18-41-42.gh-issue-150866.gXGyX-.rst @@ -0,0 +1,2 @@ +Fix :meth:`asyncio.loop.shutdown_asyncgens` to report any +:exc:`BaseException` raised during asynchronous generator cleanup. diff --git a/Parser/parser.c b/Parser/parser.c index 4f4fd3ed46d1f02..800db5b490fea98 100644 --- a/Parser/parser.c +++ b/Parser/parser.c @@ -9355,7 +9355,7 @@ real_number_rule(Parser *p) return _res; } -// imaginary_number: NUMBER | '+' NUMBER +// imaginary_number: NUMBER static expr_ty imaginary_number_rule(Parser *p) { @@ -9392,33 +9392,6 @@ imaginary_number_rule(Parser *p) D(fprintf(stderr, "%*c%s imaginary_number[%d-%d]: %s failed!\n", p->level, ' ', p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "NUMBER")); } - { // '+' NUMBER - if (p->error_indicator) { - p->level--; - return NULL; - } - D(fprintf(stderr, "%*c> imaginary_number[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'+' NUMBER")); - Token * _literal; - expr_ty imag; - if ( - (_literal = _PyPegen_expect_token(p, 14)) // token='+' - && - (imag = _PyPegen_number_token(p)) // NUMBER - ) - { - D(fprintf(stderr, "%*c+ imaginary_number[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'+' NUMBER")); - _res = _PyPegen_ensure_imaginary ( p , imag ); - if ((_res == NULL || p->error_indicator) && PyErr_Occurred()) { - p->error_indicator = 1; - p->level--; - return NULL; - } - goto done; - } - p->mark = _mark; - D(fprintf(stderr, "%*c%s imaginary_number[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'+' NUMBER")); - } _res = NULL; done: p->level--;