Skip to content

Commit 33dda2a

Browse files
committed
Merge branch 'main' into nodeaddict
* main: (21 commits) pythongh-102192: Replace PyErr_Fetch/Restore etc by more efficient alternatives in sub interpreters module (python#102472) pythongh-95672: Fix versionadded indentation of get_pagesize in test.rst (pythongh-102455) pythongh-102416: Do not memoize incorrectly loop rules in the parser (python#102467) pythonGH-101362: Optimise PurePath(PurePath(...)) (pythonGH-101667) pythonGH-101362: Check pathlib.Path flavour compatibility at import time (pythonGH-101664) pythonGH-101362: Call join() only when >1 argument supplied to pathlib.PurePath() (python#101665) pythongh-102444: Fix minor bugs in `test_typing` highlighted by pyflakes (python#102445) pythonGH-102341: Improve the test function for pow (python#102342) Fix unused classes in a typing test (pythonGH-102437) pythongh-101979: argparse: fix a bug where parentheses in metavar argument of add_argument() were dropped (python#102318) pythongh-102356: Add thrashcan macros to filter object dealloc (python#102426) Move around example in to_bytes() to avoid confusion (python#101595) pythonGH-97546: fix flaky asyncio `test_wait_for_race_condition` test (python#102421) pythongh-96821: Add config option `--with-strict-overflow` (python#96823) pythongh-101992: update pstlib module documentation (python#102133) pythongh-63301: Set exit code when tabnanny CLI exits on error (python#7699) pythongh-101863: Fix wrong comments in EUC-KR codec (pythongh-102417) pythongh-102302 Micro-optimize `inspect.Parameter.__hash__` (python#102303) pythongh-102179: Fix `os.dup2` error reporting for negative fds (python#102180) pythongh-101892: Fix `SystemError` when a callable iterator call exhausts the iterator (python#101896) ...
2 parents 4894127 + f105fe4 commit 33dda2a

40 files changed

+355
-362
lines changed

Doc/library/pathlib.rst

+3-2
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,9 @@ we also call *flavours*:
105105
PurePosixPath('setup.py')
106106

107107
Each element of *pathsegments* can be either a string representing a
108-
path segment, an object implementing the :class:`os.PathLike` interface
109-
which returns a string, or another path object::
108+
path segment, or an object implementing the :class:`os.PathLike` interface
109+
where the :meth:`~os.PathLike.__fspath__` method returns a string,
110+
such as another path object::
110111

111112
>>> PurePath('foo', 'some/path', 'bar')
112113
PurePosixPath('foo/some/path/bar')

Doc/library/stdtypes.rst

+4-2
Original file line numberDiff line numberDiff line change
@@ -530,12 +530,14 @@ class`. In addition, it provides a few more methods:
530530
is ``False``.
531531

532532
The default values can be used to conveniently turn an integer into a
533-
single byte object. However, when using the default arguments, don't try
534-
to convert a value greater than 255 or you'll get an :exc:`OverflowError`::
533+
single byte object::
535534

536535
>>> (65).to_bytes()
537536
b'A'
538537

538+
However, when using the default arguments, don't try
539+
to convert a value greater than 255 or you'll get an :exc:`OverflowError`.
540+
539541
Equivalent to::
540542

541543
def to_bytes(n, length=1, byteorder='big', signed=False):

Doc/library/test.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,7 @@ The :mod:`test.support` module defines the following functions:
540540

541541
Get size of a page in bytes.
542542

543-
.. versionadded:: 3.12
543+
.. versionadded:: 3.12
544544

545545

546546
.. function:: setswitchinterval(interval)

Doc/using/configure.rst

+5
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,11 @@ also be used to improve performance.
326326

327327
Enable C-level code profiling with ``gprof`` (disabled by default).
328328

329+
.. cmdoption:: --with-strict-overflow
330+
331+
Add ``-fstrict-overflow`` to the C compiler flags (by default we add
332+
``-fno-strict-overflow`` instead).
333+
329334

330335
.. _debug-build:
331336

Include/internal/pycore_pymath.h

-15
Original file line numberDiff line numberDiff line change
@@ -56,21 +56,6 @@ static inline void _Py_ADJUST_ERANGE2(double x, double y)
5656
}
5757
}
5858

59-
// Return the maximum value of integral type *type*.
60-
#define _Py_IntegralTypeMax(type) \
61-
(_Py_IS_TYPE_SIGNED(type) ? (((((type)1 << (sizeof(type)*CHAR_BIT - 2)) - 1) << 1) + 1) : ~(type)0)
62-
63-
// Return the minimum value of integral type *type*.
64-
#define _Py_IntegralTypeMin(type) \
65-
(_Py_IS_TYPE_SIGNED(type) ? -_Py_IntegralTypeMax(type) - 1 : 0)
66-
67-
// Check whether *v* is in the range of integral type *type*. This is most
68-
// useful if *v* is floating-point, since demoting a floating-point *v* to an
69-
// integral type that cannot represent *v*'s integral part is undefined
70-
// behavior.
71-
#define _Py_InIntegralTypeRange(type, v) \
72-
(_Py_IntegralTypeMin(type) <= v && v <= _Py_IntegralTypeMax(type))
73-
7459

7560
//--- HAVE_PY_SET_53BIT_PRECISION macro ------------------------------------
7661
//

Lib/argparse.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -403,10 +403,18 @@ def _format_actions_usage(self, actions, groups):
403403
except ValueError:
404404
continue
405405
else:
406-
end = start + len(group._group_actions)
406+
group_action_count = len(group._group_actions)
407+
end = start + group_action_count
407408
if actions[start:end] == group._group_actions:
409+
410+
suppressed_actions_count = 0
408411
for action in group._group_actions:
409412
group_actions.add(action)
413+
if action.help is SUPPRESS:
414+
suppressed_actions_count += 1
415+
416+
exposed_actions_count = group_action_count - suppressed_actions_count
417+
410418
if not group.required:
411419
if start in inserts:
412420
inserts[start] += ' ['
@@ -416,7 +424,7 @@ def _format_actions_usage(self, actions, groups):
416424
inserts[end] += ']'
417425
else:
418426
inserts[end] = ']'
419-
else:
427+
elif exposed_actions_count > 1:
420428
if start in inserts:
421429
inserts[start] += ' ('
422430
else:
@@ -490,7 +498,6 @@ def _format_actions_usage(self, actions, groups):
490498
text = _re.sub(r'(%s) ' % open, r'\1', text)
491499
text = _re.sub(r' (%s)' % close, r'\1', text)
492500
text = _re.sub(r'%s *%s' % (open, close), r'', text)
493-
text = _re.sub(r'\(([^|]*)\)', r'\1', text)
494501
text = text.strip()
495502

496503
# return the text

Lib/inspect.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2805,7 +2805,7 @@ def __repr__(self):
28052805
return '<{} "{}">'.format(self.__class__.__name__, self)
28062806

28072807
def __hash__(self):
2808-
return hash((self.name, self.kind, self.annotation, self.default))
2808+
return hash((self._name, self._kind, self._annotation, self._default))
28092809

28102810
def __eq__(self, other):
28112811
if self is other:

Lib/pathlib.py

+26-31
Original file line numberDiff line numberDiff line change
@@ -275,9 +275,20 @@ def __reduce__(self):
275275
def _parse_parts(cls, parts):
276276
if not parts:
277277
return '', '', []
278+
elif len(parts) == 1:
279+
path = os.fspath(parts[0])
280+
else:
281+
path = cls._flavour.join(*parts)
278282
sep = cls._flavour.sep
279283
altsep = cls._flavour.altsep
280-
path = cls._flavour.join(*parts)
284+
if isinstance(path, str):
285+
# Force-cast str subclasses to str (issue #21127)
286+
path = str(path)
287+
else:
288+
raise TypeError(
289+
"argument should be a str or an os.PathLike "
290+
"object where __fspath__ returns a str, "
291+
f"not {type(path).__name__!r}")
281292
if altsep:
282293
path = path.replace(altsep, sep)
283294
drv, root, rel = cls._flavour.splitroot(path)
@@ -288,32 +299,10 @@ def _parse_parts(cls, parts):
288299
parsed = [sys.intern(x) for x in unfiltered_parsed if x and x != '.']
289300
return drv, root, parsed
290301

291-
@classmethod
292-
def _parse_args(cls, args):
293-
# This is useful when you don't want to create an instance, just
294-
# canonicalize some constructor arguments.
295-
parts = []
296-
for a in args:
297-
if isinstance(a, PurePath):
298-
parts += a._parts
299-
else:
300-
a = os.fspath(a)
301-
if isinstance(a, str):
302-
# Force-cast str subclasses to str (issue #21127)
303-
parts.append(str(a))
304-
else:
305-
raise TypeError(
306-
"argument should be a str object or an os.PathLike "
307-
"object returning str, not %r"
308-
% type(a))
309-
return cls._parse_parts(parts)
310-
311302
@classmethod
312303
def _from_parts(cls, args):
313-
# We need to call _parse_args on the instance, so as to get the
314-
# right flavour.
315304
self = object.__new__(cls)
316-
drv, root, parts = self._parse_args(args)
305+
drv, root, parts = self._parse_parts(args)
317306
self._drv = drv
318307
self._root = root
319308
self._parts = parts
@@ -572,7 +561,7 @@ def joinpath(self, *args):
572561
anchored).
573562
"""
574563
drv1, root1, parts1 = self._drv, self._root, self._parts
575-
drv2, root2, parts2 = self._parse_args(args)
564+
drv2, root2, parts2 = self._parse_parts(args)
576565
if root2:
577566
if not drv2 and drv1:
578567
return self._from_parsed_parts(drv1, root2, [drv1 + root2] + parts2[1:])
@@ -659,7 +648,7 @@ def match(self, path_pattern):
659648
return True
660649

661650
# Can't subclass os.PathLike from PurePath and keep the constructor
662-
# optimizations in PurePath._parse_args().
651+
# optimizations in PurePath.__slots__.
663652
os.PathLike.register(PurePath)
664653

665654

@@ -704,11 +693,7 @@ def __new__(cls, *args, **kwargs):
704693
warnings._deprecated("pathlib.PurePath(**kwargs)", msg, remove=(3, 14))
705694
if cls is Path:
706695
cls = WindowsPath if os.name == 'nt' else PosixPath
707-
self = cls._from_parts(args)
708-
if self._flavour is not os.path:
709-
raise NotImplementedError("cannot instantiate %r on your system"
710-
% (cls.__name__,))
711-
return self
696+
return cls._from_parts(args)
712697

713698
def _make_child_relpath(self, part):
714699
# This is an optimization used for dir walking. `part` must be
@@ -1258,9 +1243,19 @@ class PosixPath(Path, PurePosixPath):
12581243
"""
12591244
__slots__ = ()
12601245

1246+
if os.name == 'nt':
1247+
def __new__(cls, *args, **kwargs):
1248+
raise NotImplementedError(
1249+
f"cannot instantiate {cls.__name__!r} on your system")
1250+
12611251
class WindowsPath(Path, PureWindowsPath):
12621252
"""Path subclass for Windows systems.
12631253
12641254
On a Windows system, instantiating a Path should return this object.
12651255
"""
12661256
__slots__ = ()
1257+
1258+
if os.name != 'nt':
1259+
def __new__(cls, *args, **kwargs):
1260+
raise NotImplementedError(
1261+
f"cannot instantiate {cls.__name__!r} on your system")

Lib/plistlib.py

+16-7
Original file line numberDiff line numberDiff line change
@@ -21,29 +21,38 @@
2121
2222
Generate Plist example:
2323
24+
import datetime
25+
import plistlib
26+
2427
pl = dict(
2528
aString = "Doodah",
2629
aList = ["A", "B", 12, 32.1, [1, 2, 3]],
2730
aFloat = 0.1,
2831
anInt = 728,
2932
aDict = dict(
3033
anotherString = "<hello & hi there!>",
31-
aUnicodeValue = "M\xe4ssig, Ma\xdf",
34+
aThirdString = "M\xe4ssig, Ma\xdf",
3235
aTrueValue = True,
3336
aFalseValue = False,
3437
),
3538
someData = b"<binary gunk>",
3639
someMoreData = b"<lots of binary gunk>" * 10,
37-
aDate = datetime.datetime.fromtimestamp(time.mktime(time.gmtime())),
40+
aDate = datetime.datetime.now()
3841
)
39-
with open(fileName, 'wb') as fp:
40-
dump(pl, fp)
42+
print(plistlib.dumps(pl).decode())
4143
4244
Parse Plist example:
4345
44-
with open(fileName, 'rb') as fp:
45-
pl = load(fp)
46-
print(pl["aKey"])
46+
import plistlib
47+
48+
plist = b'''<plist version="1.0">
49+
<dict>
50+
<key>foo</key>
51+
<string>bar</string>
52+
</dict>
53+
</plist>'''
54+
pl = plistlib.loads(plist)
55+
print(pl["foo"])
4756
"""
4857
__all__ = [
4958
"InvalidFileException", "FMT_XML", "FMT_BINARY", "load", "dump", "loads", "dumps", "UID"

Lib/tabnanny.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def errprint(*args):
3535
sys.stderr.write(sep + str(arg))
3636
sep = " "
3737
sys.stderr.write("\n")
38+
sys.exit(1)
3839

3940
def main():
4041
import getopt
@@ -44,15 +45,13 @@ def main():
4445
opts, args = getopt.getopt(sys.argv[1:], "qv")
4546
except getopt.error as msg:
4647
errprint(msg)
47-
return
4848
for o, a in opts:
4949
if o == '-q':
5050
filename_only = filename_only + 1
5151
if o == '-v':
5252
verbose = verbose + 1
5353
if not args:
5454
errprint("Usage:", sys.argv[0], "[-v] file_or_directory ...")
55-
return
5655
for arg in args:
5756
check(arg)
5857

Lib/test/test_argparse.py

+22
Original file line numberDiff line numberDiff line change
@@ -3764,6 +3764,28 @@ class TestHelpUsage(HelpTestCase):
37643764
version = ''
37653765

37663766

3767+
class TestHelpUsageWithParentheses(HelpTestCase):
3768+
parser_signature = Sig(prog='PROG')
3769+
argument_signatures = [
3770+
Sig('positional', metavar='(example) positional'),
3771+
Sig('-p', '--optional', metavar='{1 (option A), 2 (option B)}'),
3772+
]
3773+
3774+
usage = '''\
3775+
usage: PROG [-h] [-p {1 (option A), 2 (option B)}] (example) positional
3776+
'''
3777+
help = usage + '''\
3778+
3779+
positional arguments:
3780+
(example) positional
3781+
3782+
options:
3783+
-h, --help show this help message and exit
3784+
-p {1 (option A), 2 (option B)}, --optional {1 (option A), 2 (option B)}
3785+
'''
3786+
version = ''
3787+
3788+
37673789
class TestHelpOnlyUserGroups(HelpTestCase):
37683790
"""Test basic usage messages"""
37693791

Lib/test/test_asyncio/test_waitfor.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ async def test_wait_for_race_condition(self):
159159

160160
fut = loop.create_future()
161161
task = asyncio.wait_for(fut, timeout=0.2)
162-
loop.call_later(0.1, fut.set_result, "ok")
162+
loop.call_soon(fut.set_result, "ok")
163163
res = await task
164164
self.assertEqual(res, "ok")
165165

Lib/test/test_builtin.py

+10
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,16 @@ def test_filter_pickle(self):
926926
f2 = filter(filter_char, "abcdeabcde")
927927
self.check_iter_pickle(f1, list(f2), proto)
928928

929+
def test_filter_dealloc(self):
930+
# Tests recursive deallocation of nested filter objects using the
931+
# thrashcan mechanism. See gh-102356 for more details.
932+
max_iters = 1000000
933+
i = filter(bool, range(max_iters))
934+
for _ in range(max_iters):
935+
i = filter(bool, i)
936+
del i
937+
gc.collect()
938+
929939
def test_getattr(self):
930940
self.assertTrue(getattr(sys, 'stdout') is sys.stdout)
931941
self.assertRaises(TypeError, getattr)

Lib/test/test_iter.py

+25
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,31 @@ def spam(state=[0]):
348348
return i
349349
self.check_iterator(iter(spam, 20), list(range(10)), pickle=False)
350350

351+
def test_iter_function_concealing_reentrant_exhaustion(self):
352+
# gh-101892: Test two-argument iter() with a function that
353+
# exhausts its associated iterator but forgets to either return
354+
# a sentinel value or raise StopIteration.
355+
HAS_MORE = 1
356+
NO_MORE = 2
357+
358+
def exhaust(iterator):
359+
"""Exhaust an iterator without raising StopIteration."""
360+
list(iterator)
361+
362+
def spam():
363+
# Touching the iterator with exhaust() below will call
364+
# spam() once again so protect against recursion.
365+
if spam.is_recursive_call:
366+
return NO_MORE
367+
spam.is_recursive_call = True
368+
exhaust(spam.iterator)
369+
return HAS_MORE
370+
371+
spam.is_recursive_call = False
372+
spam.iterator = iter(spam, NO_MORE)
373+
with self.assertRaises(StopIteration):
374+
next(spam.iterator)
375+
351376
# Test exception propagation through function iterator
352377
def test_exception_function(self):
353378
def spam(state=[0]):

0 commit comments

Comments
 (0)