Skip to content

Commit 17fb3f0

Browse files
authored
Merge branch 'main' into Optimize_DICT_UPDATE
2 parents 9544f5d + 161329c commit 17fb3f0

File tree

128 files changed

+3834
-2378
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

128 files changed

+3834
-2378
lines changed

Doc/c-api/unicode.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1855,8 +1855,6 @@ object.
18551855
On success, return ``0``.
18561856
On error, set an exception, leave the writer unchanged, and return ``-1``.
18571857
1858-
.. versionadded:: 3.14
1859-
18601858
.. c:function:: int PyUnicodeWriter_WriteWideChar(PyUnicodeWriter *writer, const wchar_t *str, Py_ssize_t size)
18611859
18621860
Write the wide string *str* into *writer*.
@@ -1892,6 +1890,10 @@ object.
18921890
On success, return ``0``.
18931891
On error, set an exception, leave the writer unchanged, and return ``-1``.
18941892
1893+
.. versionchanged:: 3.14.4
1894+
1895+
Added support for ``NULL``.
1896+
18951897
.. c:function:: int PyUnicodeWriter_WriteSubstring(PyUnicodeWriter *writer, PyObject *str, Py_ssize_t start, Py_ssize_t end)
18961898
18971899
Write the substring ``str[start:end]`` into *writer*.

Doc/faq/programming.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1074,7 +1074,7 @@ Performance
10741074
My program is too slow. How do I speed it up?
10751075
---------------------------------------------
10761076

1077-
That's a tough one, in general. First, here is list of things to
1077+
That's a tough one, in general. First, here is a list of things to
10781078
remember before diving further:
10791079

10801080
* Performance characteristics vary across Python implementations. This FAQ

Doc/howto/enum.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ The complete :class:`!Weekday` enum now looks like this::
105105

106106
Now we can find out what today is! Observe::
107107

108-
>>> from datetime import date
109-
>>> Weekday.from_date(date.today()) # doctest: +SKIP
108+
>>> import datetime as dt
109+
>>> Weekday.from_date(dt.date.today()) # doctest: +SKIP
110110
<Weekday.TUESDAY: 2>
111111

112112
Of course, if you're reading this on some other day, you'll see that day instead.
@@ -1480,8 +1480,8 @@ TimePeriod
14801480

14811481
An example to show the :attr:`~Enum._ignore_` attribute in use::
14821482

1483-
>>> from datetime import timedelta
1484-
>>> class Period(timedelta, Enum):
1483+
>>> import datetime as dt
1484+
>>> class Period(dt.timedelta, Enum):
14851485
... "different lengths of time"
14861486
... _ignore_ = 'Period i'
14871487
... Period = vars()

Doc/howto/logging-cookbook.rst

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1549,10 +1549,10 @@ to this (remembering to first import :mod:`concurrent.futures`)::
15491549
for i in range(10):
15501550
executor.submit(worker_process, queue, worker_configurer)
15511551

1552-
Deploying Web applications using Gunicorn and uWSGI
1552+
Deploying web applications using Gunicorn and uWSGI
15531553
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
15541554

1555-
When deploying Web applications using `Gunicorn <https://gunicorn.org/>`_ or `uWSGI
1555+
When deploying web applications using `Gunicorn <https://gunicorn.org/>`_ or `uWSGI
15561556
<https://uwsgi-docs.readthedocs.io/en/latest/>`_ (or similar), multiple worker
15571557
processes are created to handle client requests. In such environments, avoid creating
15581558
file-based handlers directly in your web application. Instead, use a
@@ -3616,7 +3616,6 @@ detailed information.
36163616

36173617
.. code-block:: python3
36183618
3619-
import datetime
36203619
import logging
36213620
import random
36223621
import sys
@@ -3851,15 +3850,15 @@ Logging to syslog with RFC5424 support
38513850
Although :rfc:`5424` dates from 2009, most syslog servers are configured by default to
38523851
use the older :rfc:`3164`, which hails from 2001. When ``logging`` was added to Python
38533852
in 2003, it supported the earlier (and only existing) protocol at the time. Since
3854-
RFC5424 came out, as there has not been widespread deployment of it in syslog
3853+
RFC 5424 came out, as there has not been widespread deployment of it in syslog
38553854
servers, the :class:`~logging.handlers.SysLogHandler` functionality has not been
38563855
updated.
38573856

