Skip to content

Commit e7eec71

Browse files
Merge branch 'main' into pydoc-repl
2 parents 5bc8b55 + ba3357c commit e7eec71

28 files changed

Lines changed: 396 additions & 96 deletions

‎Doc/builtins/exceptions.rst‎

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -396,10 +396,9 @@ The following exceptions are the exceptions that are usually raised.
396396

397397
.. attribute:: strerror
398398

399-
The corresponding error message, as provided by
400-
the operating system. It is formatted by the C
401-
functions :c:func:`!perror` under POSIX, and :c:func:`!FormatMessage`
402-
under Windows.
399+
The corresponding error message, as provided by the operating system.
400+
Under POSIX, it is obtained using the C functions :c:func:`!strerror`
401+
or :c:func:`!strerror_r`; under Windows, :c:func:`!FormatMessage` is used.
403402

404403
.. attribute:: filename
405404
filename2
@@ -567,11 +566,6 @@ The following exceptions are the exceptions that are usually raised.
567566
The column in the end line where the error occurred finishes. This is
568567
1-indexed: the first character in the line has an ``offset`` of 1.
569568

570-
For errors in f-string fields, the message is prefixed by "f-string: "
571-
and the offsets are offsets in a text constructed from the replacement
572-
expression. For example, compiling f'Bad {a b} field' results in this
573-
args attribute: ('f-string: ...', ('', 1, 2, '(a b)\n', 1, 5)).
574-
575569
.. versionchanged:: 3.10
576570
Added the :attr:`end_lineno` and :attr:`end_offset` attributes.
577571

‎Doc/builtins/functions.rst‎

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -772,7 +772,7 @@ are always available. They are listed here in alphabetical order.
772772
still access all builtins.
773773

774774
The *closure* argument specifies a closure--a tuple of cellvars.
775-
It's only valid when the *object* is a code object containing
775+
It's only valid when *source* is a code object containing
776776
:term:`free (closure) variables <closure variable>`.
777777
The length of the tuple must exactly match the length of the code object's
778778
:attr:`~codeobject.co_freevars` attribute.
@@ -2166,10 +2166,10 @@ are always available. They are listed here in alphabetical order.
21662166

21672167
When called directly within an ordinary method of a class, both arguments may
21682168
be omitted ("zero-argument :func:`!super`"). In this case, *type* will be the
2169-
enclosing class, and *obj* will be the first argument of the immediately
2170-
enclosing function (typically ``self``). (This means that zero-argument
2169+
enclosing class, and *object_or_type* will be the first argument of the
2170+
immediately enclosing function (typically ``self``). Zero-argument
21712171
:func:`!super` will not work as expected within nested functions, including
2172-
generator expressions, which implicitly create nested functions.)
2172+
generator expressions, which implicitly create nested functions.
21732173

21742174
There are two typical use cases for *super*. In a class hierarchy with
21752175
single inheritance, *super* can be used to refer to parent classes without
@@ -2428,10 +2428,9 @@ are always available. They are listed here in alphabetical order.
24282428
determine the package context of the :keyword:`import` statement.
24292429

24302430
*level* specifies whether to use absolute or relative imports. ``0`` (the
2431-
default) means only perform absolute imports. Positive values for
2432-
*level* indicate the number of parent directories to search relative to the
2433-
directory of the module calling :func:`__import__` (see :pep:`328` for the
2434-
details).
2431+
default) means only perform absolute imports. For relative imports,
2432+
``1`` refers to the current package, ``2`` to its parent package, and so on
2433+
(see :pep:`328` for details).
24352434

24362435
When the *name* variable is of the form ``package.module``, normally, the
24372436
top-level package (the name up till the first dot) is returned, *not* the

‎Doc/builtins/stdtypes.rst‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5667,7 +5667,7 @@ can be used interchangeably to index the same dictionary entry.
56675667

56685668
* Use a comma-separated list of ``key: value`` pairs within braces:
56695669
``{'jack': 4098, 'sjoerd': 4127}`` or ``{4098: 'jack', 4127: 'sjoerd'}``
5670-
* Use a dict comprehension: ``{}``, ``{x: x ** 2 for x in range(10)}``
5670+
* Use a dict comprehension: ``{x: x ** 2 for x in range(10)}``
56715671
* Use the type constructor: ``dict()``,
56725672
``dict([('foo', 100), ('bar', 200)])``, ``dict(foo=100, bar=200)``
56735673

@@ -5878,9 +5878,8 @@ can be used interchangeably to index the same dictionary entry.
58785878
Return a new view of the dictionary's values. See the
58795879
:ref:`documentation of view objects <dict-views>`.
58805880

5881-
An equality comparison between one ``dict.values()`` view and another
5882-
will always return ``False``. This also applies when comparing
5883-
``dict.values()`` to itself::
5881+
An equality comparison between two distinct ``dict.values()`` views
5882+
will always return ``False``, even for views of the same dictionary::
58845883

58855884
>>> d = {'a': 1}
58865885
>>> d.values() == d.values()

