Python-checkins
Threads by month
- ----- 2024 -----
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2006 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2005 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2004 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2003 -----
- December
- November
- October
- September
- August
January 2024
- 1 participants
- 801 discussions
https://github.com/python/cpython/commit/a1332a99cf1eb9b879d4b1f28761b096b5…
commit: a1332a99cf1eb9b879d4b1f28761b096b5749a0d
branch: main
author: Terry Jan Reedy <tjreedy(a)udel.edu>
committer: terryjreedy <tjreedy(a)udel.edu>
date: 2024-01-30T18:40:54Z
summary:
Clarify one-item tuple (#114745)
A 'single tuple' means 'one typle, of whatever length.
Remove the unneeded and slight distracting parenthetical 'singleton' comment.
files:
M Doc/reference/expressions.rst
diff --git a/Doc/reference/expressions.rst b/Doc/reference/expressions.rst
index 87ebdc1ca1c9c6..50e0f97a6534af 100644
--- a/Doc/reference/expressions.rst
+++ b/Doc/reference/expressions.rst
@@ -1890,8 +1890,9 @@ the unpacking.
.. index:: pair: trailing; comma
-The trailing comma is required only to create a single tuple (a.k.a. a
-*singleton*); it is optional in all other cases. A single expression without a
+A trailing comma is required only to create a one-item tuple,
+such as ``1,``; it is optional in all other cases.
+A single expression without a
trailing comma doesn't create a tuple, but rather yields the value of that
expression. (To create an empty tuple, use an empty pair of parentheses:
``()``.)
1
0
gh-112075: refactor dictionary lookup functions for better re-usability (#114629)
by DinoV 30 Jan '24
by DinoV 30 Jan '24
30 Jan '24
https://github.com/python/cpython/commit/0990d55725cb649e74739c983b67cf08c5…
commit: 0990d55725cb649e74739c983b67cf08c58e8439
branch: main
author: Dino Viehland <dinoviehland(a)meta.com>
committer: DinoV <dinoviehland(a)gmail.com>
date: 2024-01-30T09:33:36-08:00
summary:
gh-112075: refactor dictionary lookup functions for better re-usability (#114629)
Refactor dict lookup functions to use force inline helpers
files:
M Objects/dictobject.c
diff --git a/Objects/dictobject.c b/Objects/dictobject.c
index c5477ab15f8dc9..23d7e9b5e38a35 100644
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -874,11 +874,11 @@ lookdict_index(PyDictKeysObject *k, Py_hash_t hash, Py_ssize_t index)
Py_UNREACHABLE();
}
-// Search non-Unicode key from Unicode table
-static Py_ssize_t
-unicodekeys_lookup_generic(PyDictObject *mp, PyDictKeysObject* dk, PyObject *key, Py_hash_t hash)
+static inline Py_ALWAYS_INLINE Py_ssize_t
+do_lookup(PyDictObject *mp, PyDictKeysObject *dk, PyObject *key, Py_hash_t hash,
+ Py_ssize_t (*check_lookup)(PyDictObject *, PyDictKeysObject *, void *, Py_ssize_t ix, PyObject *key, Py_hash_t))
{
- PyDictUnicodeEntry *ep0 = DK_UNICODE_ENTRIES(dk);
+ void *ep0 = _DK_ENTRIES(dk);
size_t mask = DK_MASK(dk);
size_t perturb = hash;
size_t i = (size_t)hash & mask;
@@ -886,73 +886,26 @@ unicodekeys_lookup_generic(PyDictObject *mp, PyDictKeysObject* dk, PyObject *key
for (;;) {
ix = dictkeys_get_index(dk, i);
if (ix >= 0) {
- PyDictUnicodeEntry *ep = &ep0[ix];
- assert(ep->me_key != NULL);
- assert(PyUnicode_CheckExact(ep->me_key));
- if (ep->me_key == key) {
+ Py_ssize_t cmp = check_lookup(mp, dk, ep0, ix, key, hash);
+ if (cmp < 0) {
+ return cmp;
+ } else if (cmp) {
return ix;
}
- if (unicode_get_hash(ep->me_key) == hash) {
- PyObject *startkey = ep->me_key;
- Py_INCREF(startkey);
- int cmp = PyObject_RichCompareBool(startkey, key, Py_EQ);
- Py_DECREF(startkey);
- if (cmp < 0) {
- return DKIX_ERROR;
- }
- if (dk == mp->ma_keys && ep->me_key == startkey) {
- if (cmp > 0) {
- return ix;
- }
- }
- else {
- /* The dict was mutated, restart */
- return DKIX_KEY_CHANGED;
- }
- }
}
else if (ix == DKIX_EMPTY) {
return DKIX_EMPTY;
}
perturb >>= PERTURB_SHIFT;
i = mask & (i*5 + perturb + 1);
- }
- Py_UNREACHABLE();
-}
-// Search Unicode key from Unicode table.
-static Py_ssize_t _Py_HOT_FUNCTION
-unicodekeys_lookup_unicode(PyDictKeysObject* dk, PyObject *key, Py_hash_t hash)
-{
- PyDictUnicodeEntry *ep0 = DK_UNICODE_ENTRIES(dk);
- size_t mask = DK_MASK(dk);
- size_t perturb = hash;
- size_t i = (size_t)hash & mask;
- Py_ssize_t ix;
- for (;;) {
- ix = dictkeys_get_index(dk, i);
- if (ix >= 0) {
- PyDictUnicodeEntry *ep = &ep0[ix];
- assert(ep->me_key != NULL);
- assert(PyUnicode_CheckExact(ep->me_key));
- if (ep->me_key == key ||
- (unicode_get_hash(ep->me_key) == hash && unicode_eq(ep->me_key, key))) {
- return ix;
- }
- }
- else if (ix == DKIX_EMPTY) {
- return DKIX_EMPTY;
- }
- perturb >>= PERTURB_SHIFT;
- i = mask & (i*5 + perturb + 1);
// Manual loop unrolling
ix = dictkeys_get_index(dk, i);
if (ix >= 0) {
- PyDictUnicodeEntry *ep = &ep0[ix];
- assert(ep->me_key != NULL);
- assert(PyUnicode_CheckExact(ep->me_key));
- if (ep->me_key == key ||
- (unicode_get_hash(ep->me_key) == hash && unicode_eq(ep->me_key, key))) {
+ Py_ssize_t cmp = check_lookup(mp, dk, ep0, ix, key, hash);
+ if (cmp < 0) {
+ return cmp;
+ } else if (cmp) {
return ix;
}
}
@@ -965,49 +918,94 @@ unicodekeys_lookup_unicode(PyDictKeysObject* dk, PyObject *key, Py_hash_t hash)
Py_UNREACHABLE();
}
-// Search key from Generic table.
+static inline Py_ALWAYS_INLINE Py_ssize_t
+compare_unicode_generic(PyDictObject *mp, PyDictKeysObject *dk,
+ void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash)
+{
+ PyDictUnicodeEntry *ep = &((PyDictUnicodeEntry *)ep0)[ix];
+ assert(ep->me_key != NULL);
+ assert(PyUnicode_CheckExact(ep->me_key));
+ assert(!PyUnicode_CheckExact(key));
+ // TODO: Thread safety
+
+ if (unicode_get_hash(ep->me_key) == hash) {
+ PyObject *startkey = ep->me_key;
+ Py_INCREF(startkey);
+ int cmp = PyObject_RichCompareBool(startkey, key, Py_EQ);
+ Py_DECREF(startkey);
+ if (cmp < 0) {
+ return DKIX_ERROR;
+ }
+ if (dk == mp->ma_keys && ep->me_key == startkey) {
+ return cmp;
+ }
+ else {
+ /* The dict was mutated, restart */
+ return DKIX_KEY_CHANGED;
+ }
+ }
+ return 0;
+}
+
+// Search non-Unicode key from Unicode table
static Py_ssize_t
-dictkeys_generic_lookup(PyDictObject *mp, PyDictKeysObject* dk, PyObject *key, Py_hash_t hash)
+unicodekeys_lookup_generic(PyDictObject *mp, PyDictKeysObject* dk, PyObject *key, Py_hash_t hash)
{
- PyDictKeyEntry *ep0 = DK_ENTRIES(dk);
- size_t mask = DK_MASK(dk);
- size_t perturb = hash;
- size_t i = (size_t)hash & mask;
- Py_ssize_t ix;
- for (;;) {
- ix = dictkeys_get_index(dk, i);
- if (ix >= 0) {
- PyDictKeyEntry *ep = &ep0[ix];
- assert(ep->me_key != NULL);
- if (ep->me_key == key) {
- return ix;
- }
- if (ep->me_hash == hash) {
- PyObject *startkey = ep->me_key;
- Py_INCREF(startkey);
- int cmp = PyObject_RichCompareBool(startkey, key, Py_EQ);
- Py_DECREF(startkey);
- if (cmp < 0) {
- return DKIX_ERROR;
- }
- if (dk == mp->ma_keys && ep->me_key == startkey) {
- if (cmp > 0) {
- return ix;
- }
- }
- else {
- /* The dict was mutated, restart */
- return DKIX_KEY_CHANGED;
- }
- }
+ return do_lookup(mp, dk, key, hash, compare_unicode_generic);
+}
+
+static inline Py_ALWAYS_INLINE Py_ssize_t
+compare_unicode_unicode(PyDictObject *mp, PyDictKeysObject *dk,
+ void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash)
+{
+ PyDictUnicodeEntry *ep = &((PyDictUnicodeEntry *)ep0)[ix];
+ assert(ep->me_key != NULL);
+ assert(PyUnicode_CheckExact(ep->me_key));
+ if (ep->me_key == key ||
+ (unicode_get_hash(ep->me_key) == hash && unicode_eq(ep->me_key, key))) {
+ return 1;
+ }
+ return 0;
+}
+
+static Py_ssize_t _Py_HOT_FUNCTION
+unicodekeys_lookup_unicode(PyDictKeysObject* dk, PyObject *key, Py_hash_t hash)
+{
+ return do_lookup(NULL, dk, key, hash, compare_unicode_unicode);
+}
+
+static inline Py_ALWAYS_INLINE Py_ssize_t
+compare_generic(PyDictObject *mp, PyDictKeysObject *dk,
+ void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash)
+{
+ PyDictKeyEntry *ep = &((PyDictKeyEntry *)ep0)[ix];
+ assert(ep->me_key != NULL);
+ if (ep->me_key == key) {
+ return 1;
+ }
+ if (ep->me_hash == hash) {
+ PyObject *startkey = ep->me_key;
+ Py_INCREF(startkey);
+ int cmp = PyObject_RichCompareBool(startkey, key, Py_EQ);
+ Py_DECREF(startkey);
+ if (cmp < 0) {
+ return DKIX_ERROR;
}
- else if (ix == DKIX_EMPTY) {
- return DKIX_EMPTY;
+ if (dk == mp->ma_keys && ep->me_key == startkey) {
+ return cmp;
+ }
+ else {
+ /* The dict was mutated, restart */
+ return DKIX_KEY_CHANGED;
}
- perturb >>= PERTURB_SHIFT;
- i = mask & (i*5 + perturb + 1);
}
- Py_UNREACHABLE();
+ return 0;
+}
+
+static Py_ssize_t
+dictkeys_generic_lookup(PyDictObject *mp, PyDictKeysObject* dk, PyObject *key, Py_hash_t hash)
+{
+ return do_lookup(mp, dk, key, hash, compare_generic);
}
/* Lookup a string in a (all unicode) dict keys.
1
0
gh-113744: Add a new IncompleteInputError exception to improve incomplete input detection in the codeop module (#113745)
by pablogsal 30 Jan '24
by pablogsal 30 Jan '24
30 Jan '24
https://github.com/python/cpython/commit/39d102c2ee8eec8ab0bacbcd62d62a7274…
commit: 39d102c2ee8eec8ab0bacbcd62d62a72742ecc7c
branch: main
author: Pablo Galindo Salgado <Pablogsal(a)gmail.com>
committer: pablogsal <Pablogsal(a)gmail.com>
date: 2024-01-30T16:21:30Z
summary:
gh-113744: Add a new IncompleteInputError exception to improve incomplete input detection in the codeop module (#113745)
Signed-off-by: Pablo Galindo <pablogsal(a)gmail.com>
files:
M Doc/data/stable_abi.dat
M Include/pyerrors.h
M Lib/codeop.py
M Lib/test/exception_hierarchy.txt
M Lib/test/test_pickle.py
M Lib/test/test_stable_abi_ctypes.py
M Misc/stable_abi.toml
M Objects/exceptions.c
M PC/python3dll.c
M Parser/pegen.c
M Tools/c-analyzer/cpython/globals-to-fix.tsv
diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat
index 811b1bd84d2417..da28a2be60bc1b 100644
--- a/Doc/data/stable_abi.dat
+++ b/Doc/data/stable_abi.dat
@@ -220,6 +220,7 @@ var,PyExc_GeneratorExit,3.2,,
var,PyExc_IOError,3.2,,
var,PyExc_ImportError,3.2,,
var,PyExc_ImportWarning,3.2,,
+var,PyExc_IncompleteInputError,3.13,,
var,PyExc_IndentationError,3.2,,
var,PyExc_IndexError,3.2,,
var,PyExc_InterruptedError,3.7,,
diff --git a/Include/pyerrors.h b/Include/pyerrors.h
index 5d0028c116e2d8..68d7985dac8876 100644
--- a/Include/pyerrors.h
+++ b/Include/pyerrors.h
@@ -108,6 +108,7 @@ PyAPI_DATA(PyObject *) PyExc_NotImplementedError;
PyAPI_DATA(PyObject *) PyExc_SyntaxError;
PyAPI_DATA(PyObject *) PyExc_IndentationError;
PyAPI_DATA(PyObject *) PyExc_TabError;
+PyAPI_DATA(PyObject *) PyExc_IncompleteInputError;
PyAPI_DATA(PyObject *) PyExc_ReferenceError;
PyAPI_DATA(PyObject *) PyExc_SystemError;
PyAPI_DATA(PyObject *) PyExc_SystemExit;
diff --git a/Lib/codeop.py b/Lib/codeop.py
index 91146be2c438e2..6ad60e7f85098d 100644
--- a/Lib/codeop.py
+++ b/Lib/codeop.py
@@ -65,9 +65,10 @@ def _maybe_compile(compiler, source, filename, symbol):
try:
compiler(source + "\n", filename, symbol)
return None
+ except IncompleteInputError as e:
+ return None
except SyntaxError as e:
- if "incomplete input" in str(e):
- return None
+ pass
# fallthrough
return compiler(source, filename, symbol, incomplete_input=False)
diff --git a/Lib/test/exception_hierarchy.txt b/Lib/test/exception_hierarchy.txt
index 1eca123be0fecb..217ee15d4c8af5 100644
--- a/Lib/test/exception_hierarchy.txt
+++ b/Lib/test/exception_hierarchy.txt
@@ -44,6 +44,7 @@ BaseException
├── StopAsyncIteration
├── StopIteration
├── SyntaxError
+ │ └── IncompleteInputError
│ └── IndentationError
│ └── TabError
├── SystemError
diff --git a/Lib/test/test_pickle.py b/Lib/test/test_pickle.py
index b2245ddf72f708..5e187e5189d117 100644
--- a/Lib/test/test_pickle.py
+++ b/Lib/test/test_pickle.py
@@ -567,7 +567,8 @@ def test_exceptions(self):
RecursionError,
EncodingWarning,
BaseExceptionGroup,
- ExceptionGroup):
+ ExceptionGroup,
+ IncompleteInputError):
continue
if exc is not OSError and issubclass(exc, OSError):
self.assertEqual(reverse_mapping('builtins', name),
diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py
index 90d45272838420..054e7f0feb1a19 100644
--- a/Lib/test/test_stable_abi_ctypes.py
+++ b/Lib/test/test_stable_abi_ctypes.py
@@ -261,6 +261,7 @@ def test_windows_feature_macros(self):
"PyExc_IOError",
"PyExc_ImportError",
"PyExc_ImportWarning",
+ "PyExc_IncompleteInputError",
"PyExc_IndentationError",
"PyExc_IndexError",
"PyExc_InterruptedError",
diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml
index 2e6b0fff9cd770..ae19d25809ec86 100644
--- a/Misc/stable_abi.toml
+++ b/Misc/stable_abi.toml
@@ -2485,3 +2485,5 @@
[function._Py_SetRefcnt]
added = '3.13'
abi_only = true
+[data.PyExc_IncompleteInputError]
+ added = '3.13'
diff --git a/Objects/exceptions.c b/Objects/exceptions.c
index a685ed803cd02d..cff55d05163b6b 100644
--- a/Objects/exceptions.c
+++ b/Objects/exceptions.c
@@ -2566,6 +2566,11 @@ MiddlingExtendsException(PyExc_SyntaxError, IndentationError, SyntaxError,
MiddlingExtendsException(PyExc_IndentationError, TabError, SyntaxError,
"Improper mixture of spaces and tabs.");
+/*
+ * IncompleteInputError extends SyntaxError
+ */
+MiddlingExtendsException(PyExc_SyntaxError, IncompleteInputError, SyntaxError,
+ "incomplete input.");
/*
* LookupError extends Exception
@@ -3635,6 +3640,7 @@ static struct static_exception static_exceptions[] = {
// Level 4: Other subclasses
ITEM(IndentationError), // base: SyntaxError(Exception)
+ ITEM(IncompleteInputError), // base: SyntaxError(Exception)
ITEM(IndexError), // base: LookupError(Exception)
ITEM(KeyError), // base: LookupError(Exception)
ITEM(ModuleNotFoundError), // base: ImportError(Exception)
diff --git a/PC/python3dll.c b/PC/python3dll.c
index 07aa84c91f9fc7..09ecf98fe56ea6 100755
--- a/PC/python3dll.c
+++ b/PC/python3dll.c
@@ -830,6 +830,7 @@ EXPORT_DATA(PyExc_FutureWarning)
EXPORT_DATA(PyExc_GeneratorExit)
EXPORT_DATA(PyExc_ImportError)
EXPORT_DATA(PyExc_ImportWarning)
+EXPORT_DATA(PyExc_IncompleteInputError)
EXPORT_DATA(PyExc_IndentationError)
EXPORT_DATA(PyExc_IndexError)
EXPORT_DATA(PyExc_InterruptedError)
diff --git a/Parser/pegen.c b/Parser/pegen.c
index 7766253a76066f..3d3e64559403b1 100644
--- a/Parser/pegen.c
+++ b/Parser/pegen.c
@@ -844,7 +844,7 @@ _PyPegen_run_parser(Parser *p)
if (res == NULL) {
if ((p->flags & PyPARSE_ALLOW_INCOMPLETE_INPUT) && _is_end_of_source(p)) {
PyErr_Clear();
- return RAISE_SYNTAX_ERROR("incomplete input");
+ return _PyPegen_raise_error(p, PyExc_IncompleteInputError, 0, "incomplete input");
}
if (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_SyntaxError)) {
return NULL;
diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv
index e3a1b5d532bda2..0b02ad01d39983 100644
--- a/Tools/c-analyzer/cpython/globals-to-fix.tsv
+++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv
@@ -197,6 +197,7 @@ Objects/exceptions.c - _PyExc_AttributeError -
Objects/exceptions.c - _PyExc_SyntaxError -
Objects/exceptions.c - _PyExc_IndentationError -
Objects/exceptions.c - _PyExc_TabError -
+Objects/exceptions.c - _PyExc_IncompleteInputError -
Objects/exceptions.c - _PyExc_LookupError -
Objects/exceptions.c - _PyExc_IndexError -
Objects/exceptions.c - _PyExc_KeyError -
@@ -261,6 +262,7 @@ Objects/exceptions.c - PyExc_AttributeError -
Objects/exceptions.c - PyExc_SyntaxError -
Objects/exceptions.c - PyExc_IndentationError -
Objects/exceptions.c - PyExc_TabError -
+Objects/exceptions.c - PyExc_IncompleteInputError -
Objects/exceptions.c - PyExc_LookupError -
Objects/exceptions.c - PyExc_IndexError -
Objects/exceptions.c - PyExc_KeyError -
1
0
30 Jan '24
https://github.com/python/cpython/commit/1f515e8a109204f7399d85b7fd80613516…
commit: 1f515e8a109204f7399d85b7fd806135166422d9
branch: main
author: Eugene Toder <eltoder(a)users.noreply.github.com>
committer: serhiy-storchaka <storchaka(a)gmail.com>
date: 2024-01-30T15:19:46Z
summary:
gh-112919: Speed-up datetime, date and time.replace() (GH-112921)
Use argument clinic and call new_* functions directly. This speeds up
these functions 6x to 7.5x when calling with keyword arguments.
files:
A Misc/NEWS.d/next/Library/2023-12-09-23-31-17.gh-issue-112919.S5k9QN.rst
M Include/internal/pycore_global_objects_fini_generated.h
M Include/internal/pycore_global_strings.h
M Include/internal/pycore_runtime_init_generated.h
M Include/internal/pycore_unicodeobject_generated.h
M Modules/_datetimemodule.c
M Modules/clinic/_datetimemodule.c.h
diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h
index 57505b5388fd6c..dd09ff40f39fe6 100644
--- a/Include/internal/pycore_global_objects_fini_generated.h
+++ b/Include/internal/pycore_global_objects_fini_generated.h
@@ -876,6 +876,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(d));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(data));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(database));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(day));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(decode));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(decoder));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(default));
@@ -945,6 +946,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(fix_imports));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(flags));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(flush));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(fold));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(follow_symlinks));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(format));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(from_param));
@@ -975,6 +977,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(headers));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(hi));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(hook));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(hour));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(id));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(ident));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(identity_hint));
@@ -1059,11 +1062,14 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(metaclass));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(metadata));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(method));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(microsecond));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(minute));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mod));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mode));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(module));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(module_globals));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(modules));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(month));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mro));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(msg));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mycmp));
@@ -1168,6 +1174,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(salt));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(sched_priority));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(scheduler));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(second));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(seek));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(seekable));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(selectors));
@@ -1244,6 +1251,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(type));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(type_params));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(tz));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(tzinfo));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(tzname));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(uid));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(unlink));
diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h
index 0f4f3b61910241..79d6509abcdfd9 100644
--- a/Include/internal/pycore_global_strings.h
+++ b/Include/internal/pycore_global_strings.h
@@ -365,6 +365,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(d)
STRUCT_FOR_ID(data)
STRUCT_FOR_ID(database)
+ STRUCT_FOR_ID(day)
STRUCT_FOR_ID(decode)
STRUCT_FOR_ID(decoder)
STRUCT_FOR_ID(default)
@@ -434,6 +435,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(fix_imports)
STRUCT_FOR_ID(flags)
STRUCT_FOR_ID(flush)
+ STRUCT_FOR_ID(fold)
STRUCT_FOR_ID(follow_symlinks)
STRUCT_FOR_ID(format)
STRUCT_FOR_ID(from_param)
@@ -464,6 +466,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(headers)
STRUCT_FOR_ID(hi)
STRUCT_FOR_ID(hook)
+ STRUCT_FOR_ID(hour)
STRUCT_FOR_ID(id)
STRUCT_FOR_ID(ident)
STRUCT_FOR_ID(identity_hint)
@@ -548,11 +551,14 @@ struct _Py_global_strings {
STRUCT_FOR_ID(metaclass)
STRUCT_FOR_ID(metadata)
STRUCT_FOR_ID(method)
+ STRUCT_FOR_ID(microsecond)
+ STRUCT_FOR_ID(minute)
STRUCT_FOR_ID(mod)
STRUCT_FOR_ID(mode)
STRUCT_FOR_ID(module)
STRUCT_FOR_ID(module_globals)
STRUCT_FOR_ID(modules)
+ STRUCT_FOR_ID(month)
STRUCT_FOR_ID(mro)
STRUCT_FOR_ID(msg)
STRUCT_FOR_ID(mycmp)
@@ -657,6 +663,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(salt)
STRUCT_FOR_ID(sched_priority)
STRUCT_FOR_ID(scheduler)
+ STRUCT_FOR_ID(second)
STRUCT_FOR_ID(seek)
STRUCT_FOR_ID(seekable)
STRUCT_FOR_ID(selectors)
@@ -733,6 +740,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(type)
STRUCT_FOR_ID(type_params)
STRUCT_FOR_ID(tz)
+ STRUCT_FOR_ID(tzinfo)
STRUCT_FOR_ID(tzname)
STRUCT_FOR_ID(uid)
STRUCT_FOR_ID(unlink)
diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h
index 63a2b54c839a4b..f3c55acfb3c282 100644
--- a/Include/internal/pycore_runtime_init_generated.h
+++ b/Include/internal/pycore_runtime_init_generated.h
@@ -874,6 +874,7 @@ extern "C" {
INIT_ID(d), \
INIT_ID(data), \
INIT_ID(database), \
+ INIT_ID(day), \
INIT_ID(decode), \
INIT_ID(decoder), \
INIT_ID(default), \
@@ -943,6 +944,7 @@ extern "C" {
INIT_ID(fix_imports), \
INIT_ID(flags), \
INIT_ID(flush), \
+ INIT_ID(fold), \
INIT_ID(follow_symlinks), \
INIT_ID(format), \
INIT_ID(from_param), \
@@ -973,6 +975,7 @@ extern "C" {
INIT_ID(headers), \
INIT_ID(hi), \
INIT_ID(hook), \
+ INIT_ID(hour), \
INIT_ID(id), \
INIT_ID(ident), \
INIT_ID(identity_hint), \
@@ -1057,11 +1060,14 @@ extern "C" {
INIT_ID(metaclass), \
INIT_ID(metadata), \
INIT_ID(method), \
+ INIT_ID(microsecond), \
+ INIT_ID(minute), \
INIT_ID(mod), \
INIT_ID(mode), \
INIT_ID(module), \
INIT_ID(module_globals), \
INIT_ID(modules), \
+ INIT_ID(month), \
INIT_ID(mro), \
INIT_ID(msg), \
INIT_ID(mycmp), \
@@ -1166,6 +1172,7 @@ extern "C" {
INIT_ID(salt), \
INIT_ID(sched_priority), \
INIT_ID(scheduler), \
+ INIT_ID(second), \
INIT_ID(seek), \
INIT_ID(seekable), \
INIT_ID(selectors), \
@@ -1242,6 +1249,7 @@ extern "C" {
INIT_ID(type), \
INIT_ID(type_params), \
INIT_ID(tz), \
+ INIT_ID(tzinfo), \
INIT_ID(tzname), \
INIT_ID(uid), \
INIT_ID(unlink), \
diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h
index bf8cdd85e4be5c..2e9572382fe033 100644
--- a/Include/internal/pycore_unicodeobject_generated.h
+++ b/Include/internal/pycore_unicodeobject_generated.h
@@ -936,6 +936,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
string = &_Py_ID(database);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
+ string = &_Py_ID(day);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ _PyUnicode_InternInPlace(interp, &string);
string = &_Py_ID(decode);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
@@ -1143,6 +1146,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
string = &_Py_ID(flush);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
+ string = &_Py_ID(fold);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ _PyUnicode_InternInPlace(interp, &string);
string = &_Py_ID(follow_symlinks);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
@@ -1233,6 +1239,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
string = &_Py_ID(hook);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
+ string = &_Py_ID(hour);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ _PyUnicode_InternInPlace(interp, &string);
string = &_Py_ID(id);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
@@ -1485,6 +1494,12 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
string = &_Py_ID(method);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
+ string = &_Py_ID(microsecond);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ _PyUnicode_InternInPlace(interp, &string);
+ string = &_Py_ID(minute);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ _PyUnicode_InternInPlace(interp, &string);
string = &_Py_ID(mod);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
@@ -1500,6 +1515,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
string = &_Py_ID(modules);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
+ string = &_Py_ID(month);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ _PyUnicode_InternInPlace(interp, &string);
string = &_Py_ID(mro);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
@@ -1812,6 +1830,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
string = &_Py_ID(scheduler);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
+ string = &_Py_ID(second);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ _PyUnicode_InternInPlace(interp, &string);
string = &_Py_ID(seek);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
@@ -2040,6 +2061,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
string = &_Py_ID(tz);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
+ string = &_Py_ID(tzinfo);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ _PyUnicode_InternInPlace(interp, &string);
string = &_Py_ID(tzname);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
diff --git a/Misc/NEWS.d/next/Library/2023-12-09-23-31-17.gh-issue-112919.S5k9QN.rst b/Misc/NEWS.d/next/Library/2023-12-09-23-31-17.gh-issue-112919.S5k9QN.rst
new file mode 100644
index 00000000000000..3e99d480139cbe
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-12-09-23-31-17.gh-issue-112919.S5k9QN.rst
@@ -0,0 +1,2 @@
+Speed-up :func:`datetime.datetime.replace`, :func:`datetime.date.replace` and
+:func:`datetime.time.replace`.
diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c
index cb5403e8461ff0..9b8e0a719d9048 100644
--- a/Modules/_datetimemodule.c
+++ b/Modules/_datetimemodule.c
@@ -61,16 +61,6 @@ static datetime_state _datetime_global_state;
#define STATIC_STATE() (&_datetime_global_state)
-/*[clinic input]
-module datetime
-class datetime.datetime "PyDateTime_DateTime *" "&PyDateTime_DateTimeType"
-class datetime.date "PyDateTime_Date *" "&PyDateTime_DateType"
-class datetime.IsoCalendarDate "PyDateTime_IsoCalendarDate *" "&PyDateTime_IsoCalendarDateType"
-[clinic start generated code]*/
-/*[clinic end generated code: output=da39a3ee5e6b4b0d input=81bec0fa19837f63]*/
-
-#include "clinic/_datetimemodule.c.h"
-
/* We require that C int be at least 32 bits, and use int virtually
* everywhere. In just a few cases we use a temp long, where a Python
* API returns a C long. In such cases, we have to ensure that the
@@ -161,6 +151,17 @@ static PyTypeObject PyDateTime_TimeZoneType;
static int check_tzinfo_subclass(PyObject *p);
+/*[clinic input]
+module datetime
+class datetime.datetime "PyDateTime_DateTime *" "&PyDateTime_DateTimeType"
+class datetime.date "PyDateTime_Date *" "&PyDateTime_DateType"
+class datetime.time "PyDateTime_Time *" "&PyDateTime_TimeType"
+class datetime.IsoCalendarDate "PyDateTime_IsoCalendarDate *" "&PyDateTime_IsoCalendarDateType"
+[clinic start generated code]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=6f65a48dd22fa40f]*/
+
+#include "clinic/_datetimemodule.c.h"
+
/* ---------------------------------------------------------------------------
* Math utilities.
@@ -3466,24 +3467,22 @@ date_timetuple(PyDateTime_Date *self, PyObject *Py_UNUSED(ignored))
0, 0, 0, -1);
}
+/*[clinic input]
+datetime.date.replace
+
+ year: int(c_default="GET_YEAR(self)") = unchanged
+ month: int(c_default="GET_MONTH(self)") = unchanged
+ day: int(c_default="GET_DAY(self)") = unchanged
+
+Return date with new specified fields.
+[clinic start generated code]*/
+
static PyObject *
-date_replace(PyDateTime_Date *self, PyObject *args, PyObject *kw)
+datetime_date_replace_impl(PyDateTime_Date *self, int year, int month,
+ int day)
+/*[clinic end generated code: output=2a9430d1e6318aeb input=0d1f02685b3e90f6]*/
{
- PyObject *clone;
- PyObject *tuple;
- int year = GET_YEAR(self);
- int month = GET_MONTH(self);
- int day = GET_DAY(self);
-
- if (! PyArg_ParseTupleAndKeywords(args, kw, "|iii:replace", date_kws,
- &year, &month, &day))
- return NULL;
- tuple = Py_BuildValue("iii", year, month, day);
- if (tuple == NULL)
- return NULL;
- clone = date_new(Py_TYPE(self), tuple, NULL);
- Py_DECREF(tuple);
- return clone;
+ return new_date_ex(year, month, day, Py_TYPE(self));
}
static Py_hash_t
@@ -3596,10 +3595,9 @@ static PyMethodDef date_methods[] = {
PyDoc_STR("Return the day of the week represented by the date.\n"
"Monday == 0 ... Sunday == 6")},
- {"replace", _PyCFunction_CAST(date_replace), METH_VARARGS | METH_KEYWORDS,
- PyDoc_STR("Return date with new specified fields.")},
+ DATETIME_DATE_REPLACE_METHODDEF
- {"__replace__", _PyCFunction_CAST(date_replace), METH_VARARGS | METH_KEYWORDS},
+ {"__replace__", _PyCFunction_CAST(datetime_date_replace), METH_FASTCALL | METH_KEYWORDS},
{"__reduce__", (PyCFunction)date_reduce, METH_NOARGS,
PyDoc_STR("__reduce__() -> (cls, state)")},
@@ -4573,36 +4571,28 @@ time_hash(PyDateTime_Time *self)
return self->hashcode;
}
+/*[clinic input]
+datetime.time.replace
+
+ hour: int(c_default="TIME_GET_HOUR(self)") = unchanged
+ minute: int(c_default="TIME_GET_MINUTE(self)") = unchanged
+ second: int(c_default="TIME_GET_SECOND(self)") = unchanged
+ microsecond: int(c_default="TIME_GET_MICROSECOND(self)") = unchanged
+ tzinfo: object(c_default="HASTZINFO(self) ? self->tzinfo : Py_None") = unchanged
+ *
+ fold: int(c_default="TIME_GET_FOLD(self)") = unchanged
+
+Return time with new specified fields.
+[clinic start generated code]*/
+
static PyObject *
-time_replace(PyDateTime_Time *self, PyObject *args, PyObject *kw)
+datetime_time_replace_impl(PyDateTime_Time *self, int hour, int minute,
+ int second, int microsecond, PyObject *tzinfo,
+ int fold)
+/*[clinic end generated code: output=0b89a44c299e4f80 input=9b6a35b1e704b0ca]*/
{
- PyObject *clone;
- PyObject *tuple;
- int hh = TIME_GET_HOUR(self);
- int mm = TIME_GET_MINUTE(self);
- int ss = TIME_GET_SECOND(self);
- int us = TIME_GET_MICROSECOND(self);
- PyObject *tzinfo = HASTZINFO(self) ? self->tzinfo : Py_None;
- int fold = TIME_GET_FOLD(self);
-
- if (! PyArg_ParseTupleAndKeywords(args, kw, "|iiiiO$i:replace",
- time_kws,
- &hh, &mm, &ss, &us, &tzinfo, &fold))
- return NULL;
- if (fold != 0 && fold != 1) {
- PyErr_SetString(PyExc_ValueError,
- "fold must be either 0 or 1");
- return NULL;
- }
- tuple = Py_BuildValue("iiiiO", hh, mm, ss, us, tzinfo);
- if (tuple == NULL)
- return NULL;
- clone = time_new(Py_TYPE(self), tuple, NULL);
- if (clone != NULL) {
- TIME_SET_FOLD(clone, fold);
- }
- Py_DECREF(tuple);
- return clone;
+ return new_time_ex2(hour, minute, second, microsecond, tzinfo, fold,
+ Py_TYPE(self));
}
static PyObject *
@@ -4732,10 +4722,9 @@ static PyMethodDef time_methods[] = {
{"dst", (PyCFunction)time_dst, METH_NOARGS,
PyDoc_STR("Return self.tzinfo.dst(self).")},
- {"replace", _PyCFunction_CAST(time_replace), METH_VARARGS | METH_KEYWORDS,
- PyDoc_STR("Return time with new specified fields.")},
+ DATETIME_TIME_REPLACE_METHODDEF
- {"__replace__", _PyCFunction_CAST(time_replace), METH_VARARGS | METH_KEYWORDS},
+ {"__replace__", _PyCFunction_CAST(datetime_time_replace), METH_FASTCALL | METH_KEYWORDS},
{"fromisoformat", (PyCFunction)time_fromisoformat, METH_O | METH_CLASS,
PyDoc_STR("string -> time from a string in ISO 8601 format")},
@@ -6042,40 +6031,32 @@ datetime_hash(PyDateTime_DateTime *self)
return self->hashcode;
}
+/*[clinic input]
+datetime.datetime.replace
+
+ year: int(c_default="GET_YEAR(self)") = unchanged
+ month: int(c_default="GET_MONTH(self)") = unchanged
+ day: int(c_default="GET_DAY(self)") = unchanged
+ hour: int(c_default="DATE_GET_HOUR(self)") = unchanged
+ minute: int(c_default="DATE_GET_MINUTE(self)") = unchanged
+ second: int(c_default="DATE_GET_SECOND(self)") = unchanged
+ microsecond: int(c_default="DATE_GET_MICROSECOND(self)") = unchanged
+ tzinfo: object(c_default="HASTZINFO(self) ? self->tzinfo : Py_None") = unchanged
+ *
+ fold: int(c_default="DATE_GET_FOLD(self)") = unchanged
+
+Return datetime with new specified fields.
+[clinic start generated code]*/
+
static PyObject *
-datetime_replace(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
+datetime_datetime_replace_impl(PyDateTime_DateTime *self, int year,
+ int month, int day, int hour, int minute,
+ int second, int microsecond, PyObject *tzinfo,
+ int fold)
+/*[clinic end generated code: output=00bc96536833fddb input=9b38253d56d9bcad]*/
{
- PyObject *clone;
- PyObject *tuple;
- int y = GET_YEAR(self);
- int m = GET_MONTH(self);
- int d = GET_DAY(self);
- int hh = DATE_GET_HOUR(self);
- int mm = DATE_GET_MINUTE(self);
- int ss = DATE_GET_SECOND(self);
- int us = DATE_GET_MICROSECOND(self);
- PyObject *tzinfo = HASTZINFO(self) ? self->tzinfo : Py_None;
- int fold = DATE_GET_FOLD(self);
-
- if (! PyArg_ParseTupleAndKeywords(args, kw, "|iiiiiiiO$i:replace",
- datetime_kws,
- &y, &m, &d, &hh, &mm, &ss, &us,
- &tzinfo, &fold))
- return NULL;
- if (fold != 0 && fold != 1) {
- PyErr_SetString(PyExc_ValueError,
- "fold must be either 0 or 1");
- return NULL;
- }
- tuple = Py_BuildValue("iiiiiiiO", y, m, d, hh, mm, ss, us, tzinfo);
- if (tuple == NULL)
- return NULL;
- clone = datetime_new(Py_TYPE(self), tuple, NULL);
- if (clone != NULL) {
- DATE_SET_FOLD(clone, fold);
- }
- Py_DECREF(tuple);
- return clone;
+ return new_datetime_ex2(year, month, day, hour, minute, second,
+ microsecond, tzinfo, fold, Py_TYPE(self));
}
static PyObject *
@@ -6597,10 +6578,9 @@ static PyMethodDef datetime_methods[] = {
{"dst", (PyCFunction)datetime_dst, METH_NOARGS,
PyDoc_STR("Return self.tzinfo.dst(self).")},
- {"replace", _PyCFunction_CAST(datetime_replace), METH_VARARGS | METH_KEYWORDS,
- PyDoc_STR("Return datetime with new specified fields.")},
+ DATETIME_DATETIME_REPLACE_METHODDEF
- {"__replace__", _PyCFunction_CAST(datetime_replace), METH_VARARGS | METH_KEYWORDS},
+ {"__replace__", _PyCFunction_CAST(datetime_datetime_replace), METH_FASTCALL | METH_KEYWORDS},
{"astimezone", _PyCFunction_CAST(datetime_astimezone), METH_VARARGS | METH_KEYWORDS,
PyDoc_STR("tz -> convert to local time in new timezone tz\n")},
diff --git a/Modules/clinic/_datetimemodule.c.h b/Modules/clinic/_datetimemodule.c.h
index 1ee50fc2a13762..48499e0aaf7783 100644
--- a/Modules/clinic/_datetimemodule.c.h
+++ b/Modules/clinic/_datetimemodule.c.h
@@ -82,6 +82,207 @@ iso_calendar_date_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
return return_value;
}
+PyDoc_STRVAR(datetime_date_replace__doc__,
+"replace($self, /, year=unchanged, month=unchanged, day=unchanged)\n"
+"--\n"
+"\n"
+"Return date with new specified fields.");
+
+#define DATETIME_DATE_REPLACE_METHODDEF \
+ {"replace", _PyCFunction_CAST(datetime_date_replace), METH_FASTCALL|METH_KEYWORDS, datetime_date_replace__doc__},
+
+static PyObject *
+datetime_date_replace_impl(PyDateTime_Date *self, int year, int month,
+ int day);
+
+static PyObject *
+datetime_date_replace(PyDateTime_Date *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+{
+ PyObject *return_value = NULL;
+ #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+ #define NUM_KEYWORDS 3
+ static struct {
+ PyGC_Head _this_is_not_used;
+ PyObject_VAR_HEAD
+ PyObject *ob_item[NUM_KEYWORDS];
+ } _kwtuple = {
+ .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+ .ob_item = { &_Py_ID(year), &_Py_ID(month), &_Py_ID(day), },
+ };
+ #undef NUM_KEYWORDS
+ #define KWTUPLE (&_kwtuple.ob_base.ob_base)
+
+ #else // !Py_BUILD_CORE
+ # define KWTUPLE NULL
+ #endif // !Py_BUILD_CORE
+
+ static const char * const _keywords[] = {"year", "month", "day", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "replace",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[3];
+ Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
+ int year = GET_YEAR(self);
+ int month = GET_MONTH(self);
+ int day = GET_DAY(self);
+
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 3, 0, argsbuf);
+ if (!args) {
+ goto exit;
+ }
+ if (!noptargs) {
+ goto skip_optional_pos;
+ }
+ if (args[0]) {
+ year = PyLong_AsInt(args[0]);
+ if (year == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
+ }
+ if (args[1]) {
+ month = PyLong_AsInt(args[1]);
+ if (month == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
+ }
+ day = PyLong_AsInt(args[2]);
+ if (day == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+skip_optional_pos:
+ return_value = datetime_date_replace_impl(self, year, month, day);
+
+exit:
+ return return_value;
+}
+
+PyDoc_STRVAR(datetime_time_replace__doc__,
+"replace($self, /, hour=unchanged, minute=unchanged, second=unchanged,\n"
+" microsecond=unchanged, tzinfo=unchanged, *, fold=unchanged)\n"
+"--\n"
+"\n"
+"Return time with new specified fields.");
+
+#define DATETIME_TIME_REPLACE_METHODDEF \
+ {"replace", _PyCFunction_CAST(datetime_time_replace), METH_FASTCALL|METH_KEYWORDS, datetime_time_replace__doc__},
+
+static PyObject *
+datetime_time_replace_impl(PyDateTime_Time *self, int hour, int minute,
+ int second, int microsecond, PyObject *tzinfo,
+ int fold);
+
+static PyObject *
+datetime_time_replace(PyDateTime_Time *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+{
+ PyObject *return_value = NULL;
+ #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+ #define NUM_KEYWORDS 6
+ static struct {
+ PyGC_Head _this_is_not_used;
+ PyObject_VAR_HEAD
+ PyObject *ob_item[NUM_KEYWORDS];
+ } _kwtuple = {
+ .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+ .ob_item = { &_Py_ID(hour), &_Py_ID(minute), &_Py_ID(second), &_Py_ID(microsecond), &_Py_ID(tzinfo), &_Py_ID(fold), },
+ };
+ #undef NUM_KEYWORDS
+ #define KWTUPLE (&_kwtuple.ob_base.ob_base)
+
+ #else // !Py_BUILD_CORE
+ # define KWTUPLE NULL
+ #endif // !Py_BUILD_CORE
+
+ static const char * const _keywords[] = {"hour", "minute", "second", "microsecond", "tzinfo", "fold", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "replace",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[6];
+ Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
+ int hour = TIME_GET_HOUR(self);
+ int minute = TIME_GET_MINUTE(self);
+ int second = TIME_GET_SECOND(self);
+ int microsecond = TIME_GET_MICROSECOND(self);
+ PyObject *tzinfo = HASTZINFO(self) ? self->tzinfo : Py_None;
+ int fold = TIME_GET_FOLD(self);
+
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 5, 0, argsbuf);
+ if (!args) {
+ goto exit;
+ }
+ if (!noptargs) {
+ goto skip_optional_pos;
+ }
+ if (args[0]) {
+ hour = PyLong_AsInt(args[0]);
+ if (hour == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
+ }
+ if (args[1]) {
+ minute = PyLong_AsInt(args[1]);
+ if (minute == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
+ }
+ if (args[2]) {
+ second = PyLong_AsInt(args[2]);
+ if (second == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
+ }
+ if (args[3]) {
+ microsecond = PyLong_AsInt(args[3]);
+ if (microsecond == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
+ }
+ if (args[4]) {
+ tzinfo = args[4];
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
+ }
+skip_optional_pos:
+ if (!noptargs) {
+ goto skip_optional_kwonly;
+ }
+ fold = PyLong_AsInt(args[5]);
+ if (fold == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+skip_optional_kwonly:
+ return_value = datetime_time_replace_impl(self, hour, minute, second, microsecond, tzinfo, fold);
+
+exit:
+ return return_value;
+}
+
PyDoc_STRVAR(datetime_datetime_now__doc__,
"now($type, /, tz=None)\n"
"--\n"
@@ -146,4 +347,153 @@ datetime_datetime_now(PyTypeObject *type, PyObject *const *args, Py_ssize_t narg
exit:
return return_value;
}
-/*[clinic end generated code: output=562813dd3e164794 input=a9049054013a1b77]*/
+
+PyDoc_STRVAR(datetime_datetime_replace__doc__,
+"replace($self, /, year=unchanged, month=unchanged, day=unchanged,\n"
+" hour=unchanged, minute=unchanged, second=unchanged,\n"
+" microsecond=unchanged, tzinfo=unchanged, *, fold=unchanged)\n"
+"--\n"
+"\n"
+"Return datetime with new specified fields.");
+
+#define DATETIME_DATETIME_REPLACE_METHODDEF \
+ {"replace", _PyCFunction_CAST(datetime_datetime_replace), METH_FASTCALL|METH_KEYWORDS, datetime_datetime_replace__doc__},
+
+static PyObject *
+datetime_datetime_replace_impl(PyDateTime_DateTime *self, int year,
+ int month, int day, int hour, int minute,
+ int second, int microsecond, PyObject *tzinfo,
+ int fold);
+
+static PyObject *
+datetime_datetime_replace(PyDateTime_DateTime *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+{
+ PyObject *return_value = NULL;
+ #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+ #define NUM_KEYWORDS 9
+ static struct {
+ PyGC_Head _this_is_not_used;
+ PyObject_VAR_HEAD
+ PyObject *ob_item[NUM_KEYWORDS];
+ } _kwtuple = {
+ .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+ .ob_item = { &_Py_ID(year), &_Py_ID(month), &_Py_ID(day), &_Py_ID(hour), &_Py_ID(minute), &_Py_ID(second), &_Py_ID(microsecond), &_Py_ID(tzinfo), &_Py_ID(fold), },
+ };
+ #undef NUM_KEYWORDS
+ #define KWTUPLE (&_kwtuple.ob_base.ob_base)
+
+ #else // !Py_BUILD_CORE
+ # define KWTUPLE NULL
+ #endif // !Py_BUILD_CORE
+
+ static const char * const _keywords[] = {"year", "month", "day", "hour", "minute", "second", "microsecond", "tzinfo", "fold", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "replace",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[9];
+ Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
+ int year = GET_YEAR(self);
+ int month = GET_MONTH(self);
+ int day = GET_DAY(self);
+ int hour = DATE_GET_HOUR(self);
+ int minute = DATE_GET_MINUTE(self);
+ int second = DATE_GET_SECOND(self);
+ int microsecond = DATE_GET_MICROSECOND(self);
+ PyObject *tzinfo = HASTZINFO(self) ? self->tzinfo : Py_None;
+ int fold = DATE_GET_FOLD(self);
+
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 8, 0, argsbuf);
+ if (!args) {
+ goto exit;
+ }
+ if (!noptargs) {
+ goto skip_optional_pos;
+ }
+ if (args[0]) {
+ year = PyLong_AsInt(args[0]);
+ if (year == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
+ }
+ if (args[1]) {
+ month = PyLong_AsInt(args[1]);
+ if (month == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
+ }
+ if (args[2]) {
+ day = PyLong_AsInt(args[2]);
+ if (day == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
+ }
+ if (args[3]) {
+ hour = PyLong_AsInt(args[3]);
+ if (hour == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
+ }
+ if (args[4]) {
+ minute = PyLong_AsInt(args[4]);
+ if (minute == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
+ }
+ if (args[5]) {
+ second = PyLong_AsInt(args[5]);
+ if (second == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
+ }
+ if (args[6]) {
+ microsecond = PyLong_AsInt(args[6]);
+ if (microsecond == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
+ }
+ if (args[7]) {
+ tzinfo = args[7];
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
+ }
+skip_optional_pos:
+ if (!noptargs) {
+ goto skip_optional_kwonly;
+ }
+ fold = PyLong_AsInt(args[8]);
+ if (fold == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+skip_optional_kwonly:
+ return_value = datetime_datetime_replace_impl(self, year, month, day, hour, minute, second, microsecond, tzinfo, fold);
+
+exit:
+ return return_value;
+}
+/*[clinic end generated code: output=c7a04b865b1e0890 input=a9049054013a1b77]*/
1
0
https://github.com/python/cpython/commit/4287e8608bcabcc5bde851d838d4709db5…
commit: 4287e8608bcabcc5bde851d838d4709db5d69089
branch: main
author: Hugo van Kemenade <1324225+hugovk(a)users.noreply.github.com>
committer: hugovk <1324225+hugovk(a)users.noreply.github.com>
date: 2024-01-30T17:12:11+02:00
summary:
gh-109975: Copyedit "What's New in Python 3.13" (#114401)
Co-authored-by: Serhiy Storchaka <storchaka(a)gmail.com>
files:
M Doc/whatsnew/3.13.rst
diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst
index 985e34b453f63a..fec1e55e0daf0e 100644
--- a/Doc/whatsnew/3.13.rst
+++ b/Doc/whatsnew/3.13.rst
@@ -146,14 +146,6 @@ New Modules
Improved Modules
================
-ast
----
-
-* :func:`ast.parse` now accepts an optional argument ``optimize``
- which is passed on to the :func:`compile` built-in. This makes it
- possible to obtain an optimized ``AST``.
- (Contributed by Irit Katriel in :gh:`108113`).
-
array
-----
@@ -161,6 +153,14 @@ array
It can be used instead of ``'u'`` type code, which is deprecated.
(Contributed by Inada Naoki in :gh:`80480`.)
+ast
+---
+
+* :func:`ast.parse` now accepts an optional argument ``optimize``
+ which is passed on to the :func:`compile` built-in. This makes it
+ possible to obtain an optimized ``AST``.
+ (Contributed by Irit Katriel in :gh:`108113`.)
+
asyncio
-------
@@ -180,6 +180,13 @@ copy
any user classes which define the :meth:`!__replace__` method.
(Contributed by Serhiy Storchaka in :gh:`108751`.)
+dbm
+---
+
+* Add :meth:`dbm.gnu.gdbm.clear` and :meth:`dbm.ndbm.ndbm.clear` methods that remove all items
+ from the database.
+ (Contributed by Donghee Na in :gh:`107122`.)
+
dis
---
@@ -189,13 +196,6 @@ dis
the ``show_offsets`` parameter.
(Contributed by Irit Katriel in :gh:`112137`.)
-dbm
----
-
-* Add :meth:`dbm.gnu.gdbm.clear` and :meth:`dbm.ndbm.ndbm.clear` methods that remove all items
- from the database.
- (Contributed by Donghee Na in :gh:`107122`.)
-
doctest
-------
@@ -213,7 +213,7 @@ email
parameter to these two functions: use ``strict=False`` to get the old
behavior, accept malformed inputs.
``getattr(email.utils, 'supports_strict_parsing', False)`` can be use to
- check if the *strict* paramater is available.
+ check if the *strict* parameter is available.
(Contributed by Thomas Dwyer and Victor Stinner for :gh:`102988` to improve
the CVE-2023-27043 fix.)
@@ -223,7 +223,7 @@ fractions
* Formatting for objects of type :class:`fractions.Fraction` now supports
the standard format specification mini-language rules for fill, alignment,
sign handling, minimum width and grouping. (Contributed by Mark Dickinson
- in :gh:`111320`)
+ in :gh:`111320`.)
glob
----
@@ -297,17 +297,17 @@ os
the new environment variable :envvar:`PYTHON_CPU_COUNT` or the new command-line option
:option:`-X cpu_count <-X>`. This option is useful for users who need to limit
CPU resources of a container system without having to modify the container (application code).
- (Contributed by Donghee Na in :gh:`109595`)
+ (Contributed by Donghee Na in :gh:`109595`.)
* Add support of :func:`os.lchmod` and the *follow_symlinks* argument
in :func:`os.chmod` on Windows.
Note that the default value of *follow_symlinks* in :func:`!os.lchmod` is
``False`` on Windows.
- (Contributed by Serhiy Storchaka in :gh:`59616`)
+ (Contributed by Serhiy Storchaka in :gh:`59616`.)
* Add support of :func:`os.fchmod` and a file descriptor
in :func:`os.chmod` on Windows.
- (Contributed by Serhiy Storchaka in :gh:`113191`)
+ (Contributed by Serhiy Storchaka in :gh:`113191`.)
* :func:`os.posix_spawn` now accepts ``env=None``, which makes the newly spawned
process use the current process environment.
@@ -357,7 +357,7 @@ pdb
the new ``exceptions [exc_number]`` command for Pdb. (Contributed by Matthias
Bussonnier in :gh:`106676`.)
-* Expressions/Statements whose prefix is a pdb command are now correctly
+* Expressions/statements whose prefix is a pdb command are now correctly
identified and executed.
(Contributed by Tian Gao in :gh:`108464`.)
@@ -487,34 +487,69 @@ Deprecated
Replace ``ctypes.ARRAY(item_type, size)`` with ``item_type * size``.
(Contributed by Victor Stinner in :gh:`105733`.)
+* :mod:`decimal`: Deprecate non-standard format specifier "N" for
+ :class:`decimal.Decimal`.
+ It was not documented and only supported in the C implementation.
+ (Contributed by Serhiy Storchaka in :gh:`89902`.)
+
+* :mod:`dis`: The ``dis.HAVE_ARGUMENT`` separator is deprecated. Check
+ membership in :data:`~dis.hasarg` instead.
+ (Contributed by Irit Katriel in :gh:`109319`.)
+
* :mod:`getopt` and :mod:`optparse` modules: They are now
- :term:`soft deprecated`: the :mod:`argparse` should be used for new projects.
+ :term:`soft deprecated`: the :mod:`argparse` module should be used for new projects.
Previously, the :mod:`optparse` module was already deprecated, its removal
was not scheduled, and no warnings was emitted: so there is no change in
practice.
(Contributed by Victor Stinner in :gh:`106535`.)
+* :mod:`gettext`: Emit deprecation warning for non-integer numbers in
+ :mod:`gettext` functions and methods that consider plural forms even if the
+ translation was not found.
+ (Contributed by Serhiy Storchaka in :gh:`88434`.)
+
* :mod:`http.server`: :class:`http.server.CGIHTTPRequestHandler` now emits a
- :exc:`DeprecationWarning` as it will be removed in 3.15. Process based CGI
- http servers have been out of favor for a very long time. This code was
+ :exc:`DeprecationWarning` as it will be removed in 3.15. Process-based CGI
+ HTTP servers have been out of favor for a very long time. This code was
outdated, unmaintained, and rarely used. It has a high potential for both
security and functionality bugs. This includes removal of the ``--cgi``
flag to the ``python -m http.server`` command line in 3.15.
* :mod:`pathlib`:
+ :meth:`pathlib.PurePath.is_reserved` is deprecated and scheduled for
+ removal in Python 3.15. Use :func:`os.path.isreserved` to detect reserved
+ paths on Windows.
+
+* :mod:`pydoc`: Deprecate undocumented :func:`!pydoc.ispackage` function.
+ (Contributed by Zackery Spytz in :gh:`64020`.)
+
+* :mod:`sqlite3`: Passing more than one positional argument to
+ :func:`sqlite3.connect` and the :class:`sqlite3.Connection` constructor is
+ deprecated. The remaining parameters will become keyword-only in Python 3.15.
+
+ Deprecate passing name, number of arguments, and the callable as keyword
+ arguments for the following :class:`sqlite3.Connection` APIs:
+
+ * :meth:`~sqlite3.Connection.create_function`
+ * :meth:`~sqlite3.Connection.create_aggregate`
+
+ Deprecate passing the callback callable by keyword for the following
+ :class:`sqlite3.Connection` APIs:
+
+ * :meth:`~sqlite3.Connection.set_authorizer`
+ * :meth:`~sqlite3.Connection.set_progress_handler`
+ * :meth:`~sqlite3.Connection.set_trace_callback`
+
+ The affected parameters will become positional-only in Python 3.15.
- * :meth:`pathlib.PurePath.is_reserved` is deprecated and scheduled for
- removal in Python 3.15. Use :func:`os.path.isreserved` to detect reserved
- paths on Windows.
+ (Contributed by Erlend E. Aasland in :gh:`107948` and :gh:`108278`.)
* :mod:`sys`: :func:`sys._enablelegacywindowsfsencoding` function.
- Replace it with :envvar:`PYTHONLEGACYWINDOWSFSENCODING` environment variable.
+ Replace it with the :envvar:`PYTHONLEGACYWINDOWSFSENCODING` environment variable.
(Contributed by Inada Naoki in :gh:`73427`.)
-* :mod:`traceback`:
-
- * The field *exc_type* of :class:`traceback.TracebackException` is
- deprecated. Use *exc_type_str* instead.
+* :mod:`traceback`: The field *exc_type* of :class:`traceback.TracebackException`
+ is deprecated. Use *exc_type_str* instead.
* :mod:`typing`:
@@ -550,39 +585,6 @@ Deprecated
They will be removed in Python 3.15.
(Contributed by Victor Stinner in :gh:`105096`.)
-* Passing more than one positional argument to :func:`sqlite3.connect` and the
- :class:`sqlite3.Connection` constructor is deprecated. The remaining
- parameters will become keyword-only in Python 3.15.
-
- Deprecate passing name, number of arguments, and the callable as keyword
- arguments, for the following :class:`sqlite3.Connection` APIs:
-
- * :meth:`~sqlite3.Connection.create_function`
- * :meth:`~sqlite3.Connection.create_aggregate`
-
- Deprecate passing the callback callable by keyword for the following
- :class:`sqlite3.Connection` APIs:
-
- * :meth:`~sqlite3.Connection.set_authorizer`
- * :meth:`~sqlite3.Connection.set_progress_handler`
- * :meth:`~sqlite3.Connection.set_trace_callback`
-
- The affected parameters will become positional-only in Python 3.15.
-
- (Contributed by Erlend E. Aasland in :gh:`107948` and :gh:`108278`.)
-
-* The ``dis.HAVE_ARGUMENT`` separator is deprecated. Check membership
- in :data:`~dis.hasarg` instead.
- (Contributed by Irit Katriel in :gh:`109319`.)
-
-* Deprecate non-standard format specifier "N" for :class:`decimal.Decimal`.
- It was not documented and only supported in the C implementation.
- (Contributed by Serhiy Storchaka in :gh:`89902`.)
-
-* Emit deprecation warning for non-integer numbers in :mod:`gettext` functions
- and methods that consider plural forms even if the translation was not found.
- (Contributed by Serhiy Storchaka in :gh:`88434`.)
-
* Calling :meth:`frame.clear` on a suspended frame raises :exc:`RuntimeError`
(as has always been the case for an executing frame).
(Contributed by Irit Katriel in :gh:`79932`.)
@@ -593,9 +595,6 @@ Deprecated
coroutine.
(Contributed by Irit Katriel in :gh:`81137`.)
-* Deprecate undocumented :func:`!pydoc.ispackage` function.
- (Contributed by Zackery Spytz in :gh:`64020`.)
-
Pending Removal in Python 3.14
------------------------------
@@ -657,11 +656,11 @@ Pending Removal in Python 3.14
:func:`~multiprocessing.set_start_method` APIs to explicitly specify when
your code *requires* ``'fork'``. See :ref:`multiprocessing-start-methods`.
-* :mod:`pathlib`: :meth:`~pathlib.PurePath.is_relative_to`,
+* :mod:`pathlib`: :meth:`~pathlib.PurePath.is_relative_to` and
:meth:`~pathlib.PurePath.relative_to`: passing additional arguments is
deprecated.
-* :func:`pkgutil.find_loader` and :func:`pkgutil.get_loader`
+* :mod:`pkgutil`: :func:`~pkgutil.find_loader` and :func:`~pkgutil.get_loader`
now raise :exc:`DeprecationWarning`;
use :func:`importlib.util.find_spec` instead.
(Contributed by Nikita Sobolev in :gh:`97850`.)
@@ -719,10 +718,16 @@ Pending Removal in Python 3.15
(Contributed by Hugo van Kemenade in :gh:`111187`.)
* :mod:`pathlib`:
+ :meth:`pathlib.PurePath.is_reserved` is deprecated and scheduled for
+ removal in Python 3.15. Use :func:`os.path.isreserved` to detect reserved
+ paths on Windows.
- * :meth:`pathlib.PurePath.is_reserved` is deprecated and scheduled for
- removal in Python 3.15. Use :func:`os.path.isreserved` to detect reserved
- paths on Windows.
+* :mod:`threading`:
+ Passing any arguments to :func:`threading.RLock` is now deprecated.
+ C version allows any numbers of args and kwargs,
+ but they are just ignored. Python version does not allow any arguments.
+ All arguments will be removed from :func:`threading.RLock` in Python 3.15.
+ (Contributed by Nikita Sobolev in :gh:`102029`.)
* :class:`typing.NamedTuple`:
@@ -749,12 +754,6 @@ Pending Removal in Python 3.15
They will be removed in Python 3.15.
(Contributed by Victor Stinner in :gh:`105096`.)
-* Passing any arguments to :func:`threading.RLock` is now deprecated.
- C version allows any numbers of args and kwargs,
- but they are just ignored. Python version does not allow any arguments.
- All arguments will be removed from :func:`threading.RLock` in Python 3.15.
- (Contributed by Nikita Sobolev in :gh:`102029`.)
-
Pending Removal in Python 3.16
------------------------------
@@ -801,6 +800,9 @@ although there is currently no date scheduled for their removal.
:data:`calendar.FEBRUARY`.
(Contributed by Prince Roshan in :gh:`103636`.)
+* :attr:`codeobject.co_lnotab`: use the :meth:`codeobject.co_lines` method
+ instead.
+
* :mod:`datetime`:
* :meth:`~datetime.datetime.utcnow`:
@@ -836,11 +838,13 @@ although there is currently no date scheduled for their removal.
underscore.
(Contributed by Serhiy Storchaka in :gh:`91760`.)
+* :mod:`!sre_compile`, :mod:`!sre_constants` and :mod:`!sre_parse` modules.
+
* :mod:`ssl` options and protocols:
* :class:`ssl.SSLContext` without protocol argument is deprecated.
* :class:`ssl.SSLContext`: :meth:`~ssl.SSLContext.set_npn_protocols` and
- :meth:`!~ssl.SSLContext.selected_npn_protocol` are deprecated: use ALPN
+ :meth:`!selected_npn_protocol` are deprecated: use ALPN
instead.
* ``ssl.OP_NO_SSL*`` options
* ``ssl.OP_NO_TLS*`` options
@@ -853,13 +857,6 @@ although there is currently no date scheduled for their removal.
* ``ssl.TLSVersion.TLSv1``
* ``ssl.TLSVersion.TLSv1_1``
-* :mod:`!sre_compile`, :mod:`!sre_constants` and :mod:`!sre_parse` modules.
-
-* :attr:`codeobject.co_lnotab`: use the :meth:`codeobject.co_lines` method
- instead.
-
-* :class:`typing.Text` (:gh:`92332`).
-
* :func:`sysconfig.is_python_build` *check_home* parameter is deprecated and
ignored.
@@ -874,14 +871,10 @@ although there is currently no date scheduled for their removal.
* :meth:`!threading.currentThread`: use :meth:`threading.current_thread`.
* :meth:`!threading.activeCount`: use :meth:`threading.active_count`.
-* :class:`unittest.IsolatedAsyncioTestCase`: it is deprecated to return a value
- that is not None from a test case.
-
-* :mod:`urllib.request`: :class:`~urllib.request.URLopener` and
- :class:`~urllib.request.FancyURLopener` style of invoking requests is
- deprecated. Use newer :func:`~urllib.request.urlopen` functions and methods.
+* :class:`typing.Text` (:gh:`92332`).
-* :func:`!urllib.parse.to_bytes`.
+* :class:`unittest.IsolatedAsyncioTestCase`: it is deprecated to return a value
+ that is not ``None`` from a test case.
* :mod:`urllib.parse` deprecated functions: :func:`~urllib.parse.urlparse` instead
@@ -895,6 +888,11 @@ although there is currently no date scheduled for their removal.
* ``splittype()``
* ``splituser()``
* ``splitvalue()``
+ * ``to_bytes()``
+
+* :mod:`urllib.request`: :class:`~urllib.request.URLopener` and
+ :class:`~urllib.request.FancyURLopener` style of invoking requests is
+ deprecated. Use newer :func:`~urllib.request.urlopen` functions and methods.
* :mod:`wsgiref`: ``SimpleHandler.stdout.write()`` should not do partial
writes.
@@ -1190,10 +1188,10 @@ Changes in the Python API
* 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 occurred during calling
- the function, report now them using :func:`sys.unraisablehook`.
- You can consider to replace these functions with other functions as
- recomended in the documentation.
+ :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`.)
* An :exc:`OSError` is now raised by :func:`getpass.getuser` for any failure to
@@ -1202,7 +1200,7 @@ Changes in the Python API
* 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
+ arguments that return ``True`` if the current interpreter is the
main interpreter.
Any library or application that provides a custom ``_thread`` module
@@ -1225,7 +1223,7 @@ Build Changes
(Contributed by Erlend Aasland in :gh:`105875`.)
* Python built with :file:`configure` :option:`--with-trace-refs` (tracing
- references) is now ABI compatible with Python release build and
+ references) is now ABI compatible with the Python release build and
:ref:`debug build <debug-build>`.
(Contributed by Victor Stinner in :gh:`108634`.)
@@ -1252,7 +1250,7 @@ New Features
(Contributed by Inada Naoki in :gh:`104922`.)
* The *keywords* parameter of :c:func:`PyArg_ParseTupleAndKeywords` and
- :c:func:`PyArg_VaParseTupleAndKeywords` has now type :c:expr:`char * const *`
+ :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
@@ -1309,14 +1307,14 @@ New Features
always steals a reference to the value.
(Contributed by Serhiy Storchaka in :gh:`86493`.)
-* Added :c:func:`PyDict_GetItemRef` and :c:func:`PyDict_GetItemStringRef`
+* 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`.)
-* Added :c:func:`PyDict_ContainsString` function: same as
+* 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`.)
@@ -1374,7 +1372,7 @@ New Features
(Contributed by Victor Stinner in :gh:`85283`.)
* Add :c:func:`PyErr_FormatUnraisable` function: similar to
- :c:func:`PyErr_WriteUnraisable`, but allow to customize the warning mesage.
+ :c:func:`PyErr_WriteUnraisable`, but allow customizing the warning message.
(Contributed by Serhiy Storchaka in :gh:`108082`.)
* Add :c:func:`PyList_Extend` and :c:func:`PyList_Clear` functions: similar to
@@ -1384,7 +1382,7 @@ New Features
* 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 missing.
+ :exc:`KeyError` if the key is missing.
(Contributed by Stefan Behnel and Victor Stinner in :gh:`111262`.)
* Add :c:func:`Py_HashPointer` function to hash a pointer.
@@ -1497,7 +1495,7 @@ Removed
-------
* Removed chained :class:`classmethod` descriptors (introduced in
- :issue:`19072`). This can no longer be used to wrap other descriptors
+ :gh:`63272`). This can no longer be used to wrap other descriptors
such as :class:`property`. The core design of this feature was flawed
and caused a number of downstream problems. To "pass-through" a
:class:`classmethod`, consider using the :attr:`!__wrapped__`
@@ -1511,14 +1509,14 @@ Removed
add ``cc @vstinner`` to the issue to notify Victor Stinner.
(Contributed by Victor Stinner in :gh:`106320`.)
-* Remove functions deprecated in Python 3.9.
+* 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.
+ :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) <PyTuple_New>`.
1
0
GH-114610: Fix `pathlib._abc.PurePathBase.with_suffix('.ext')` handling of stems (#114613)
by barneygale 30 Jan '24
by barneygale 30 Jan '24
30 Jan '24
https://github.com/python/cpython/commit/809eed48058ea7391df57ead09dff53bcc…
commit: 809eed48058ea7391df57ead09dff53bcc5d81e9
branch: main
author: Barney Gale <barney.gale(a)gmail.com>
committer: barneygale <barney.gale(a)gmail.com>
date: 2024-01-30T14:25:16Z
summary:
GH-114610: Fix `pathlib._abc.PurePathBase.with_suffix('.ext')` handling of stems (#114613)
Raise `ValueError` if `with_suffix('.ext')` is called on a path without a
stem. Paths may only have a non-empty suffix if they also have a non-empty
stem.
ABC-only bugfix; no effect on public classes.
files:
M Lib/pathlib/_abc.py
M Lib/test/test_pathlib/test_pathlib.py
M Lib/test/test_pathlib/test_pathlib_abc.py
diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py
index ad5684829ebc80..580d631cbf3b53 100644
--- a/Lib/pathlib/_abc.py
+++ b/Lib/pathlib/_abc.py
@@ -299,10 +299,13 @@ def with_suffix(self, suffix):
has no suffix, add given suffix. If the given suffix is an empty
string, remove the suffix from the path.
"""
+ stem = self.stem
if not suffix:
- return self.with_name(self.stem)
+ return self.with_name(stem)
+ elif not stem:
+ raise ValueError(f"{self!r} has an empty name")
elif suffix.startswith('.') and len(suffix) > 1:
- return self.with_name(self.stem + suffix)
+ return self.with_name(stem + suffix)
else:
raise ValueError(f"Invalid suffix {suffix!r}")
diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py
index 5ce3b605c58e63..a8cc30ef0ab63f 100644
--- a/Lib/test/test_pathlib/test_pathlib.py
+++ b/Lib/test/test_pathlib/test_pathlib.py
@@ -327,13 +327,6 @@ def test_with_stem_empty(self):
self.assertRaises(ValueError, P('a/b').with_stem, '')
self.assertRaises(ValueError, P('a/b').with_stem, '.')
- def test_with_suffix_empty(self):
- # Path doesn't have a "filename" component.
- P = self.cls
- self.assertRaises(ValueError, P('').with_suffix, '.gz')
- self.assertRaises(ValueError, P('.').with_suffix, '.gz')
- self.assertRaises(ValueError, P('/').with_suffix, '.gz')
-
def test_relative_to_several_args(self):
P = self.cls
p = P('a/b')
diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py
index ab989cb5503f99..18a612f6b81bac 100644
--- a/Lib/test/test_pathlib/test_pathlib_abc.py
+++ b/Lib/test/test_pathlib/test_pathlib_abc.py
@@ -977,9 +977,8 @@ def test_with_suffix_windows(self):
def test_with_suffix_empty(self):
P = self.cls
# Path doesn't have a "filename" component.
- self.assertEqual(P('').with_suffix('.gz'), P('.gz'))
- self.assertEqual(P('.').with_suffix('.gz'), P('..gz'))
- self.assertEqual(P('/').with_suffix('.gz'), P('/.gz'))
+ self.assertRaises(ValueError, P('').with_suffix, '.gz')
+ self.assertRaises(ValueError, P('/').with_suffix, '.gz')
def test_with_suffix_seps(self):
P = self.cls
1
0
30 Jan '24
https://github.com/python/cpython/commit/e21754d7f8336d4647e28f355d8a3dbd5a…
commit: e21754d7f8336d4647e28f355d8a3dbd5a2c7545
branch: main
author: Vinay Sajip <vinay_sajip(a)yahoo.co.uk>
committer: vsajip <vinay_sajip(a)yahoo.co.uk>
date: 2024-01-30T12:34:18Z
summary:
gh-114706: Allow QueueListener.stop() to be called more than once. (GH-114748)
files:
M Lib/logging/handlers.py
M Lib/test/test_logging.py
diff --git a/Lib/logging/handlers.py b/Lib/logging/handlers.py
index 9840b7b0aeba88..e7f1322e4ba3d9 100644
--- a/Lib/logging/handlers.py
+++ b/Lib/logging/handlers.py
@@ -1586,6 +1586,7 @@ def stop(self):
Note that if you don't call this before your application exits, there
may be some records still left on the queue, which won't be processed.
"""
- self.enqueue_sentinel()
- self._thread.join()
- self._thread = None
+ if self._thread: # see gh-114706 - allow calling this more than once
+ self.enqueue_sentinel()
+ self._thread.join()
+ self._thread = None
diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py
index 908e242b85f5e7..888523227c2ac4 100644
--- a/Lib/test/test_logging.py
+++ b/Lib/test/test_logging.py
@@ -4089,6 +4089,7 @@ def test_queue_listener(self):
self.que_logger.critical(self.next_message())
finally:
listener.stop()
+ listener.stop() # gh-114706 - ensure no crash if called again
self.assertTrue(handler.matches(levelno=logging.WARNING, message='1'))
self.assertTrue(handler.matches(levelno=logging.ERROR, message='2'))
self.assertTrue(handler.matches(levelno=logging.CRITICAL, message='3'))
1
0
gh-113732: Fix support of QUOTE_NOTNULL and QUOTE_STRINGS in csv.reader (GH-113738)
by serhiy-storchaka 30 Jan '24
by serhiy-storchaka 30 Jan '24
30 Jan '24
https://github.com/python/cpython/commit/ea30a28c3e89b69a214c536e6140266024…
commit: ea30a28c3e89b69a214c536e61402660242c0f2a
branch: main
author: Serhiy Storchaka <storchaka(a)gmail.com>
committer: serhiy-storchaka <storchaka(a)gmail.com>
date: 2024-01-30T14:21:12+02:00
summary:
gh-113732: Fix support of QUOTE_NOTNULL and QUOTE_STRINGS in csv.reader (GH-113738)
files:
A Misc/NEWS.d/next/Library/2024-01-05-16-27-34.gh-issue-113732.fgDRXA.rst
M Doc/whatsnew/3.12.rst
M Lib/test/test_csv.py
M Modules/_csv.c
diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst
index 77b12f9284ba0d..100312a5940b79 100644
--- a/Doc/whatsnew/3.12.rst
+++ b/Doc/whatsnew/3.12.rst
@@ -690,7 +690,7 @@ csv
* Add :const:`csv.QUOTE_NOTNULL` and :const:`csv.QUOTE_STRINGS` flags to
provide finer grained control of ``None`` and empty strings by
- :class:`csv.writer` objects.
+ :class:`~csv.reader` and :class:`~csv.writer` objects.
dis
---
diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py
index 69fef5945ae66f..21a4cb586ff665 100644
--- a/Lib/test/test_csv.py
+++ b/Lib/test/test_csv.py
@@ -392,10 +392,26 @@ def test_read_quoting(self):
# will this fail where locale uses comma for decimals?
self._read_test([',3,"5",7.3, 9'], [['', 3, '5', 7.3, 9]],
quoting=csv.QUOTE_NONNUMERIC)
+ self._read_test([',3,"5",7.3, 9'], [[None, '3', '5', '7.3', ' 9']],
+ quoting=csv.QUOTE_NOTNULL)
+ self._read_test([',3,"5",7.3, 9'], [[None, 3, '5', 7.3, 9]],
+ quoting=csv.QUOTE_STRINGS)
+
+ self._read_test([',,"",'], [['', '', '', '']])
+ self._read_test([',,"",'], [['', '', '', '']],
+ quoting=csv.QUOTE_NONNUMERIC)
+ self._read_test([',,"",'], [[None, None, '', None]],
+ quoting=csv.QUOTE_NOTNULL)
+ self._read_test([',,"",'], [[None, None, '', None]],
+ quoting=csv.QUOTE_STRINGS)
+
self._read_test(['"a\nb", 7'], [['a\nb', ' 7']])
self.assertRaises(ValueError, self._read_test,
['abc,3'], [[]],
quoting=csv.QUOTE_NONNUMERIC)
+ self.assertRaises(ValueError, self._read_test,
+ ['abc,3'], [[]],
+ quoting=csv.QUOTE_STRINGS)
self._read_test(['1,@,3,@,5'], [['1', ',3,', '5']], quotechar='@')
self._read_test(['1,\0,3,\0,5'], [['1', ',3,', '5']], quotechar='\0')
@@ -403,6 +419,15 @@ def test_read_skipinitialspace(self):
self._read_test(['no space, space, spaces,\ttab'],
[['no space', 'space', 'spaces', '\ttab']],
skipinitialspace=True)
+ self._read_test([' , , '],
+ [['', '', '']],
+ skipinitialspace=True)
+ self._read_test([' , , '],
+ [[None, None, None]],
+ skipinitialspace=True, quoting=csv.QUOTE_NOTNULL)
+ self._read_test([' , , '],
+ [[None, None, None]],
+ skipinitialspace=True, quoting=csv.QUOTE_STRINGS)
def test_read_bigfield(self):
# This exercises the buffer realloc functionality and field size
diff --git a/Misc/NEWS.d/next/Library/2024-01-05-16-27-34.gh-issue-113732.fgDRXA.rst b/Misc/NEWS.d/next/Library/2024-01-05-16-27-34.gh-issue-113732.fgDRXA.rst
new file mode 100644
index 00000000000000..7582603dcf95f5
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-01-05-16-27-34.gh-issue-113732.fgDRXA.rst
@@ -0,0 +1,2 @@
+Fix support of :data:`~csv.QUOTE_NOTNULL` and :data:`~csv.QUOTE_STRINGS` in
+:func:`csv.reader`.
diff --git a/Modules/_csv.c b/Modules/_csv.c
index 929c21584ac2ef..3aa648b8e9cec4 100644
--- a/Modules/_csv.c
+++ b/Modules/_csv.c
@@ -131,7 +131,7 @@ typedef struct {
Py_UCS4 *field; /* temporary buffer */
Py_ssize_t field_size; /* size of allocated buffer */
Py_ssize_t field_len; /* length of current field */
- int numeric_field; /* treat field as numeric */
+ bool unquoted_field; /* true if no quotes around the current field */
unsigned long line_num; /* Source-file line number */
} ReaderObj;
@@ -644,22 +644,33 @@ _call_dialect(_csvstate *module_state, PyObject *dialect_inst, PyObject *kwargs)
static int
parse_save_field(ReaderObj *self)
{
+ int quoting = self->dialect->quoting;
PyObject *field;
- field = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND,
- (void *) self->field, self->field_len);
- if (field == NULL)
- return -1;
- self->field_len = 0;
- if (self->numeric_field) {
- PyObject *tmp;
-
- self->numeric_field = 0;
- tmp = PyNumber_Float(field);
- Py_DECREF(field);
- if (tmp == NULL)
+ if (self->unquoted_field &&
+ self->field_len == 0 &&
+ (quoting == QUOTE_NOTNULL || quoting == QUOTE_STRINGS))
+ {
+ field = Py_NewRef(Py_None);
+ }
+ else {
+ field = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND,
+ (void *) self->field, self->field_len);
+ if (field == NULL) {
return -1;
- field = tmp;
+ }
+ if (self->unquoted_field &&
+ self->field_len != 0 &&
+ (quoting == QUOTE_NONNUMERIC || quoting == QUOTE_STRINGS))
+ {
+ PyObject *tmp = PyNumber_Float(field);
+ Py_DECREF(field);
+ if (tmp == NULL) {
+ return -1;
+ }
+ field = tmp;
+ }
+ self->field_len = 0;
}
if (PyList_Append(self->fields, field) < 0) {
Py_DECREF(field);
@@ -721,6 +732,7 @@ parse_process_char(ReaderObj *self, _csvstate *module_state, Py_UCS4 c)
/* fallthru */
case START_FIELD:
/* expecting field */
+ self->unquoted_field = true;
if (c == '\n' || c == '\r' || c == EOL) {
/* save empty field - return [fields] */
if (parse_save_field(self) < 0)
@@ -730,10 +742,12 @@ parse_process_char(ReaderObj *self, _csvstate *module_state, Py_UCS4 c)
else if (c == dialect->quotechar &&
dialect->quoting != QUOTE_NONE) {
/* start quoted field */
+ self->unquoted_field = false;
self->state = IN_QUOTED_FIELD;
}
else if (c == dialect->escapechar) {
/* possible escaped character */
+ self->unquoted_field = false;
self->state = ESCAPED_CHAR;
}
else if (c == ' ' && dialect->skipinitialspace)
@@ -746,8 +760,6 @@ parse_process_char(ReaderObj *self, _csvstate *module_state, Py_UCS4 c)
}
else {
/* begin new unquoted field */
- if (dialect->quoting == QUOTE_NONNUMERIC)
- self->numeric_field = 1;
if (parse_add_char(self, module_state, c) < 0)
return -1;
self->state = IN_FIELD;
@@ -892,7 +904,7 @@ parse_reset(ReaderObj *self)
return -1;
self->field_len = 0;
self->state = START_RECORD;
- self->numeric_field = 0;
+ self->unquoted_field = false;
return 0;
}
1
0
https://github.com/python/cpython/commit/58f883b91bd8dd4cac38b58a0263973631…
commit: 58f883b91bd8dd4cac38b58a026397363104a129
branch: main
author: Victor Stinner <vstinner(a)python.org>
committer: vstinner <vstinner(a)python.org>
date: 2024-01-30T11:47:58+01:00
summary:
gh-103323: Remove current_fast_get() unused parameter (#114593)
The current_fast_get() static inline function doesn't use its
'runtime' parameter, so just remove it.
files:
M Python/pystate.c
diff --git a/Python/pystate.c b/Python/pystate.c
index 8e097c848cf4a1..430121a6a35d7f 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -67,7 +67,7 @@ _Py_thread_local PyThreadState *_Py_tss_tstate = NULL;
#endif
static inline PyThreadState *
-current_fast_get(_PyRuntimeState *Py_UNUSED(runtime))
+current_fast_get(void)
{
#ifdef HAVE_THREAD_LOCAL
return _Py_tss_tstate;
@@ -101,14 +101,14 @@ current_fast_clear(_PyRuntimeState *Py_UNUSED(runtime))
}
#define tstate_verify_not_active(tstate) \
- if (tstate == current_fast_get((tstate)->interp->runtime)) { \
+ if (tstate == current_fast_get()) { \
_Py_FatalErrorFormat(__func__, "tstate %p is still current", tstate); \
}
PyThreadState *
_PyThreadState_GetCurrent(void)
{
- return current_fast_get(&_PyRuntime);
+ return current_fast_get();
}
@@ -360,10 +360,9 @@ holds_gil(PyThreadState *tstate)
// XXX Fall back to tstate->interp->runtime->ceval.gil.last_holder
// (and tstate->interp->runtime->ceval.gil.locked).
assert(tstate != NULL);
- _PyRuntimeState *runtime = tstate->interp->runtime;
/* Must be the tstate for this thread */
- assert(tstate == gilstate_tss_get(runtime));
- return tstate == current_fast_get(runtime);
+ assert(tstate == gilstate_tss_get(tstate->interp->runtime));
+ return tstate == current_fast_get();
}
@@ -723,7 +722,7 @@ PyInterpreterState *
PyInterpreterState_New(void)
{
// tstate can be NULL
- PyThreadState *tstate = current_fast_get(&_PyRuntime);
+ PyThreadState *tstate = current_fast_get();
PyInterpreterState *interp;
PyStatus status = _PyInterpreterState_New(tstate, &interp);
@@ -882,7 +881,7 @@ PyInterpreterState_Clear(PyInterpreterState *interp)
// Use the current Python thread state to call audit hooks and to collect
// garbage. It can be different than the current Python thread state
// of 'interp'.
- PyThreadState *current_tstate = current_fast_get(interp->runtime);
+ PyThreadState *current_tstate = current_fast_get();
_PyImport_ClearCore(interp);
interpreter_clear(interp, current_tstate);
}
@@ -908,7 +907,7 @@ PyInterpreterState_Delete(PyInterpreterState *interp)
// XXX Clearing the "current" thread state should happen before
// we start finalizing the interpreter (or the current thread state).
- PyThreadState *tcur = current_fast_get(runtime);
+ PyThreadState *tcur = current_fast_get();
if (tcur != NULL && interp == tcur->interp) {
/* Unset current thread. After this, many C API calls become crashy. */
_PyThreadState_Detach(tcur);
@@ -1010,7 +1009,7 @@ _PyInterpreterState_SetRunningMain(PyInterpreterState *interp)
if (_PyInterpreterState_FailIfRunningMain(interp) < 0) {
return -1;
}
- PyThreadState *tstate = current_fast_get(&_PyRuntime);
+ PyThreadState *tstate = current_fast_get();
_Py_EnsureTstateNotNULL(tstate);
if (tstate->interp != interp) {
PyErr_SetString(PyExc_RuntimeError,
@@ -1025,7 +1024,7 @@ void
_PyInterpreterState_SetNotRunningMain(PyInterpreterState *interp)
{
PyThreadState *tstate = interp->threads.main;
- assert(tstate == current_fast_get(&_PyRuntime));
+ assert(tstate == current_fast_get());
if (tstate->on_delete != NULL) {
// The threading module was imported for the first time in this
@@ -1178,7 +1177,7 @@ PyInterpreterState_GetDict(PyInterpreterState *interp)
PyInterpreterState*
PyInterpreterState_Get(void)
{
- PyThreadState *tstate = current_fast_get(&_PyRuntime);
+ PyThreadState *tstate = current_fast_get();
_Py_EnsureTstateNotNULL(tstate);
PyInterpreterState *interp = tstate->interp;
if (interp == NULL) {
@@ -1474,7 +1473,7 @@ void
PyThreadState_Clear(PyThreadState *tstate)
{
assert(tstate->_status.initialized && !tstate->_status.cleared);
- assert(current_fast_get(&_PyRuntime)->interp == tstate->interp);
+ assert(current_fast_get()->interp == tstate->interp);
// XXX assert(!tstate->_status.bound || tstate->_status.unbound);
tstate->_status.finalizing = 1; // just in case
@@ -1656,7 +1655,7 @@ _PyThreadState_DeleteCurrent(PyThreadState *tstate)
void
PyThreadState_DeleteCurrent(void)
{
- PyThreadState *tstate = current_fast_get(&_PyRuntime);
+ PyThreadState *tstate = current_fast_get();
_PyThreadState_DeleteCurrent(tstate);
}
@@ -1732,7 +1731,7 @@ _PyThreadState_GetDict(PyThreadState *tstate)
PyObject *
PyThreadState_GetDict(void)
{
- PyThreadState *tstate = current_fast_get(&_PyRuntime);
+ PyThreadState *tstate = current_fast_get();
if (tstate == NULL) {
return NULL;
}
@@ -1853,7 +1852,7 @@ _PyThreadState_Attach(PyThreadState *tstate)
#endif
_Py_EnsureTstateNotNULL(tstate);
- if (current_fast_get(&_PyRuntime) != NULL) {
+ if (current_fast_get() != NULL) {
Py_FatalError("non-NULL old thread state");
}
@@ -1883,7 +1882,7 @@ detach_thread(PyThreadState *tstate, int detached_state)
{
// XXX assert(tstate_is_alive(tstate) && tstate_is_bound(tstate));
assert(tstate->state == _Py_THREAD_ATTACHED);
- assert(tstate == current_fast_get(&_PyRuntime));
+ assert(tstate == current_fast_get());
if (tstate->critical_section != 0) {
_PyCriticalSection_SuspendAll(tstate);
}
@@ -2168,14 +2167,14 @@ PyThreadState_SetAsyncExc(unsigned long id, PyObject *exc)
PyThreadState *
PyThreadState_GetUnchecked(void)
{
- return current_fast_get(&_PyRuntime);
+ return current_fast_get();
}
PyThreadState *
PyThreadState_Get(void)
{
- PyThreadState *tstate = current_fast_get(&_PyRuntime);
+ PyThreadState *tstate = current_fast_get();
_Py_EnsureTstateNotNULL(tstate);
return tstate;
}
@@ -2183,7 +2182,7 @@ PyThreadState_Get(void)
PyThreadState *
_PyThreadState_Swap(_PyRuntimeState *runtime, PyThreadState *newts)
{
- PyThreadState *oldts = current_fast_get(runtime);
+ PyThreadState *oldts = current_fast_get();
if (oldts != NULL) {
_PyThreadState_Detach(oldts);
}
@@ -2278,7 +2277,7 @@ PyObject *
_PyThread_CurrentFrames(void)
{
_PyRuntimeState *runtime = &_PyRuntime;
- PyThreadState *tstate = current_fast_get(runtime);
+ PyThreadState *tstate = current_fast_get();
if (_PySys_Audit(tstate, "sys._current_frames", NULL) < 0) {
return NULL;
}
@@ -2339,7 +2338,7 @@ PyObject *
_PyThread_CurrentExceptions(void)
{
_PyRuntimeState *runtime = &_PyRuntime;
- PyThreadState *tstate = current_fast_get(runtime);
+ PyThreadState *tstate = current_fast_get();
_Py_EnsureTstateNotNULL(tstate);
@@ -2481,7 +2480,7 @@ PyGILState_Check(void)
return 1;
}
- PyThreadState *tstate = current_fast_get(runtime);
+ PyThreadState *tstate = current_fast_get();
if (tstate == NULL) {
return 0;
}
@@ -2579,7 +2578,7 @@ PyGILState_Release(PyGILState_STATE oldstate)
* races; see bugs 225673 and 1061968 (that nasty bug has a
* habit of coming back).
*/
- assert(current_fast_get(runtime) == tstate);
+ assert(current_fast_get() == tstate);
_PyThreadState_DeleteCurrent(tstate);
}
/* Release the lock if necessary */
@@ -2645,9 +2644,8 @@ _PyInterpreterState_GetConfigCopy(PyConfig *config)
const PyConfig*
_Py_GetConfig(void)
{
- _PyRuntimeState *runtime = &_PyRuntime;
assert(PyGILState_Check());
- PyThreadState *tstate = current_fast_get(runtime);
+ PyThreadState *tstate = current_fast_get();
_Py_EnsureTstateNotNULL(tstate);
return _PyInterpreterState_GetConfig(tstate->interp);
}
1
0
29 Jan '24
https://github.com/python/cpython/commit/963904335e579bfe39101adf3fd6a0cf70…
commit: 963904335e579bfe39101adf3fd6a0cf705975ff
branch: main
author: Sviatoslav Sydorenko (Святослав Сидоренко) <wk(a)sydorenko.org.ua>
committer: pradyunsg <pradyunsg(a)gmail.com>
date: 2024-01-30T01:25:31Z
summary:
GH-80789: Get rid of the ``ensurepip`` infra for many wheels (#109245)
Co-authored-by: vstinner(a)python.org
Co-authored-by: Pradyun Gedam <pradyunsg(a)gmail.com>
Co-authored-by: Adam Turner <9087854+aa-turner(a)users.noreply.github.com>
files:
M Lib/ensurepip/__init__.py
M Lib/test/test_ensurepip.py
M Tools/build/verify_ensurepip_wheels.py
diff --git a/Lib/ensurepip/__init__.py b/Lib/ensurepip/__init__.py
index a09bf3201e1fb7..80ee125cfd4ed3 100644
--- a/Lib/ensurepip/__init__.py
+++ b/Lib/ensurepip/__init__.py
@@ -1,78 +1,64 @@
-import collections
import os
-import os.path
import subprocess
import sys
import sysconfig
import tempfile
+from contextlib import nullcontext
from importlib import resources
+from pathlib import Path
+from shutil import copy2
__all__ = ["version", "bootstrap"]
-_PACKAGE_NAMES = ('pip',)
_PIP_VERSION = "23.3.2"
-_PROJECTS = [
- ("pip", _PIP_VERSION, "py3"),
-]
-
-# Packages bundled in ensurepip._bundled have wheel_name set.
-# Packages from WHEEL_PKG_DIR have wheel_path set.
-_Package = collections.namedtuple('Package',
- ('version', 'wheel_name', 'wheel_path'))
# Directory of system wheel packages. Some Linux distribution packaging
# policies recommend against bundling dependencies. For example, Fedora
# installs wheel packages in the /usr/share/python-wheels/ directory and don't
# install the ensurepip._bundled package.
-_WHEEL_PKG_DIR = sysconfig.get_config_var('WHEEL_PKG_DIR')
+if (_pkg_dir := sysconfig.get_config_var('WHEEL_PKG_DIR')) is not None:
+ _WHEEL_PKG_DIR = Path(_pkg_dir).resolve()
+else:
+ _WHEEL_PKG_DIR = None
+
+def _find_wheel_pkg_dir_pip():
+ if _WHEEL_PKG_DIR is None:
+ # NOTE: The compile-time `WHEEL_PKG_DIR` is unset so there is no place
+ # NOTE: for looking up the wheels.
+ return None
-def _find_packages(path):
- packages = {}
+ dist_matching_wheels = _WHEEL_PKG_DIR.glob('pip-*.whl')
try:
- filenames = os.listdir(path)
- except OSError:
- # Ignore: path doesn't exist or permission error
- filenames = ()
- # Make the code deterministic if a directory contains multiple wheel files
- # of the same package, but don't attempt to implement correct version
- # comparison since this case should not happen.
- filenames = sorted(filenames)
- for filename in filenames:
- # filename is like 'pip-21.2.4-py3-none-any.whl'
- if not filename.endswith(".whl"):
- continue
- for name in _PACKAGE_NAMES:
- prefix = name + '-'
- if filename.startswith(prefix):
- break
- else:
- continue
-
- # Extract '21.2.4' from 'pip-21.2.4-py3-none-any.whl'
- version = filename.removeprefix(prefix).partition('-')[0]
- wheel_path = os.path.join(path, filename)
- packages[name] = _Package(version, None, wheel_path)
- return packages
-
-
-def _get_packages():
- global _PACKAGES, _WHEEL_PKG_DIR
- if _PACKAGES is not None:
- return _PACKAGES
-
- packages = {}
- for name, version, py_tag in _PROJECTS:
- wheel_name = f"{name}-{version}-{py_tag}-none-any.whl"
- packages[name] = _Package(version, wheel_name, None)
- if _WHEEL_PKG_DIR:
- dir_packages = _find_packages(_WHEEL_PKG_DIR)
- # only used the wheel package directory if all packages are found there
- if all(name in dir_packages for name in _PACKAGE_NAMES):
- packages = dir_packages
- _PACKAGES = packages
- return packages
-_PACKAGES = None
+ last_matching_dist_wheel = sorted(dist_matching_wheels)[-1]
+ except IndexError:
+ # NOTE: `WHEEL_PKG_DIR` does not contain any wheel files for `pip`.
+ return None
+
+ return nullcontext(last_matching_dist_wheel)
+
+
+def _get_pip_whl_path_ctx():
+ # Prefer pip from the wheel package directory, if present.
+ if (alternative_pip_wheel_path := _find_wheel_pkg_dir_pip()) is not None:
+ return alternative_pip_wheel_path
+
+ return resources.as_file(
+ resources.files('ensurepip')
+ / '_bundled'
+ / f'pip-{_PIP_VERSION}-py3-none-any.whl'
+ )
+
+
+def _get_pip_version():
+ with _get_pip_whl_path_ctx() as bundled_wheel_path:
+ wheel_name = bundled_wheel_path.name
+ return (
+ # Extract '21.2.4' from 'pip-21.2.4-py3-none-any.whl'
+ wheel_name.
+ removeprefix('pip-').
+ partition('-')[0]
+ )
def _run_pip(args, additional_paths=None):
@@ -105,7 +91,7 @@ def version():
"""
Returns a string specifying the bundled version of pip.
"""
- return _get_packages()['pip'].version
+ return _get_pip_version()
def _disable_pip_configuration_settings():
@@ -167,24 +153,10 @@ def _bootstrap(*, root=None, upgrade=False, user=False,
with tempfile.TemporaryDirectory() as tmpdir:
# Put our bundled wheels into a temporary directory and construct the
# additional paths that need added to sys.path
- additional_paths = []
- for name, package in _get_packages().items():
- if package.wheel_name:
- # Use bundled wheel package
- wheel_name = package.wheel_name
- wheel_path = resources.files("ensurepip") / "_bundled" / wheel_name
- whl = wheel_path.read_bytes()
- else:
- # Use the wheel package directory
- with open(package.wheel_path, "rb") as fp:
- whl = fp.read()
- wheel_name = os.path.basename(package.wheel_path)
-
- filename = os.path.join(tmpdir, wheel_name)
- with open(filename, "wb") as fp:
- fp.write(whl)
-
- additional_paths.append(filename)
+ tmpdir_path = Path(tmpdir)
+ with _get_pip_whl_path_ctx() as bundled_wheel_path:
+ tmp_wheel_path = tmpdir_path / bundled_wheel_path.name
+ copy2(bundled_wheel_path, tmp_wheel_path)
# Construct the arguments to be passed to the pip command
args = ["install", "--no-cache-dir", "--no-index", "--find-links", tmpdir]
@@ -197,7 +169,8 @@ def _bootstrap(*, root=None, upgrade=False, user=False,
if verbosity:
args += ["-" + "v" * verbosity]
- return _run_pip([*args, *_PACKAGE_NAMES], additional_paths)
+ return _run_pip([*args, "pip"], [os.fsdecode(tmp_wheel_path)])
+
def _uninstall_helper(*, verbosity=0):
"""Helper to support a clean default uninstall process on Windows
@@ -227,7 +200,7 @@ def _uninstall_helper(*, verbosity=0):
if verbosity:
args += ["-" + "v" * verbosity]
- return _run_pip([*args, *reversed(_PACKAGE_NAMES)])
+ return _run_pip([*args, "pip"])
def _main(argv=None):
diff --git a/Lib/test/test_ensurepip.py b/Lib/test/test_ensurepip.py
index 69ab2a4feaa938..a4b36a90d8815e 100644
--- a/Lib/test/test_ensurepip.py
+++ b/Lib/test/test_ensurepip.py
@@ -6,6 +6,8 @@
import test.support
import unittest
import unittest.mock
+from importlib.resources.abc import Traversable
+from pathlib import Path
import ensurepip
import ensurepip._uninstall
@@ -20,41 +22,35 @@ def test_version(self):
# Test version()
with tempfile.TemporaryDirectory() as tmpdir:
self.touch(tmpdir, "pip-1.2.3b1-py2.py3-none-any.whl")
- with (unittest.mock.patch.object(ensurepip, '_PACKAGES', None),
- unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', tmpdir)):
+ with unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', Path(tmpdir)):
self.assertEqual(ensurepip.version(), '1.2.3b1')
- def test_get_packages_no_dir(self):
- # Test _get_packages() without a wheel package directory
- with (unittest.mock.patch.object(ensurepip, '_PACKAGES', None),
- unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', None)):
- packages = ensurepip._get_packages()
-
- # when bundled wheel packages are used, we get _PIP_VERSION
+ def test_version_no_dir(self):
+ # Test version() without a wheel package directory
+ with unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', None):
+ # when the bundled pip wheel is used, we get _PIP_VERSION
self.assertEqual(ensurepip._PIP_VERSION, ensurepip.version())
- # use bundled wheel packages
- self.assertIsNotNone(packages['pip'].wheel_name)
+ def test_selected_wheel_path_no_dir(self):
+ pip_filename = f'pip-{ensurepip._PIP_VERSION}-py3-none-any.whl'
+ with unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', None):
+ with ensurepip._get_pip_whl_path_ctx() as bundled_wheel_path:
+ self.assertEqual(pip_filename, bundled_wheel_path.name)
- def test_get_packages_with_dir(self):
- # Test _get_packages() with a wheel package directory
+ def test_selected_wheel_path_with_dir(self):
+ # Test _get_pip_whl_path_ctx() with a wheel package directory
pip_filename = "pip-20.2.2-py2.py3-none-any.whl"
with tempfile.TemporaryDirectory() as tmpdir:
self.touch(tmpdir, pip_filename)
- # not used, make sure that it's ignored
+ # not used, make sure that they're ignored
+ self.touch(tmpdir, "pip-1.2.3-py2.py3-none-any.whl")
self.touch(tmpdir, "wheel-0.34.2-py2.py3-none-any.whl")
+ self.touch(tmpdir, "pip-script.py")
- with (unittest.mock.patch.object(ensurepip, '_PACKAGES', None),
- unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', tmpdir)):
- packages = ensurepip._get_packages()
-
- self.assertEqual(packages['pip'].version, '20.2.2')
- self.assertEqual(packages['pip'].wheel_path,
- os.path.join(tmpdir, pip_filename))
-
- # wheel package is ignored
- self.assertEqual(sorted(packages), ['pip'])
+ with unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', Path(tmpdir)):
+ with ensurepip._get_pip_whl_path_ctx() as bundled_wheel_path:
+ self.assertEqual(pip_filename, bundled_wheel_path.name)
class EnsurepipMixin:
@@ -69,7 +65,7 @@ def setUp(self):
real_devnull = os.devnull
os_patch = unittest.mock.patch("ensurepip.os")
patched_os = os_patch.start()
- # But expose os.listdir() used by _find_packages()
+ # But expose os.listdir() used by _find_wheel_pkg_dir_pip()
patched_os.listdir = os.listdir
self.addCleanup(os_patch.stop)
patched_os.devnull = real_devnull
diff --git a/Tools/build/verify_ensurepip_wheels.py b/Tools/build/verify_ensurepip_wheels.py
index 29897425da6c03..a37da2f70757e5 100755
--- a/Tools/build/verify_ensurepip_wheels.py
+++ b/Tools/build/verify_ensurepip_wheels.py
@@ -14,7 +14,6 @@
from pathlib import Path
from urllib.request import urlopen
-PACKAGE_NAMES = ("pip",)
ENSURE_PIP_ROOT = Path(__file__).parent.parent.parent / "Lib/ensurepip"
WHEEL_DIR = ENSURE_PIP_ROOT / "_bundled"
ENSURE_PIP_INIT_PY_TEXT = (ENSURE_PIP_ROOT / "__init__.py").read_text(encoding="utf-8")
@@ -97,8 +96,5 @@ def verify_wheel(package_name: str) -> bool:
if __name__ == "__main__":
- exit_status = 0
- for package_name in PACKAGE_NAMES:
- if not verify_wheel(package_name):
- exit_status = 1
+ exit_status = int(not verify_wheel("pip"))
raise SystemExit(exit_status)
1
0