38583857
RFC 5424 contains some useful features such as support for structured data, and if you
38593858
need to be able to log to a syslog server with support for it, you can do so with a
38603859
subclassed handler which looks something like this::
38613860

3862-
import datetime
3861+
import datetime as dt
38633862
import logging.handlers
38643863
import re
38653864
import socket
@@ -3877,7 +3876,7 @@ subclassed handler which looks something like this::
38773876

38783877
def format(self, record):
38793878
version = 1
3880-
asctime = datetime.datetime.fromtimestamp(record.created).isoformat()
3879+
asctime = dt.datetime.fromtimestamp(record.created).isoformat()
38813880
m = self.tz_offset.match(time.strftime('%z'))
38823881
has_offset = False
38833882
if m and time.timezone:

Doc/includes/diff.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
""" Command line interface to difflib.py providing diffs in four formats:
1+
""" Command-line interface to difflib.py providing diffs in four formats:
22
33
* ndiff: lists every line and highlights interline changes.
44
* context: highlights clusters of changes in a before/after format.
@@ -8,11 +8,11 @@
88
"""
99

1010
import sys, os, difflib, argparse
11-
from datetime import datetime, timezone
11+
import datetime as dt
1212

1313
def file_mtime(path):
14-
t = datetime.fromtimestamp(os.stat(path).st_mtime,
15-
timezone.utc)
14+
t = dt.datetime.fromtimestamp(os.stat(path).st_mtime,
15+
dt.timezone.utc)
1616
return t.astimezone().isoformat()
1717

1818
def main():

Doc/library/asyncio-dev.rst

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,3 +248,225 @@ Output in debug mode::
248248
File "../t.py", line 4, in bug
249249
raise Exception("not consumed")
250250
Exception: not consumed
251+
252+
253+
Asynchronous generators best practices
254+
======================================
255+
256+
Writing correct and efficient asyncio code requires awareness of certain pitfalls.
257+
This section outlines essential best practices that can save you hours of debugging.
258+
259+
260+
Close asynchronous generators explicitly
261+
----------------------------------------
262+
263+
It is recommended to manually close the
264+
:term:`asynchronous generator <asynchronous generator iterator>`. If a generator
265+
exits early - for example, due to an exception raised in the body of
266+
an ``async for`` loop - its asynchronous cleanup code may run in an
267+
unexpected context. This can occur after the tasks it depends on have completed,
268+
or during the event loop shutdown when the async-generator's garbage collection
269+
hook is called.
270+
271+
To avoid this, explicitly close the generator by calling its
272+
:meth:`~agen.aclose` method, or use the :func:`contextlib.aclosing`
273+
context manager::
274+
275+
import asyncio
276+
import contextlib
277+
278+
async def gen():
279+
yield 1
280+
yield 2
281+
282+
async def func():
283+
async with contextlib.aclosing(gen()) as g:
284+
async for x in g:
285+
break # Don't iterate until the end
286+
287+
asyncio.run(func())
288+
289+
As noted above, the cleanup code for these asynchronous generators is deferred.
290+
The following example demonstrates that the finalization of an asynchronous
291+
generator can occur in an unexpected order::
292+
293+
import asyncio
294+
work_done = False
295+
296+
async def cursor():
297+
try:
298+
yield 1
299+
finally:
300+
assert work_done
301+
302+
async def rows():
303+
global work_done
304+
try:
305+
yield 2
306+
finally:
307+
await asyncio.sleep(0.1) # immitate some async work
308+
work_done = True
309+
310+
311+
async def main():
312+
async for c in cursor():
313+
async for r in rows():
314+
break
315+
break
316+
317+
asyncio.run(main())
318+
319+
For this example, we get the following output::
320+
321+
unhandled exception during asyncio.run() shutdown
322+
task: <Task finished name='Task-3' coro=<<async_generator_athrow without __name__>()> exception=AssertionError()>
323+
Traceback (most recent call last):
324+
File "example.py", line 6, in cursor
325+
yield 1
326+
asyncio.exceptions.CancelledError
327+
328+
During handling of the above exception, another exception occurred:
329+
330+
Traceback (most recent call last):
331+
File "example.py", line 8, in cursor
332+
assert work_done
333+
^^^^^^^^^
334+
AssertionError
335+
336+
The ``cursor()`` asynchronous generator was finalized before the ``rows``
337+
generator - an unexpected behavior.
338+
339+
The example can be fixed by explicitly closing the
340+
``cursor`` and ``rows`` async-generators::
341+
342+
async def main():
343+
async with contextlib.aclosing(cursor()) as cursor_gen:
344+
async for c in cursor_gen:
345+
async with contextlib.aclosing(rows()) as rows_gen:
346+
async for r in rows_gen:
347+
break
348+
break
349+
350+
351+
Create asynchronous generators only when the event loop is running
352+
------------------------------------------------------------------
353+
354+
It is recommended to create
355+
:term:`asynchronous generators <asynchronous generator iterator>` only after
356+
the event loop has been created.
357+
358+
To ensure that asynchronous generators close reliably, the event loop uses the
359+
:func:`sys.set_asyncgen_hooks` function to register callback functions. These
360+
callbacks update the list of running asynchronous generators to keep it in a
361+
consistent state.
362+
363+
When the :meth:`loop.shutdown_asyncgens() <asyncio.loop.shutdown_asyncgens>`
364+
function is called, the running generators are stopped gracefully and the
365+
list is cleared.
366+
367+
The asynchronous generator invokes the corresponding system hook during its
368+
first iteration. At the same time, the generator records that the hook has
369+
been called and does not call it again.
370+
371+
Therefore, if iteration begins before the event loop is created,
372+
the event loop will not be able to add the generator to its list of active
373+
generators because the hooks are set after the generator attempts to call them.
374+
Consequently, the event loop will not be able to terminate the generator
375+
if necessary.
376+
377+
Consider the following example::
378+
379+
import asyncio
380+
381+
async def agenfn():
382+
try:
383+
yield 10
384+
finally:
385+
await asyncio.sleep(0)
386+
387+
388+
with asyncio.Runner() as runner:
389+
agen = agenfn()
390+
print(runner.run(anext(agen)))
391+
del agen
392+
393+
Output::
394+
395+
10
396+
Exception ignored while closing generator <async_generator object agenfn at 0x000002F71CD10D70>:
397+
Traceback (most recent call last):
398+
File "example.py", line 13, in <module>
399+
del agen
400+
^^^^
401+
RuntimeError: async generator ignored GeneratorExit
402+
403+
This example can be fixed as follows::
404+
405+
import asyncio
406+
407+
async def agenfn():
408+
try:
409+
yield 10
410+
finally:
411+
await asyncio.sleep(0)
412+
413+
async def main():
414+
agen = agenfn()
415+
print(await anext(agen))
416+
del agen
417+
418+
asyncio.run(main())
419+
420+
421+
Avoid concurrent iteration and closure of the same generator
422+
------------------------------------------------------------
423+
424+
Async generators may be reentered while another
425+
:meth:`~agen.__anext__` / :meth:`~agen.athrow` / :meth:`~agen.aclose` call is in
426+
progress. This may lead to an inconsistent state of the async generator and can
427+
cause errors.
428+
429+
Let's consider the following example::
430+
431+
import asyncio
432+
433+
async def consumer():
434+
for idx in range(100):
435+
await asyncio.sleep(0)
436+
message = yield idx
437+
print('received', message)
438+
439+
async def amain():
440+
agenerator = consumer()
441+
await agenerator.asend(None)
442+
443+
fa = asyncio.create_task(agenerator.asend('A'))
444+
fb = asyncio.create_task(agenerator.asend('B'))
445+
await fa
446+
await fb
447+
448+
asyncio.run(amain())
449+
450+
Output::
451+
452+
received A
453+
Traceback (most recent call last):
454+
File "test.py", line 38, in <module>
455+
asyncio.run(amain())
456+
~~~~~~~~~~~^^^^^^^^^
457+
File "Lib/asyncio/runners.py", line 204, in run
458+
return runner.run(main)
459+
~~~~~~~~~~^^^^^^
460+
File "Lib/asyncio/runners.py", line 127, in run
461+
return self._loop.run_until_complete(task)
462+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
463+
File "Lib/asyncio/base_events.py", line 719, in run_until_complete
464+
return future.result()
465+
~~~~~~~~~~~~~^^
466+
File "test.py", line 36, in amain
467+
await fb
468+
RuntimeError: anext(): asynchronous generator is already running
469+
470+
471+
Therefore, it is recommended to avoid using asynchronous generators in parallel
472+
tasks or across multiple event loops.

0 commit comments

Comments
 (0)