‎Doc/library/inspect.rst‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,12 @@ attributes (see :ref:`import-mod-attrs` for module attributes):
284284
| | | ``AGEN_SUSPENDED``, or |
285285
| | | ``AGEN_CLOSED`` |
286286
+-----------------+-------------------+---------------------------+
287+
| async generator | aga_gen | async generator driven by |
288+
| awaitable | | the awaitable returned |
289+
| | | from ``asend()``, |
290+
| | | ``athrow()``, or |
291+
| | | ``aclose()`` |
292+
+-----------------+-------------------+---------------------------+
287293
| coroutine | __name__ | name |
288294
+-----------------+-------------------+---------------------------+
289295
| | __qualname__ | qualified name |
@@ -360,6 +366,11 @@ attributes (see :ref:`import-mod-attrs` for module attributes):
360366
Add ``gi_state`` attribute to generators, ``cr_state`` attribute to
361367
coroutines, and ``ag_state`` attribute to async generators.
362368

369+
.. versionchanged:: 3.16
370+
371+
Add ``aga_gen`` attribute to the awaitables returned by the ``asend()``,
372+
``athrow()`` and ``aclose()`` methods of async generators.
373+
363374
.. function:: getmembers(object[, predicate])
364375

365376
Return all the members of an object in a list of ``(name, value)``

‎Lib/asyncio/graph.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ def _build_graph_for_future(
7070
if coro.gi_frame is not None:
7171
st.append(FrameCallGraphEntry(coro.gi_frame))
7272
coro = coro.gi_yieldfrom
73+
elif hasattr(coro, 'aga_gen'):
74+
# An asend()/athrow() awaitable: step over it to its generator
75+
coro = coro.aga_gen
7376
else:
7477
break
7578

‎Lib/asyncio/streams.py‎

Lines changed: 37 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,16 @@ async def _wait_for_data(self, func_name):
553553
finally:
554554
self._waiter = None
555555

556+
def _readuntil_fast(self, separator):
557+
# Fast linear search inside already buffered data.
558+
if type(separator) is bytes and separator and self._exception is None:
559+
isep = self._buffer.find(separator)
560+
if 0 <= isep <= self._limit:
561+
chunk = self._buffer.take_bytes(isep + len(separator))
562+
self._maybe_resume_transport()
563+
return chunk
564+
return None
565+
556566
async def readline(self):
557567
r"""Read chunk of data from the stream until newline (b'\n') is found.
558568
@@ -570,19 +580,18 @@ async def readline(self):
570580
needed.
571581
"""
572582
sep = b'\n'
573-
seplen = len(sep)
583+
if (line := self._readuntil_fast(sep)) is not None:
584+
return line
574585
try:
575-
line = await self.readuntil(sep)
586+
return await self.readuntil(sep)
576587
except exceptions.IncompleteReadError as e:
577588
return e.partial
578589
except exceptions.LimitOverrunError as e:
579-
if self._buffer.startswith(sep, e.consumed):
580-
del self._buffer[:e.consumed + seplen]
581-
else:
582-
self._buffer.clear()
590+
# e.consumed is the newline's index, or len(self._buffer) if
591+
# there is none: drop the line, or everything.
592+
del self._buffer[:e.consumed + 1]
583593
self._maybe_resume_transport()
584594
raise ValueError(e.args[0])
585-
return line
586595

587596
async def readuntil(self, separator=b'\n'):
588597
"""Read data from the stream until ``separator`` is found.
@@ -610,17 +619,18 @@ async def readuntil(self, separator=b'\n'):
610619
the shortest possible separator is considered to be the one that
611620
matched.
612621
"""
622+
if (chunk := self._readuntil_fast(separator)) is not None:
623+
return chunk
613624
if isinstance(separator, tuple):
614625
# Makes sure shortest matches wins
615626
separator = sorted(separator, key=len)
616627
else:
617628
separator = [separator]
618629
if not separator:
619630
raise ValueError('Separator should contain at least one element')
620-
min_seplen = len(separator[0])
621-
max_seplen = len(separator[-1])
622-
if min_seplen == 0:
631+
if len(separator[0]) == 0:
623632
raise ValueError('Separator should be at least one-byte string')
633+
max_seplen = len(separator[-1])
624634

625635
if self._exception is not None:
626636
raise self._exception
@@ -650,32 +660,23 @@ async def readuntil(self, separator=b'\n'):
650660
# or an EOF has happened.
651661
while True:
652662
buflen = len(self._buffer)
653-
654-
# Check if we now have enough data in the buffer for shortest
655-
# separator to fit.
656-
if buflen - offset >= min_seplen:
657-
match_start = None
658-
match_end = None
659-
for sep in separator:
660-
isep = self._buffer.find(sep, offset)
661-
662-
if isep != -1:
663-
# `separator` is in the buffer. `match_start` and
664-
# `match_end` will be used later to retrieve the
665-
# data.
666-
end = isep + len(sep)
667-
if match_end is None or end < match_end:
668-
match_end = end
669-
match_start = isep
670-
if match_end is not None:
671-
break
672-
673-
# see upper comment for explanation.
674-
offset = max(0, buflen + 1 - max_seplen)
675-
if offset > self._limit:
676-
raise exceptions.LimitOverrunError(
677-
'Separator is not found, and chunk exceed the limit',
678-
offset)
663+
match_end = buflen + 1
664+
for sep in separator:
665+
# Only a match that ends before the best one so far wins,
666+
# so don't search past it.
667+
isep = self._buffer.find(sep, offset, match_end - 1)
668+
if isep != -1:
669+
match_start = isep
670+
match_end = isep + len(sep)
671+
if match_end <= buflen:
672+
break
673+
674+
# see upper comment for explanation.
675+
offset = max(0, buflen + 1 - max_seplen)
676+
if offset > self._limit:
677+
raise exceptions.LimitOverrunError(
678+
'Separator is not found, and chunk exceed the limit',
679+
offset)
679680

680681
# Complete message (with full separator) may be present in buffer
681682
# even when EOF flag is set. This may happen when the last chunk

‎Lib/test/exception_hierarchy.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ BaseException
1414
├── EOFError
1515
├── ExceptionGroup [BaseExceptionGroup]
1616
├── ImportError
17-
│ └── ImportCycleError
17+
│ ├── ImportCycleError
1818
│ └── ModuleNotFoundError
1919
├── LookupError
2020
│ ├── IndexError

‎Lib/test/test_asyncgen.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,23 @@ async def gen():
399399
r"cannot reuse already awaited __anext__\(\)/asend\(\)"):
400400
an.send(None)
401401

402+
def test_async_gen_asend_athrow_aga_gen(self):
403+
# gh-160837: asend()/athrow() awaitables expose the generator they drive
404+
async def gen():
405+
yield 1
406+
407+
g = gen()
408+
asend = g.asend(None)
409+
athrow = g.athrow(ValueError)
410+
try:
411+
self.assertIs(asend.aga_gen, g)
412+
self.assertIs(athrow.aga_gen, g)
413+
with self.assertRaises(AttributeError):
414+
asend.aga_gen = None
415+
finally:
416+
asend.close()
417+
athrow.close()
418+
402419
def test_async_gen_asend_throw_concurrent_with_send(self):
403420
import types
404421

‎Lib/test/test_asyncio/test_graph.py‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,77 @@ async def probe(t):
430430
['T<probe>', ['a _cancel_and_wait', 'a wait_for', 'a probe'], []],
431431
])
432432

433+
async def test_stack_async_gen_asend(self):
434+
# gh-160837: an async_generator_asend must not truncate the stack
435+
fut = asyncio.Future()
436+
stack_for_consumer = None
437+
438+
async def deep():
439+
await fut
440+
441+
async def gen():
442+
await deep()
443+
yield 1
444+
445+
async def consumer():
446+
async for _ in gen():
447+
pass
448+
449+
async def main():
450+
nonlocal stack_for_consumer
451+
452+
async with asyncio.TaskGroup() as g:
453+
t = g.create_task(consumer(), name='consumer')
454+
for _ in range(5):
455+
await asyncio.sleep(0)
456+
457+
stack_for_consumer = capture_test_stack(fut=t)
458+
fut.set_result(None)
459+
460+
await main()
461+
462+
self.assertEqual(stack_for_consumer[0][:2], [
463+
'T<consumer>',
464+
['a deep', 'ag gen', 'a consumer'],
465+
])
466+
467+
async def test_stack_async_gen_aclose(self):
468+
# gh-160837: an async_generator_athrow must not truncate the stack
469+
fut = asyncio.Future()
470+
stack_for_consumer = None
471+
472+
async def deep():
473+
await fut
474+
475+
async def gen():
476+
try:
477+
yield 1
478+
finally:
479+
await deep()
480+
481+
async def consumer():
482+
agen = gen()
483+
await anext(agen)
484+
await agen.aclose()
485+
486+
async def main():
487+
nonlocal stack_for_consumer
488+
489+
async with asyncio.TaskGroup() as g:
490+
t = g.create_task(consumer(), name='consumer')
491+
for _ in range(5):
492+
await asyncio.sleep(0)
493+
494+
stack_for_consumer = capture_test_stack(fut=t)
495+
fut.set_result(None)
496+
497+
await main()
498+
499+
self.assertEqual(stack_for_consumer[0][:2], [
500+
'T<consumer>',
501+
['a deep', 'ag gen', 'a consumer'],
502+
])
503+
433504
async def test_stack_gather(self):
434505

435506
stack_for_deep = None

‎Lib/test/test_asyncio/test_streams.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,14 @@ def test_exception(self):
592592
stream.set_exception(exc)
593593
self.assertIs(stream.exception(), exc)
594594

595+
# Raised even when the data is already buffered.
596+
stream.feed_data(b'line\n')
597+
self.assertRaises(
598+
ValueError, self.loop.run_until_complete, stream.readline())
599+
self.assertRaises(
600+
ValueError, self.loop.run_until_complete, stream.readuntil())
601+
self.assertEqual(b'line\n', stream._buffer)
602+
595603
def test_exception_waiter(self):
596604
stream = asyncio.StreamReader(loop=self.loop)
597605

0 commit comments

Comments
 (0)