From f5c05e015c178975f24b77e5a8975a22d694e019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavol=20Babin=C4=8D=C3=A1k=E2=80=8F?= Date: Wed, 13 Dec 2023 21:23:13 +0100 Subject: [PATCH 01/62] bpo-40648: Test modes that file can get with chmod() on Windows (GH-20130) Order of tests matter second part makes testing file writable and possible to remove again. --- Lib/test/test_stat.py | 7 +++++++ .../next/Tests/2020-05-16-18-00-21.bpo-40648.p2uPqy.rst | 1 + 2 files changed, 8 insertions(+) create mode 100644 Misc/NEWS.d/next/Tests/2020-05-16-18-00-21.bpo-40648.p2uPqy.rst diff --git a/Lib/test/test_stat.py b/Lib/test/test_stat.py index 0eced2fcf98376..a0d0f61e5a192c 100644 --- a/Lib/test/test_stat.py +++ b/Lib/test/test_stat.py @@ -148,12 +148,19 @@ def test_mode(self): self.assertEqual(modestr, '-r--r--r--') self.assertEqual(self.statmod.S_IMODE(st_mode), 0o444) else: + os.chmod(TESTFN, 0o500) + st_mode, modestr = self.get_mode() + self.assertEqual(modestr[:3], '-r-') + self.assertS_IS("REG", st_mode) + self.assertEqual(self.statmod.S_IMODE(st_mode), 0o444) + os.chmod(TESTFN, 0o700) st_mode, modestr = self.get_mode() self.assertEqual(modestr[:3], '-rw') self.assertS_IS("REG", st_mode) self.assertEqual(self.statmod.S_IFMT(st_mode), self.statmod.S_IFREG) + self.assertEqual(self.statmod.S_IMODE(st_mode), 0o666) @os_helper.skip_unless_working_chmod def test_directory(self): diff --git a/Misc/NEWS.d/next/Tests/2020-05-16-18-00-21.bpo-40648.p2uPqy.rst b/Misc/NEWS.d/next/Tests/2020-05-16-18-00-21.bpo-40648.p2uPqy.rst new file mode 100644 index 00000000000000..8fbe42d263feb9 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2020-05-16-18-00-21.bpo-40648.p2uPqy.rst @@ -0,0 +1 @@ +Test modes that file can get with chmod() on Windows. From 41c18aacc7a6082fbd9b08697c4d6180a9b62265 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 13 Dec 2023 13:49:29 -0800 Subject: [PATCH 02/62] Move optimizer/executor tests to new file test_capi/test_opt.py (#113072) --- Lib/test/test_capi/test_misc.py | 535 ------------------------------- Lib/test/test_capi/test_opt.py | 544 ++++++++++++++++++++++++++++++++ 2 files changed, 544 insertions(+), 535 deletions(-) create mode 100644 Lib/test/test_capi/test_opt.py diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 776ee913a02216..123813b949fb7d 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -7,7 +7,6 @@ import importlib.machinery import importlib.util import json -import opcode import os import pickle import queue @@ -2483,540 +2482,6 @@ def func(): self.do_test(func, names) -@contextlib.contextmanager -def temporary_optimizer(opt): - old_opt = _testinternalcapi.get_optimizer() - _testinternalcapi.set_optimizer(opt) - try: - yield - finally: - _testinternalcapi.set_optimizer(old_opt) - - -@contextlib.contextmanager -def clear_executors(func): - # Clear executors in func before and after running a block - func.__code__ = func.__code__.replace() - try: - yield - finally: - func.__code__ = func.__code__.replace() - - -class TestOptimizerAPI(unittest.TestCase): - - def test_get_counter_optimizer_dealloc(self): - # See gh-108727 - def f(): - _testinternalcapi.get_counter_optimizer() - - f() - - def test_get_set_optimizer(self): - old = _testinternalcapi.get_optimizer() - opt = _testinternalcapi.get_counter_optimizer() - try: - _testinternalcapi.set_optimizer(opt) - self.assertEqual(_testinternalcapi.get_optimizer(), opt) - _testinternalcapi.set_optimizer(None) - self.assertEqual(_testinternalcapi.get_optimizer(), None) - finally: - _testinternalcapi.set_optimizer(old) - - - def test_counter_optimizer(self): - # Generate a new function at each call - ns = {} - exec(textwrap.dedent(""" - def loop(): - for _ in range(1000): - pass - """), ns, ns) - loop = ns['loop'] - - for repeat in range(5): - opt = _testinternalcapi.get_counter_optimizer() - with temporary_optimizer(opt): - self.assertEqual(opt.get_count(), 0) - with clear_executors(loop): - loop() - self.assertEqual(opt.get_count(), 1000) - - def test_long_loop(self): - "Check that we aren't confused by EXTENDED_ARG" - - # Generate a new function at each call - ns = {} - exec(textwrap.dedent(""" - def nop(): - pass - - def long_loop(): - for _ in range(10): - nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); - nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); - nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); - nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); - nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); - nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); - nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); - """), ns, ns) - long_loop = ns['long_loop'] - - opt = _testinternalcapi.get_counter_optimizer() - with temporary_optimizer(opt): - self.assertEqual(opt.get_count(), 0) - long_loop() - self.assertEqual(opt.get_count(), 10) - - def test_code_restore_for_ENTER_EXECUTOR(self): - def testfunc(x): - i = 0 - while i < x: - i += 1 - - opt = _testinternalcapi.get_counter_optimizer() - with temporary_optimizer(opt): - testfunc(1000) - code, replace_code = testfunc.__code__, testfunc.__code__.replace() - self.assertEqual(code, replace_code) - self.assertEqual(hash(code), hash(replace_code)) - - -def get_first_executor(func): - code = func.__code__ - co_code = code.co_code - JUMP_BACKWARD = opcode.opmap["JUMP_BACKWARD"] - for i in range(0, len(co_code), 2): - if co_code[i] == JUMP_BACKWARD: - try: - return _testinternalcapi.get_executor(code, i) - except ValueError: - pass - return None - - -class TestExecutorInvalidation(unittest.TestCase): - - def setUp(self): - self.old = _testinternalcapi.get_optimizer() - self.opt = _testinternalcapi.get_counter_optimizer() - _testinternalcapi.set_optimizer(self.opt) - - def tearDown(self): - _testinternalcapi.set_optimizer(self.old) - - def test_invalidate_object(self): - # Generate a new set of functions at each call - ns = {} - func_src = "\n".join( - f""" - def f{n}(): - for _ in range(1000): - pass - """ for n in range(5) - ) - exec(textwrap.dedent(func_src), ns, ns) - funcs = [ ns[f'f{n}'] for n in range(5)] - objects = [object() for _ in range(5)] - - for f in funcs: - f() - executors = [get_first_executor(f) for f in funcs] - # Set things up so each executor depends on the objects - # with an equal or lower index. - for i, exe in enumerate(executors): - self.assertTrue(exe.is_valid()) - for obj in objects[:i+1]: - _testinternalcapi.add_executor_dependency(exe, obj) - self.assertTrue(exe.is_valid()) - # Assert that the correct executors are invalidated - # and check that nothing crashes when we invalidate - # an executor mutliple times. - for i in (4,3,2,1,0): - _testinternalcapi.invalidate_executors(objects[i]) - for exe in executors[i:]: - self.assertFalse(exe.is_valid()) - for exe in executors[:i]: - self.assertTrue(exe.is_valid()) - - def test_uop_optimizer_invalidation(self): - # Generate a new function at each call - ns = {} - exec(textwrap.dedent(""" - def f(): - for i in range(1000): - pass - """), ns, ns) - f = ns['f'] - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - f() - exe = get_first_executor(f) - self.assertTrue(exe.is_valid()) - _testinternalcapi.invalidate_executors(f.__code__) - self.assertFalse(exe.is_valid()) - -class TestUops(unittest.TestCase): - - def test_basic_loop(self): - def testfunc(x): - i = 0 - while i < x: - i += 1 - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - testfunc(1000) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - self.assertIn("_SET_IP", uops) - self.assertIn("LOAD_FAST", uops) - - def test_extended_arg(self): - "Check EXTENDED_ARG handling in superblock creation" - ns = {} - exec(textwrap.dedent(""" - def many_vars(): - # 260 vars, so z9 should have index 259 - a0 = a1 = a2 = a3 = a4 = a5 = a6 = a7 = a8 = a9 = 42 - b0 = b1 = b2 = b3 = b4 = b5 = b6 = b7 = b8 = b9 = 42 - c0 = c1 = c2 = c3 = c4 = c5 = c6 = c7 = c8 = c9 = 42 - d0 = d1 = d2 = d3 = d4 = d5 = d6 = d7 = d8 = d9 = 42 - e0 = e1 = e2 = e3 = e4 = e5 = e6 = e7 = e8 = e9 = 42 - f0 = f1 = f2 = f3 = f4 = f5 = f6 = f7 = f8 = f9 = 42 - g0 = g1 = g2 = g3 = g4 = g5 = g6 = g7 = g8 = g9 = 42 - h0 = h1 = h2 = h3 = h4 = h5 = h6 = h7 = h8 = h9 = 42 - i0 = i1 = i2 = i3 = i4 = i5 = i6 = i7 = i8 = i9 = 42 - j0 = j1 = j2 = j3 = j4 = j5 = j6 = j7 = j8 = j9 = 42 - k0 = k1 = k2 = k3 = k4 = k5 = k6 = k7 = k8 = k9 = 42 - l0 = l1 = l2 = l3 = l4 = l5 = l6 = l7 = l8 = l9 = 42 - m0 = m1 = m2 = m3 = m4 = m5 = m6 = m7 = m8 = m9 = 42 - n0 = n1 = n2 = n3 = n4 = n5 = n6 = n7 = n8 = n9 = 42 - o0 = o1 = o2 = o3 = o4 = o5 = o6 = o7 = o8 = o9 = 42 - p0 = p1 = p2 = p3 = p4 = p5 = p6 = p7 = p8 = p9 = 42 - q0 = q1 = q2 = q3 = q4 = q5 = q6 = q7 = q8 = q9 = 42 - r0 = r1 = r2 = r3 = r4 = r5 = r6 = r7 = r8 = r9 = 42 - s0 = s1 = s2 = s3 = s4 = s5 = s6 = s7 = s8 = s9 = 42 - t0 = t1 = t2 = t3 = t4 = t5 = t6 = t7 = t8 = t9 = 42 - u0 = u1 = u2 = u3 = u4 = u5 = u6 = u7 = u8 = u9 = 42 - v0 = v1 = v2 = v3 = v4 = v5 = v6 = v7 = v8 = v9 = 42 - w0 = w1 = w2 = w3 = w4 = w5 = w6 = w7 = w8 = w9 = 42 - x0 = x1 = x2 = x3 = x4 = x5 = x6 = x7 = x8 = x9 = 42 - y0 = y1 = y2 = y3 = y4 = y5 = y6 = y7 = y8 = y9 = 42 - z0 = z1 = z2 = z3 = z4 = z5 = z6 = z7 = z8 = z9 = 42 - while z9 > 0: - z9 = z9 - 1 - """), ns, ns) - many_vars = ns["many_vars"] - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - ex = get_first_executor(many_vars) - self.assertIsNone(ex) - many_vars() - - ex = get_first_executor(many_vars) - self.assertIsNotNone(ex) - self.assertIn(("LOAD_FAST", 259, 0), list(ex)) - - def test_unspecialized_unpack(self): - # An example of an unspecialized opcode - def testfunc(x): - i = 0 - while i < x: - i += 1 - a, b = {1: 2, 3: 3} - assert a == 1 and b == 3 - i = 0 - while i < x: - i += 1 - - opt = _testinternalcapi.get_uop_optimizer() - - with temporary_optimizer(opt): - testfunc(20) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - self.assertIn("_UNPACK_SEQUENCE", uops) - - def test_pop_jump_if_false(self): - def testfunc(n): - i = 0 - while i < n: - i += 1 - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - testfunc(20) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - self.assertIn("_GUARD_IS_TRUE_POP", uops) - - def test_pop_jump_if_none(self): - def testfunc(a): - for x in a: - if x is None: - x = 0 - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - testfunc(range(20)) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - self.assertIn("_GUARD_IS_NOT_NONE_POP", uops) - - def test_pop_jump_if_not_none(self): - def testfunc(a): - for x in a: - x = None - if x is not None: - x = 0 - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - testfunc(range(20)) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - self.assertIn("_GUARD_IS_NONE_POP", uops) - - def test_pop_jump_if_true(self): - def testfunc(n): - i = 0 - while not i >= n: - i += 1 - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - testfunc(20) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - self.assertIn("_GUARD_IS_FALSE_POP", uops) - - def test_jump_backward(self): - def testfunc(n): - i = 0 - while i < n: - i += 1 - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - testfunc(20) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - self.assertIn("_JUMP_TO_TOP", uops) - - def test_jump_forward(self): - def testfunc(n): - a = 0 - while a < n: - if a < 0: - a = -a - else: - a = +a - a += 1 - return a - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - testfunc(20) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - # Since there is no JUMP_FORWARD instruction, - # look for indirect evidence: the += operator - self.assertIn("_BINARY_OP_ADD_INT", uops) - - def test_for_iter_range(self): - def testfunc(n): - total = 0 - for i in range(n): - total += i - return total - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - total = testfunc(20) - self.assertEqual(total, 190) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - # for i, (opname, oparg) in enumerate(ex): - # print(f"{i:4d}: {opname:<20s} {oparg:3d}") - uops = {opname for opname, _, _ in ex} - self.assertIn("_GUARD_NOT_EXHAUSTED_RANGE", uops) - # Verification that the jump goes past END_FOR - # is done by manual inspection of the output - - def test_for_iter_list(self): - def testfunc(a): - total = 0 - for i in a: - total += i - return total - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - a = list(range(20)) - total = testfunc(a) - self.assertEqual(total, 190) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - # for i, (opname, oparg) in enumerate(ex): - # print(f"{i:4d}: {opname:<20s} {oparg:3d}") - uops = {opname for opname, _, _ in ex} - self.assertIn("_GUARD_NOT_EXHAUSTED_LIST", uops) - # Verification that the jump goes past END_FOR - # is done by manual inspection of the output - - def test_for_iter_tuple(self): - def testfunc(a): - total = 0 - for i in a: - total += i - return total - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - a = tuple(range(20)) - total = testfunc(a) - self.assertEqual(total, 190) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - # for i, (opname, oparg) in enumerate(ex): - # print(f"{i:4d}: {opname:<20s} {oparg:3d}") - uops = {opname for opname, _, _ in ex} - self.assertIn("_GUARD_NOT_EXHAUSTED_TUPLE", uops) - # Verification that the jump goes past END_FOR - # is done by manual inspection of the output - - def test_list_edge_case(self): - def testfunc(it): - for x in it: - pass - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - a = [1, 2, 3] - it = iter(a) - testfunc(it) - a.append(4) - with self.assertRaises(StopIteration): - next(it) - - def test_call_py_exact_args(self): - def testfunc(n): - def dummy(x): - return x+1 - for i in range(n): - dummy(i) - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - testfunc(20) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - self.assertIn("_PUSH_FRAME", uops) - self.assertIn("_BINARY_OP_ADD_INT", uops) - - def test_branch_taken(self): - def testfunc(n): - for i in range(n): - if i < 0: - i = 0 - else: - i = 1 - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - testfunc(20) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - self.assertIn("_GUARD_IS_FALSE_POP", uops) - - def test_for_iter_tier_two(self): - class MyIter: - def __init__(self, n): - self.n = n - def __iter__(self): - return self - def __next__(self): - self.n -= 1 - if self.n < 0: - raise StopIteration - return self.n - - def testfunc(n, m): - x = 0 - for i in range(m): - for j in MyIter(n): - x += 1000*i + j - return x - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - x = testfunc(10, 10) - - self.assertEqual(x, sum(range(10)) * 10010) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - self.assertIn("_FOR_ITER_TIER_TWO", uops) - - def test_confidence_score(self): - def testfunc(n): - bits = 0 - for i in range(n): - if i & 0x01: - bits += 1 - if i & 0x02: - bits += 1 - if i&0x04: - bits += 1 - if i&0x08: - bits += 1 - if i&0x10: - bits += 1 - if i&0x20: - bits += 1 - return bits - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - x = testfunc(20) - - self.assertEqual(x, 40) - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - ops = [opname for opname, _, _ in ex] - count = ops.count("_GUARD_IS_TRUE_POP") - # Because Each 'if' halves the score, the second branch is - # too much already. - self.assertEqual(count, 1) - - @unittest.skipUnless(support.Py_GIL_DISABLED, 'need Py_GIL_DISABLED') class TestPyThreadId(unittest.TestCase): def test_py_thread_id(self): diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py new file mode 100644 index 00000000000000..9f4731103c9413 --- /dev/null +++ b/Lib/test/test_capi/test_opt.py @@ -0,0 +1,544 @@ +import contextlib +import opcode +import textwrap +import unittest + +import _testinternalcapi + + +@contextlib.contextmanager +def temporary_optimizer(opt): + old_opt = _testinternalcapi.get_optimizer() + _testinternalcapi.set_optimizer(opt) + try: + yield + finally: + _testinternalcapi.set_optimizer(old_opt) + + +@contextlib.contextmanager +def clear_executors(func): + # Clear executors in func before and after running a block + func.__code__ = func.__code__.replace() + try: + yield + finally: + func.__code__ = func.__code__.replace() + + +class TestOptimizerAPI(unittest.TestCase): + + def test_get_counter_optimizer_dealloc(self): + # See gh-108727 + def f(): + _testinternalcapi.get_counter_optimizer() + + f() + + def test_get_set_optimizer(self): + old = _testinternalcapi.get_optimizer() + opt = _testinternalcapi.get_counter_optimizer() + try: + _testinternalcapi.set_optimizer(opt) + self.assertEqual(_testinternalcapi.get_optimizer(), opt) + _testinternalcapi.set_optimizer(None) + self.assertEqual(_testinternalcapi.get_optimizer(), None) + finally: + _testinternalcapi.set_optimizer(old) + + + def test_counter_optimizer(self): + # Generate a new function at each call + ns = {} + exec(textwrap.dedent(""" + def loop(): + for _ in range(1000): + pass + """), ns, ns) + loop = ns['loop'] + + for repeat in range(5): + opt = _testinternalcapi.get_counter_optimizer() + with temporary_optimizer(opt): + self.assertEqual(opt.get_count(), 0) + with clear_executors(loop): + loop() + self.assertEqual(opt.get_count(), 1000) + + def test_long_loop(self): + "Check that we aren't confused by EXTENDED_ARG" + + # Generate a new function at each call + ns = {} + exec(textwrap.dedent(""" + def nop(): + pass + + def long_loop(): + for _ in range(10): + nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); + nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); + nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); + nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); + nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); + nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); + nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); + """), ns, ns) + long_loop = ns['long_loop'] + + opt = _testinternalcapi.get_counter_optimizer() + with temporary_optimizer(opt): + self.assertEqual(opt.get_count(), 0) + long_loop() + self.assertEqual(opt.get_count(), 10) + + def test_code_restore_for_ENTER_EXECUTOR(self): + def testfunc(x): + i = 0 + while i < x: + i += 1 + + opt = _testinternalcapi.get_counter_optimizer() + with temporary_optimizer(opt): + testfunc(1000) + code, replace_code = testfunc.__code__, testfunc.__code__.replace() + self.assertEqual(code, replace_code) + self.assertEqual(hash(code), hash(replace_code)) + + +def get_first_executor(func): + code = func.__code__ + co_code = code.co_code + JUMP_BACKWARD = opcode.opmap["JUMP_BACKWARD"] + for i in range(0, len(co_code), 2): + if co_code[i] == JUMP_BACKWARD: + try: + return _testinternalcapi.get_executor(code, i) + except ValueError: + pass + return None + + +class TestExecutorInvalidation(unittest.TestCase): + + def setUp(self): + self.old = _testinternalcapi.get_optimizer() + self.opt = _testinternalcapi.get_counter_optimizer() + _testinternalcapi.set_optimizer(self.opt) + + def tearDown(self): + _testinternalcapi.set_optimizer(self.old) + + def test_invalidate_object(self): + # Generate a new set of functions at each call + ns = {} + func_src = "\n".join( + f""" + def f{n}(): + for _ in range(1000): + pass + """ for n in range(5) + ) + exec(textwrap.dedent(func_src), ns, ns) + funcs = [ ns[f'f{n}'] for n in range(5)] + objects = [object() for _ in range(5)] + + for f in funcs: + f() + executors = [get_first_executor(f) for f in funcs] + # Set things up so each executor depends on the objects + # with an equal or lower index. + for i, exe in enumerate(executors): + self.assertTrue(exe.is_valid()) + for obj in objects[:i+1]: + _testinternalcapi.add_executor_dependency(exe, obj) + self.assertTrue(exe.is_valid()) + # Assert that the correct executors are invalidated + # and check that nothing crashes when we invalidate + # an executor mutliple times. + for i in (4,3,2,1,0): + _testinternalcapi.invalidate_executors(objects[i]) + for exe in executors[i:]: + self.assertFalse(exe.is_valid()) + for exe in executors[:i]: + self.assertTrue(exe.is_valid()) + + def test_uop_optimizer_invalidation(self): + # Generate a new function at each call + ns = {} + exec(textwrap.dedent(""" + def f(): + for i in range(1000): + pass + """), ns, ns) + f = ns['f'] + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + f() + exe = get_first_executor(f) + self.assertTrue(exe.is_valid()) + _testinternalcapi.invalidate_executors(f.__code__) + self.assertFalse(exe.is_valid()) + +class TestUops(unittest.TestCase): + + def test_basic_loop(self): + def testfunc(x): + i = 0 + while i < x: + i += 1 + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + testfunc(1000) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + self.assertIn("_SET_IP", uops) + self.assertIn("LOAD_FAST", uops) + + def test_extended_arg(self): + "Check EXTENDED_ARG handling in superblock creation" + ns = {} + exec(textwrap.dedent(""" + def many_vars(): + # 260 vars, so z9 should have index 259 + a0 = a1 = a2 = a3 = a4 = a5 = a6 = a7 = a8 = a9 = 42 + b0 = b1 = b2 = b3 = b4 = b5 = b6 = b7 = b8 = b9 = 42 + c0 = c1 = c2 = c3 = c4 = c5 = c6 = c7 = c8 = c9 = 42 + d0 = d1 = d2 = d3 = d4 = d5 = d6 = d7 = d8 = d9 = 42 + e0 = e1 = e2 = e3 = e4 = e5 = e6 = e7 = e8 = e9 = 42 + f0 = f1 = f2 = f3 = f4 = f5 = f6 = f7 = f8 = f9 = 42 + g0 = g1 = g2 = g3 = g4 = g5 = g6 = g7 = g8 = g9 = 42 + h0 = h1 = h2 = h3 = h4 = h5 = h6 = h7 = h8 = h9 = 42 + i0 = i1 = i2 = i3 = i4 = i5 = i6 = i7 = i8 = i9 = 42 + j0 = j1 = j2 = j3 = j4 = j5 = j6 = j7 = j8 = j9 = 42 + k0 = k1 = k2 = k3 = k4 = k5 = k6 = k7 = k8 = k9 = 42 + l0 = l1 = l2 = l3 = l4 = l5 = l6 = l7 = l8 = l9 = 42 + m0 = m1 = m2 = m3 = m4 = m5 = m6 = m7 = m8 = m9 = 42 + n0 = n1 = n2 = n3 = n4 = n5 = n6 = n7 = n8 = n9 = 42 + o0 = o1 = o2 = o3 = o4 = o5 = o6 = o7 = o8 = o9 = 42 + p0 = p1 = p2 = p3 = p4 = p5 = p6 = p7 = p8 = p9 = 42 + q0 = q1 = q2 = q3 = q4 = q5 = q6 = q7 = q8 = q9 = 42 + r0 = r1 = r2 = r3 = r4 = r5 = r6 = r7 = r8 = r9 = 42 + s0 = s1 = s2 = s3 = s4 = s5 = s6 = s7 = s8 = s9 = 42 + t0 = t1 = t2 = t3 = t4 = t5 = t6 = t7 = t8 = t9 = 42 + u0 = u1 = u2 = u3 = u4 = u5 = u6 = u7 = u8 = u9 = 42 + v0 = v1 = v2 = v3 = v4 = v5 = v6 = v7 = v8 = v9 = 42 + w0 = w1 = w2 = w3 = w4 = w5 = w6 = w7 = w8 = w9 = 42 + x0 = x1 = x2 = x3 = x4 = x5 = x6 = x7 = x8 = x9 = 42 + y0 = y1 = y2 = y3 = y4 = y5 = y6 = y7 = y8 = y9 = 42 + z0 = z1 = z2 = z3 = z4 = z5 = z6 = z7 = z8 = z9 = 42 + while z9 > 0: + z9 = z9 - 1 + """), ns, ns) + many_vars = ns["many_vars"] + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + ex = get_first_executor(many_vars) + self.assertIsNone(ex) + many_vars() + + ex = get_first_executor(many_vars) + self.assertIsNotNone(ex) + self.assertIn(("LOAD_FAST", 259, 0), list(ex)) + + def test_unspecialized_unpack(self): + # An example of an unspecialized opcode + def testfunc(x): + i = 0 + while i < x: + i += 1 + a, b = {1: 2, 3: 3} + assert a == 1 and b == 3 + i = 0 + while i < x: + i += 1 + + opt = _testinternalcapi.get_uop_optimizer() + + with temporary_optimizer(opt): + testfunc(20) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + self.assertIn("_UNPACK_SEQUENCE", uops) + + def test_pop_jump_if_false(self): + def testfunc(n): + i = 0 + while i < n: + i += 1 + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + testfunc(20) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + self.assertIn("_GUARD_IS_TRUE_POP", uops) + + def test_pop_jump_if_none(self): + def testfunc(a): + for x in a: + if x is None: + x = 0 + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + testfunc(range(20)) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + self.assertIn("_GUARD_IS_NOT_NONE_POP", uops) + + def test_pop_jump_if_not_none(self): + def testfunc(a): + for x in a: + x = None + if x is not None: + x = 0 + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + testfunc(range(20)) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + self.assertIn("_GUARD_IS_NONE_POP", uops) + + def test_pop_jump_if_true(self): + def testfunc(n): + i = 0 + while not i >= n: + i += 1 + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + testfunc(20) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + self.assertIn("_GUARD_IS_FALSE_POP", uops) + + def test_jump_backward(self): + def testfunc(n): + i = 0 + while i < n: + i += 1 + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + testfunc(20) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + self.assertIn("_JUMP_TO_TOP", uops) + + def test_jump_forward(self): + def testfunc(n): + a = 0 + while a < n: + if a < 0: + a = -a + else: + a = +a + a += 1 + return a + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + testfunc(20) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + # Since there is no JUMP_FORWARD instruction, + # look for indirect evidence: the += operator + self.assertIn("_BINARY_OP_ADD_INT", uops) + + def test_for_iter_range(self): + def testfunc(n): + total = 0 + for i in range(n): + total += i + return total + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + total = testfunc(20) + self.assertEqual(total, 190) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + # for i, (opname, oparg) in enumerate(ex): + # print(f"{i:4d}: {opname:<20s} {oparg:3d}") + uops = {opname for opname, _, _ in ex} + self.assertIn("_GUARD_NOT_EXHAUSTED_RANGE", uops) + # Verification that the jump goes past END_FOR + # is done by manual inspection of the output + + def test_for_iter_list(self): + def testfunc(a): + total = 0 + for i in a: + total += i + return total + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + a = list(range(20)) + total = testfunc(a) + self.assertEqual(total, 190) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + # for i, (opname, oparg) in enumerate(ex): + # print(f"{i:4d}: {opname:<20s} {oparg:3d}") + uops = {opname for opname, _, _ in ex} + self.assertIn("_GUARD_NOT_EXHAUSTED_LIST", uops) + # Verification that the jump goes past END_FOR + # is done by manual inspection of the output + + def test_for_iter_tuple(self): + def testfunc(a): + total = 0 + for i in a: + total += i + return total + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + a = tuple(range(20)) + total = testfunc(a) + self.assertEqual(total, 190) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + # for i, (opname, oparg) in enumerate(ex): + # print(f"{i:4d}: {opname:<20s} {oparg:3d}") + uops = {opname for opname, _, _ in ex} + self.assertIn("_GUARD_NOT_EXHAUSTED_TUPLE", uops) + # Verification that the jump goes past END_FOR + # is done by manual inspection of the output + + def test_list_edge_case(self): + def testfunc(it): + for x in it: + pass + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + a = [1, 2, 3] + it = iter(a) + testfunc(it) + a.append(4) + with self.assertRaises(StopIteration): + next(it) + + def test_call_py_exact_args(self): + def testfunc(n): + def dummy(x): + return x+1 + for i in range(n): + dummy(i) + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + testfunc(20) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + self.assertIn("_PUSH_FRAME", uops) + self.assertIn("_BINARY_OP_ADD_INT", uops) + + def test_branch_taken(self): + def testfunc(n): + for i in range(n): + if i < 0: + i = 0 + else: + i = 1 + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + testfunc(20) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + self.assertIn("_GUARD_IS_FALSE_POP", uops) + + def test_for_iter_tier_two(self): + class MyIter: + def __init__(self, n): + self.n = n + def __iter__(self): + return self + def __next__(self): + self.n -= 1 + if self.n < 0: + raise StopIteration + return self.n + + def testfunc(n, m): + x = 0 + for i in range(m): + for j in MyIter(n): + x += 1000*i + j + return x + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + x = testfunc(10, 10) + + self.assertEqual(x, sum(range(10)) * 10010) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + self.assertIn("_FOR_ITER_TIER_TWO", uops) + + def test_confidence_score(self): + def testfunc(n): + bits = 0 + for i in range(n): + if i & 0x01: + bits += 1 + if i & 0x02: + bits += 1 + if i&0x04: + bits += 1 + if i&0x08: + bits += 1 + if i&0x10: + bits += 1 + if i&0x20: + bits += 1 + return bits + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + x = testfunc(20) + + self.assertEqual(x, 40) + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + ops = [opname for opname, _, _ in ex] + count = ops.count("_GUARD_IS_TRUE_POP") + # Because Each 'if' halves the score, the second branch is + # too much already. + self.assertEqual(count, 1) + + +if __name__ == "__main__": + unittest.main() From fddc829236d7b29a522a2160e57b2d7ca23b9b95 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Wed, 13 Dec 2023 23:41:43 +0000 Subject: [PATCH 03/62] gh-86179: Implement realpath() on Windows for getpath.py calculations (GH-113033) --- Lib/sysconfig/__init__.py | 11 +----- Lib/test/test_venv.py | 23 ++++++++++- ...3-12-12-20-58-09.gh-issue-86179.YYSk_6.rst | 1 + Modules/getpath.c | 39 +++++++++++++++++++ 4 files changed, 63 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2023-12-12-20-58-09.gh-issue-86179.YYSk_6.rst diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py index c60c9f3440615b..deb438c705f3a0 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py @@ -404,16 +404,7 @@ def get_config_h_filename(): """Return the path of pyconfig.h.""" if _PYTHON_BUILD: if os.name == "nt": - # This ought to be as simple as dirname(sys._base_executable), but - # if a venv uses symlinks to a build in the source tree, then this - # fails. So instead we guess the subdirectory name from sys.winver - if sys.winver.endswith('-32'): - arch = 'win32' - elif sys.winver.endswith('-arm64'): - arch = 'arm64' - else: - arch = 'amd64' - inc_dir = os.path.join(_PROJECT_BASE, 'PCbuild', arch) + inc_dir = os.path.dirname(sys._base_executable) else: inc_dir = _PROJECT_BASE else: diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index 617d14dcb9c5fe..8ecb23ff384362 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -46,7 +46,8 @@ def check_output(cmd, encoding=None): p = subprocess.Popen(cmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + stderr=subprocess.PIPE, + env={**os.environ, "PYTHONHOME": ""}) out, err = p.communicate() if p.returncode: if verbose and err: @@ -287,6 +288,16 @@ def test_sysconfig(self): cmd[2] = 'import sysconfig; print(sysconfig.%s)' % call out, err = check_output(cmd, encoding='utf-8') self.assertEqual(out.strip(), expected, err) + for attr, expected in ( + ('executable', self.envpy()), + # Usually compare to sys.executable, but if we're running in our own + # venv then we really need to compare to our base executable + ('_base_executable', sys._base_executable), + ): + with self.subTest(attr): + cmd[2] = f'import sys; print(sys.{attr})' + out, err = check_output(cmd, encoding='utf-8') + self.assertEqual(out.strip(), expected, err) @requireVenvCreate @unittest.skipUnless(can_symlink(), 'Needs symlinks') @@ -309,6 +320,16 @@ def test_sysconfig_symlinks(self): cmd[2] = 'import sysconfig; print(sysconfig.%s)' % call out, err = check_output(cmd, encoding='utf-8') self.assertEqual(out.strip(), expected, err) + for attr, expected in ( + ('executable', self.envpy()), + # Usually compare to sys.executable, but if we're running in our own + # venv then we really need to compare to our base executable + ('_base_executable', sys._base_executable), + ): + with self.subTest(attr): + cmd[2] = f'import sys; print(sys.{attr})' + out, err = check_output(cmd, encoding='utf-8') + self.assertEqual(out.strip(), expected, err) if sys.platform == 'win32': ENV_SUBDIRS = ( diff --git a/Misc/NEWS.d/next/Windows/2023-12-12-20-58-09.gh-issue-86179.YYSk_6.rst b/Misc/NEWS.d/next/Windows/2023-12-12-20-58-09.gh-issue-86179.YYSk_6.rst new file mode 100644 index 00000000000000..c1d96792bdae0b --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2023-12-12-20-58-09.gh-issue-86179.YYSk_6.rst @@ -0,0 +1 @@ +Fixes path calculations when launching Python on Windows through a symlink. diff --git a/Modules/getpath.c b/Modules/getpath.c index 6c1078b8914522..422056b1fb6de4 100644 --- a/Modules/getpath.c +++ b/Modules/getpath.c @@ -502,6 +502,45 @@ getpath_realpath(PyObject *Py_UNUSED(self) , PyObject *args) PyMem_Free((void *)path); PyMem_Free((void *)narrow); return r; +#elif defined(MS_WINDOWS) + HANDLE hFile; + wchar_t resolved[MAXPATHLEN+1]; + int len = 0, err; + PyObject *result; + + wchar_t *path = PyUnicode_AsWideCharString(pathobj, NULL); + if (!path) { + return NULL; + } + + Py_BEGIN_ALLOW_THREADS + hFile = CreateFileW(path, 0, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (hFile != INVALID_HANDLE_VALUE) { + len = GetFinalPathNameByHandleW(hFile, resolved, MAXPATHLEN, VOLUME_NAME_DOS); + err = len ? 0 : GetLastError(); + CloseHandle(hFile); + } else { + err = GetLastError(); + } + Py_END_ALLOW_THREADS + + if (err) { + PyErr_SetFromWindowsErr(err); + result = NULL; + } else if (len <= MAXPATHLEN) { + const wchar_t *p = resolved; + if (0 == wcsncmp(p, L"\\\\?\\", 4)) { + if (GetFileAttributesW(&p[4]) != INVALID_FILE_ATTRIBUTES) { + p += 4; + len -= 4; + } + } + result = PyUnicode_FromWideChar(p, len); + } else { + result = Py_NewRef(pathobj); + } + PyMem_Free(path); + return result; #endif return Py_NewRef(pathobj); From b4f2c89118d5a48ce6c495ba931d7f6493422029 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 14 Dec 2023 09:16:06 +0200 Subject: [PATCH 04/62] gh-113086: Add tests for os.chmod() and os.lchmod() (GH-113087) Also make test_copymode_symlink_to_symlink in test_shutil more strict. --- Lib/test/test_posix.py | 116 ++++++++++++++++++++++++++++++++++++++++ Lib/test/test_shutil.py | 3 +- 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 1722c84727bbd8..4eb3cacc23768f 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -935,6 +935,122 @@ def test_utime(self): posix.utime(os_helper.TESTFN, (int(now), int(now))) posix.utime(os_helper.TESTFN, (now, now)) + def check_chmod(self, chmod_func, target, **kwargs): + mode = os.stat(target).st_mode + try: + new_mode = mode & ~(stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) + chmod_func(target, new_mode, **kwargs) + self.assertEqual(os.stat(target).st_mode, new_mode) + if stat.S_ISREG(mode): + try: + with open(target, 'wb+'): + pass + except PermissionError: + pass + new_mode = mode | (stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) + chmod_func(target, new_mode, **kwargs) + self.assertEqual(os.stat(target).st_mode, new_mode) + if stat.S_ISREG(mode): + with open(target, 'wb+'): + pass + finally: + posix.chmod(target, mode) + + def test_chmod_file(self): + self.check_chmod(posix.chmod, os_helper.TESTFN) + + def tempdir(self): + target = os_helper.TESTFN + 'd' + posix.mkdir(target) + self.addCleanup(posix.rmdir, target) + return target + + def test_chmod_dir(self): + target = self.tempdir() + self.check_chmod(posix.chmod, target) + + @unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()') + def test_lchmod_file(self): + self.check_chmod(posix.lchmod, os_helper.TESTFN) + self.check_chmod(posix.chmod, os_helper.TESTFN, follow_symlinks=False) + + @unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()') + def test_lchmod_dir(self): + target = self.tempdir() + self.check_chmod(posix.lchmod, target) + self.check_chmod(posix.chmod, target, follow_symlinks=False) + + def check_chmod_link(self, chmod_func, target, link, **kwargs): + target_mode = os.stat(target).st_mode + link_mode = os.lstat(link).st_mode + try: + new_mode = target_mode & ~(stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) + chmod_func(link, new_mode, **kwargs) + self.assertEqual(os.stat(target).st_mode, new_mode) + self.assertEqual(os.lstat(link).st_mode, link_mode) + new_mode = target_mode | (stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) + chmod_func(link, new_mode, **kwargs) + self.assertEqual(os.stat(target).st_mode, new_mode) + self.assertEqual(os.lstat(link).st_mode, link_mode) + finally: + posix.chmod(target, target_mode) + + def check_lchmod_link(self, chmod_func, target, link, **kwargs): + target_mode = os.stat(target).st_mode + link_mode = os.lstat(link).st_mode + new_mode = link_mode & ~(stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) + chmod_func(link, new_mode, **kwargs) + self.assertEqual(os.stat(target).st_mode, target_mode) + self.assertEqual(os.lstat(link).st_mode, new_mode) + new_mode = link_mode | (stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) + chmod_func(link, new_mode, **kwargs) + self.assertEqual(os.stat(target).st_mode, target_mode) + self.assertEqual(os.lstat(link).st_mode, new_mode) + + @os_helper.skip_unless_symlink + def test_chmod_file_symlink(self): + target = os_helper.TESTFN + link = os_helper.TESTFN + '-link' + os.symlink(target, link) + self.addCleanup(posix.unlink, link) + if os.name == 'nt': + self.check_lchmod_link(posix.chmod, target, link) + else: + self.check_chmod_link(posix.chmod, target, link) + self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) + + @os_helper.skip_unless_symlink + def test_chmod_dir_symlink(self): + target = self.tempdir() + link = os_helper.TESTFN + '-link' + os.symlink(target, link, target_is_directory=True) + self.addCleanup(posix.unlink, link) + if os.name == 'nt': + self.check_lchmod_link(posix.chmod, target, link) + else: + self.check_chmod_link(posix.chmod, target, link) + self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) + + @unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()') + @os_helper.skip_unless_symlink + def test_lchmod_file_symlink(self): + target = os_helper.TESTFN + link = os_helper.TESTFN + '-link' + os.symlink(target, link) + self.addCleanup(posix.unlink, link) + self.check_lchmod_link(posix.chmod, target, link, follow_symlinks=False) + self.check_lchmod_link(posix.lchmod, target, link) + + @unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()') + @os_helper.skip_unless_symlink + def test_lchmod_dir_symlink(self): + target = self.tempdir() + link = os_helper.TESTFN + '-link' + os.symlink(target, link) + self.addCleanup(posix.unlink, link) + self.check_lchmod_link(posix.chmod, target, link, follow_symlinks=False) + self.check_lchmod_link(posix.lchmod, target, link) + def _test_chflags_regular_file(self, chflags_func, target_file, **kwargs): st = os.stat(target_file) self.assertTrue(hasattr(st, 'st_flags')) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index b29d316352f219..5ce8e5d77fbbf3 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1132,10 +1132,11 @@ def test_copymode_symlink_to_symlink(self): os.lchmod(src_link, stat.S_IRWXO|stat.S_IRWXG) # link to link os.lchmod(dst_link, stat.S_IRWXO) + old_mode = os.stat(dst).st_mode shutil.copymode(src_link, dst_link, follow_symlinks=False) self.assertEqual(os.lstat(src_link).st_mode, os.lstat(dst_link).st_mode) - self.assertNotEqual(os.stat(src).st_mode, os.stat(dst).st_mode) + self.assertEqual(os.stat(dst).st_mode, old_mode) # src link - use chmod os.lchmod(dst_link, stat.S_IRWXO) shutil.copymode(src_link, dst, follow_symlinks=False) From 4d5d9acb22ee8c6908ebd4fdc3d17472f8b286e3 Mon Sep 17 00:00:00 2001 From: Stephen Gildea Date: Wed, 13 Dec 2023 23:53:08 -0800 Subject: [PATCH 05/62] gh-90890: Reorder mailbox.Maildir method documentation (GH-113071) When new mailbox.Maildir methods were added for 3.13.0a2, their documentation was added at the end of the mailbox.Maildir section instead of grouping them with other methods Maildir adds to Mailbox. This commit moves the new methods' documentation adjacent to documentation for existing Maildir-specific methods, so that the "special remarks" for common methods remains at the end. --- Doc/library/mailbox.rst | 80 ++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/Doc/library/mailbox.rst b/Doc/library/mailbox.rst index 05ffaf6c9b336e..fd60d163378f07 100644 --- a/Doc/library/mailbox.rst +++ b/Doc/library/mailbox.rst @@ -383,46 +383,6 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. last 36 hours. The Maildir specification says that mail-reading programs should do this occasionally. - Some :class:`Mailbox` methods implemented by :class:`Maildir` deserve special - remarks: - - - .. method:: add(message) - __setitem__(key, message) - update(arg) - - .. warning:: - - These methods generate unique file names based upon the current process - ID. When using multiple threads, undetected name clashes may occur and - cause corruption of the mailbox unless threads are coordinated to avoid - using these methods to manipulate the same mailbox simultaneously. - - - .. method:: flush() - - All changes to Maildir mailboxes are immediately applied, so this method - does nothing. - - - .. method:: lock() - unlock() - - Maildir mailboxes do not support (or require) locking, so these methods do - nothing. - - - .. method:: close() - - :class:`Maildir` instances do not keep any open files and the underlying - mailboxes do not support locking, so this method does nothing. - - - .. method:: get_file(key) - - Depending upon the host platform, it may not be possible to modify or - remove the underlying message while the returned file remains open. - .. method:: get_flags(key) @@ -525,6 +485,46 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. versionadded:: 3.13 + Some :class:`Mailbox` methods implemented by :class:`Maildir` deserve special + remarks: + + + .. method:: add(message) + __setitem__(key, message) + update(arg) + + .. warning:: + + These methods generate unique file names based upon the current process + ID. When using multiple threads, undetected name clashes may occur and + cause corruption of the mailbox unless threads are coordinated to avoid + using these methods to manipulate the same mailbox simultaneously. + + + .. method:: flush() + + All changes to Maildir mailboxes are immediately applied, so this method + does nothing. + + + .. method:: lock() + unlock() + + Maildir mailboxes do not support (or require) locking, so these methods do + nothing. + + + .. method:: close() + + :class:`Maildir` instances do not keep any open files and the underlying + mailboxes do not support locking, so this method does nothing. + + + .. method:: get_file(key) + + Depending upon the host platform, it may not be possible to modify or + remove the underlying message while the returned file remains open. + .. seealso:: From bb36f72efcc6a656e0907ffa83620a1e44044895 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 14 Dec 2023 12:04:23 +0200 Subject: [PATCH 06/62] gh-111049: Fix crash during garbage collection of the BytesIO buffer object (GH-111221) --- Lib/test/test_memoryio.py | 21 +++++++++++++++++++ ...-10-23-18-42-26.gh-issue-111049.Ys7-o_.rst | 2 ++ Modules/_io/bytesio.c | 14 ++++--------- 3 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-10-23-18-42-26.gh-issue-111049.Ys7-o_.rst diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py index 731299294e6877..8192502a40791b 100644 --- a/Lib/test/test_memoryio.py +++ b/Lib/test/test_memoryio.py @@ -6,10 +6,12 @@ import unittest from test import support +import gc import io import _pyio as pyio import pickle import sys +import weakref class IntLike: def __init__(self, num): @@ -477,6 +479,25 @@ def test_getbuffer_empty(self): buf2.release() memio.write(b'x') + def test_getbuffer_gc_collect(self): + memio = self.ioclass(b"1234567890") + buf = memio.getbuffer() + memiowr = weakref.ref(memio) + bufwr = weakref.ref(buf) + # Create a reference loop. + a = [buf] + a.append(a) + # The Python implementation emits an unraisable exception. + with support.catch_unraisable_exception(): + del memio + del buf + del a + # The C implementation emits an unraisable exception. + with support.catch_unraisable_exception(): + gc.collect() + self.assertIsNone(memiowr()) + self.assertIsNone(bufwr()) + def test_read1(self): buf = self.buftype("1234567890") self.assertEqual(self.ioclass(buf).read1(), buf) diff --git a/Misc/NEWS.d/next/Library/2023-10-23-18-42-26.gh-issue-111049.Ys7-o_.rst b/Misc/NEWS.d/next/Library/2023-10-23-18-42-26.gh-issue-111049.Ys7-o_.rst new file mode 100644 index 00000000000000..b1de348bea0a58 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-10-23-18-42-26.gh-issue-111049.Ys7-o_.rst @@ -0,0 +1,2 @@ +Fix crash during garbage collection of the :class:`io.BytesIO` buffer +object. diff --git a/Modules/_io/bytesio.c b/Modules/_io/bytesio.c index 16b8ac600ace79..4a15c8e841f25f 100644 --- a/Modules/_io/bytesio.c +++ b/Modules/_io/bytesio.c @@ -990,7 +990,9 @@ static int bytesio_clear(bytesio *self) { Py_CLEAR(self->dict); - Py_CLEAR(self->buf); + if (self->exports == 0) { + Py_CLEAR(self->buf); + } return 0; } @@ -1095,13 +1097,6 @@ bytesiobuf_releasebuffer(bytesiobuf *obj, Py_buffer *view) b->exports--; } -static int -bytesiobuf_clear(bytesiobuf *self) -{ - Py_CLEAR(self->source); - return 0; -} - static int bytesiobuf_traverse(bytesiobuf *self, visitproc visit, void *arg) { @@ -1116,7 +1111,7 @@ bytesiobuf_dealloc(bytesiobuf *self) PyTypeObject *tp = Py_TYPE(self); /* bpo-31095: UnTrack is needed before calling any callbacks */ PyObject_GC_UnTrack(self); - (void)bytesiobuf_clear(self); + Py_CLEAR(self->source); tp->tp_free(self); Py_DECREF(tp); } @@ -1124,7 +1119,6 @@ bytesiobuf_dealloc(bytesiobuf *self) static PyType_Slot bytesiobuf_slots[] = { {Py_tp_dealloc, bytesiobuf_dealloc}, {Py_tp_traverse, bytesiobuf_traverse}, - {Py_tp_clear, bytesiobuf_clear}, // Buffer protocol {Py_bf_getbuffer, bytesiobuf_getbuffer}, From b3c21265fa48a137a4c6a35e425b2b7f94f96cf4 Mon Sep 17 00:00:00 2001 From: Daniel Wysocki Date: Thu, 14 Dec 2023 04:07:37 -0600 Subject: [PATCH 07/62] Fixing typo in DocTestRunner docs (GH-112326) --- Doc/library/doctest.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/doctest.rst b/Doc/library/doctest.rst index fa1b850c531346..8c28e4478bb70e 100644 --- a/Doc/library/doctest.rst +++ b/Doc/library/doctest.rst @@ -1502,7 +1502,7 @@ DocTestRunner objects :attr:`failures` and :attr:`skips` attributes. The :meth:`run` and :meth:`summarize` methods return a :class:`TestResults` instance. - :class:`DocTestParser` defines the following methods: + :class:`DocTestRunner` defines the following methods: .. method:: report_start(out, test, example) From 23a5711100271e5a8b9dd9ab48b10807627ef4fb Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Thu, 14 Dec 2023 10:26:46 +0000 Subject: [PATCH 08/62] gh-112205: Update textio module to use `@getter` as possible. (gh-113095) --- Modules/_io/clinic/textio.c.h | 90 ++++++++++++++++++++++++++++++++++- Modules/_io/textio.c | 84 ++++++++++++++------------------ 2 files changed, 125 insertions(+), 49 deletions(-) diff --git a/Modules/_io/clinic/textio.c.h b/Modules/_io/clinic/textio.c.h index a492f340c74c0d..f24f65f0c1d4f9 100644 --- a/Modules/_io/clinic/textio.c.h +++ b/Modules/_io/clinic/textio.c.h @@ -1048,6 +1048,94 @@ _io_TextIOWrapper_close(textio *self, PyObject *Py_UNUSED(ignored)) return return_value; } +#if defined(_IO_TEXTIOWRAPPER_NAME_GETSETDEF) +# undef _IO_TEXTIOWRAPPER_NAME_GETSETDEF +# define _IO_TEXTIOWRAPPER_NAME_GETSETDEF {"name", (getter)_io_TextIOWrapper_name_get, (setter)_io_TextIOWrapper_name_set, NULL}, +#else +# define _IO_TEXTIOWRAPPER_NAME_GETSETDEF {"name", (getter)_io_TextIOWrapper_name_get, NULL, NULL}, +#endif + +static PyObject * +_io_TextIOWrapper_name_get_impl(textio *self); + +static PyObject * +_io_TextIOWrapper_name_get(textio *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _io_TextIOWrapper_name_get_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +#if defined(_IO_TEXTIOWRAPPER_CLOSED_GETSETDEF) +# undef _IO_TEXTIOWRAPPER_CLOSED_GETSETDEF +# define _IO_TEXTIOWRAPPER_CLOSED_GETSETDEF {"closed", (getter)_io_TextIOWrapper_closed_get, (setter)_io_TextIOWrapper_closed_set, NULL}, +#else +# define _IO_TEXTIOWRAPPER_CLOSED_GETSETDEF {"closed", (getter)_io_TextIOWrapper_closed_get, NULL, NULL}, +#endif + +static PyObject * +_io_TextIOWrapper_closed_get_impl(textio *self); + +static PyObject * +_io_TextIOWrapper_closed_get(textio *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _io_TextIOWrapper_closed_get_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +#if defined(_IO_TEXTIOWRAPPER_NEWLINES_GETSETDEF) +# undef _IO_TEXTIOWRAPPER_NEWLINES_GETSETDEF +# define _IO_TEXTIOWRAPPER_NEWLINES_GETSETDEF {"newlines", (getter)_io_TextIOWrapper_newlines_get, (setter)_io_TextIOWrapper_newlines_set, NULL}, +#else +# define _IO_TEXTIOWRAPPER_NEWLINES_GETSETDEF {"newlines", (getter)_io_TextIOWrapper_newlines_get, NULL, NULL}, +#endif + +static PyObject * +_io_TextIOWrapper_newlines_get_impl(textio *self); + +static PyObject * +_io_TextIOWrapper_newlines_get(textio *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _io_TextIOWrapper_newlines_get_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +#if defined(_IO_TEXTIOWRAPPER_ERRORS_GETSETDEF) +# undef _IO_TEXTIOWRAPPER_ERRORS_GETSETDEF +# define _IO_TEXTIOWRAPPER_ERRORS_GETSETDEF {"errors", (getter)_io_TextIOWrapper_errors_get, (setter)_io_TextIOWrapper_errors_set, NULL}, +#else +# define _IO_TEXTIOWRAPPER_ERRORS_GETSETDEF {"errors", (getter)_io_TextIOWrapper_errors_get, NULL, NULL}, +#endif + +static PyObject * +_io_TextIOWrapper_errors_get_impl(textio *self); + +static PyObject * +_io_TextIOWrapper_errors_get(textio *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _io_TextIOWrapper_errors_get_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + #if defined(_IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF) # undef _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF # define _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF {"_CHUNK_SIZE", (getter)_io_TextIOWrapper__CHUNK_SIZE_get, (setter)_io_TextIOWrapper__CHUNK_SIZE_set, NULL}, @@ -1091,4 +1179,4 @@ _io_TextIOWrapper__CHUNK_SIZE_set(textio *self, PyObject *value, void *Py_UNUSED return return_value; } -/*[clinic end generated code: output=b312f2d2e2221580 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=7af87bf848a5d3f3 input=a9049054013a1b77]*/ diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c index c76d92cdd38b9a..702336ca2aeb06 100644 --- a/Modules/_io/textio.c +++ b/Modules/_io/textio.c @@ -1475,7 +1475,7 @@ textiowrapper_traverse(textio *self, visitproc visit, void *arg) } static PyObject * -textiowrapper_closed_get(textio *self, void *context); +_io_TextIOWrapper_closed_get_impl(textio *self); /* This macro takes some shortcuts to make the common case faster. */ #define CHECK_CLOSED(self) \ @@ -1486,7 +1486,7 @@ textiowrapper_closed_get(textio *self, void *context); if (self->raw != NULL) \ r = _PyFileIO_closed(self->raw); \ else { \ - _res = textiowrapper_closed_get(self, NULL); \ + _res = _io_TextIOWrapper_closed_get_impl(self); \ if (_res == NULL) \ return NULL; \ r = PyObject_IsTrue(_res); \ @@ -3090,7 +3090,7 @@ _io_TextIOWrapper_close_impl(textio *self) int r; CHECK_ATTACHED(self); - res = textiowrapper_closed_get(self, NULL); + res = _io_TextIOWrapper_closed_get_impl(self); if (res == NULL) return NULL; r = PyObject_IsTrue(res); @@ -3164,42 +3164,43 @@ textiowrapper_iternext(textio *self) return line; } +/*[clinic input] +@critical_section +@getter +_io.TextIOWrapper.name +[clinic start generated code]*/ + static PyObject * -textiowrapper_name_get_impl(textio *self, void *context) +_io_TextIOWrapper_name_get_impl(textio *self) +/*[clinic end generated code: output=8c2f1d6d8756af40 input=26ecec9b39e30e07]*/ { CHECK_ATTACHED(self); return PyObject_GetAttr(self->buffer, &_Py_ID(name)); } -static PyObject * -textiowrapper_name_get(textio *self, void *context) -{ - PyObject *result = NULL; - Py_BEGIN_CRITICAL_SECTION(self); - result = textiowrapper_name_get_impl(self, context); - Py_END_CRITICAL_SECTION(); - return result; -} +/*[clinic input] +@critical_section +@getter +_io.TextIOWrapper.closed +[clinic start generated code]*/ static PyObject * -textiowrapper_closed_get_impl(textio *self, void *context) +_io_TextIOWrapper_closed_get_impl(textio *self) +/*[clinic end generated code: output=b49b68f443a85e3c input=7dfcf43f63c7003d]*/ { CHECK_ATTACHED(self); return PyObject_GetAttr(self->buffer, &_Py_ID(closed)); } -static PyObject * -textiowrapper_closed_get(textio *self, void *context) -{ - PyObject *result = NULL; - Py_BEGIN_CRITICAL_SECTION(self); - result = textiowrapper_closed_get_impl(self, context); - Py_END_CRITICAL_SECTION(); - return result; -} +/*[clinic input] +@critical_section +@getter +_io.TextIOWrapper.newlines +[clinic start generated code]*/ static PyObject * -textiowrapper_newlines_get_impl(textio *self, void *context) +_io_TextIOWrapper_newlines_get_impl(textio *self) +/*[clinic end generated code: output=53aa03ac35573180 input=610df647e514b3e8]*/ { PyObject *res; CHECK_ATTACHED(self); @@ -3211,33 +3212,20 @@ textiowrapper_newlines_get_impl(textio *self, void *context) return res; } -static PyObject * -textiowrapper_newlines_get(textio *self, void *context) -{ - PyObject *result = NULL; - Py_BEGIN_CRITICAL_SECTION(self); - result = textiowrapper_newlines_get_impl(self, context); - Py_END_CRITICAL_SECTION(); - return result; -} +/*[clinic input] +@critical_section +@getter +_io.TextIOWrapper.errors +[clinic start generated code]*/ static PyObject * -textiowrapper_errors_get_impl(textio *self, void *context) +_io_TextIOWrapper_errors_get_impl(textio *self) +/*[clinic end generated code: output=dca3a3ef21b09484 input=b45f983e6d43c4d8]*/ { CHECK_INITIALIZED(self); return Py_NewRef(self->errors); } -static PyObject * -textiowrapper_errors_get(textio *self, void *context) -{ - PyObject *result = NULL; - Py_BEGIN_CRITICAL_SECTION(self); - result = textiowrapper_errors_get_impl(self, context); - Py_END_CRITICAL_SECTION(); - return result; -} - /*[clinic input] @critical_section @getter @@ -3349,12 +3337,12 @@ static PyMemberDef textiowrapper_members[] = { }; static PyGetSetDef textiowrapper_getset[] = { - {"name", (getter)textiowrapper_name_get, NULL, NULL}, - {"closed", (getter)textiowrapper_closed_get, NULL, NULL}, + _IO_TEXTIOWRAPPER_NAME_GETSETDEF + _IO_TEXTIOWRAPPER_CLOSED_GETSETDEF /* {"mode", (getter)TextIOWrapper_mode_get, NULL, NULL}, */ - {"newlines", (getter)textiowrapper_newlines_get, NULL, NULL}, - {"errors", (getter)textiowrapper_errors_get, NULL, NULL}, + _IO_TEXTIOWRAPPER_NEWLINES_GETSETDEF + _IO_TEXTIOWRAPPER_ERRORS_GETSETDEF _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF {NULL} }; From c6e953be125c491226adc1a5bc9c804ce256aa17 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 14 Dec 2023 13:27:43 +0200 Subject: [PATCH 09/62] gh-113090: Fix test.support.os_support.can_chmod() on Windows (GH-113091) --- Lib/test/support/os_helper.py | 10 +++++++--- Lib/test/test_os.py | 2 +- Lib/test/test_posix.py | 4 +++- Lib/test/test_tarfile.py | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Lib/test/support/os_helper.py b/Lib/test/support/os_helper.py index 7a67d87fb9e846..20f38fd36a8876 100644 --- a/Lib/test/support/os_helper.py +++ b/Lib/test/support/os_helper.py @@ -247,15 +247,15 @@ def can_chmod(): global _can_chmod if _can_chmod is not None: return _can_chmod - if not hasattr(os, "chown"): + if not hasattr(os, "chmod"): _can_chmod = False return _can_chmod try: with open(TESTFN, "wb") as f: try: - os.chmod(TESTFN, 0o777) + os.chmod(TESTFN, 0o555) mode1 = os.stat(TESTFN).st_mode - os.chmod(TESTFN, 0o666) + os.chmod(TESTFN, 0o777) mode2 = os.stat(TESTFN).st_mode except OSError as e: can = False @@ -302,6 +302,10 @@ def can_dac_override(): else: _can_dac_override = True finally: + try: + os.chmod(TESTFN, 0o700) + except OSError: + pass unlink(TESTFN) return _can_dac_override diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index d4680ef0f0e03f..c66c5797471413 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -1743,7 +1743,7 @@ def tearDown(self): os.removedirs(path) -@os_helper.skip_unless_working_chmod +@unittest.skipUnless(hasattr(os, "chown"), "requires os.chown()") class ChownFileTests(unittest.TestCase): @classmethod diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 4eb3cacc23768f..887420f8caccfb 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -791,7 +791,7 @@ def check_stat(uid, gid): self.assertRaises(TypeError, chown_func, first_param, uid, t(gid)) check_stat(uid, gid) - @os_helper.skip_unless_working_chmod + @unittest.skipUnless(hasattr(os, "chown"), "requires os.chown()") @unittest.skipIf(support.is_emscripten, "getgid() is a stub") def test_chown(self): # raise an OSError if the file does not exist @@ -956,6 +956,7 @@ def check_chmod(self, chmod_func, target, **kwargs): finally: posix.chmod(target, mode) + @os_helper.skip_unless_working_chmod def test_chmod_file(self): self.check_chmod(posix.chmod, os_helper.TESTFN) @@ -965,6 +966,7 @@ def tempdir(self): self.addCleanup(posix.rmdir, target) return target + @os_helper.skip_unless_working_chmod def test_chmod_dir(self): target = self.tempdir() self.check_chmod(posix.chmod, target) diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index 761560bfbf8b53..edfeac6d6a5edf 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -3452,7 +3452,7 @@ def expect_file(self, name, type=None, symlink_to=None, mode=None, path = pathlib.Path(os.path.normpath(self.destdir / name)) self.assertIn(path, self.expected_paths) self.expected_paths.remove(path) - if mode is not None and os_helper.can_chmod(): + if mode is not None and os_helper.can_chmod() and os.name != 'nt': got = stat.filemode(stat.S_IMODE(path.stat().st_mode)) self.assertEqual(got, mode) if type is None and isinstance(name, str) and name.endswith('/'): From 29f7eb4859bfc27a4c93f36449ca7d810e13288b Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 14 Dec 2023 13:28:37 +0200 Subject: [PATCH 10/62] gh-59616: Support os.chmod(follow_symlinks=True) and os.lchmod() on Windows (GH-113049) --- Doc/library/os.rst | 9 +- Doc/whatsnew/3.13.rst | 6 ++ Lib/os.py | 1 + Lib/tempfile.py | 2 +- Lib/test/test_posix.py | 4 +- ...3-12-13-17-08-21.gh-issue-59616.JNlWSs.rst | 3 + Modules/clinic/posixmodule.c.h | 11 +-- Modules/posixmodule.c | 83 +++++++++++++++---- Tools/clinic/clinic.py | 2 +- 9 files changed, 93 insertions(+), 28 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-13-17-08-21.gh-issue-59616.JNlWSs.rst diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 9d2a3d65069253..f4566a6684e14c 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -2062,6 +2062,7 @@ features: Although Windows supports :func:`chmod`, you can only set the file's read-only flag with it (via the ``stat.S_IWRITE`` and ``stat.S_IREAD`` constants or a corresponding integer value). All other bits are ignored. + The default value of *follow_symlinks* is ``False`` on Windows. The function is limited on Emscripten and WASI, see :ref:`wasm-availability` for more information. @@ -2075,6 +2076,9 @@ features: .. versionchanged:: 3.6 Accepts a :term:`path-like object`. + .. versionchanged:: 3.13 + Added support for the *follow_symlinks* argument on Windows. + .. function:: chown(path, uid, gid, *, dir_fd=None, follow_symlinks=True) @@ -2165,11 +2169,14 @@ features: .. audit-event:: os.chmod path,mode,dir_fd os.lchmod - .. availability:: Unix, not Linux, FreeBSD >= 1.3, NetBSD >= 1.3, not OpenBSD + .. availability:: Unix, Windows, not Linux, FreeBSD >= 1.3, NetBSD >= 1.3, not OpenBSD .. versionchanged:: 3.6 Accepts a :term:`path-like object`. + .. versionchanged:: 3.13 + Added support on Windows. + .. function:: lchown(path, uid, gid) Change the owner and group id of *path* to the numeric *uid* and *gid*. This diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index d599ba9ae6fac8..bd2ae653100a43 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -261,6 +261,12 @@ os CPU resources of a container system without having to modify the container (application code). (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`) + pathlib ------- diff --git a/Lib/os.py b/Lib/os.py index a17946750ea7e7..8c4b93250918eb 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -171,6 +171,7 @@ def _add(str, fn): _add("HAVE_FSTATAT", "stat") _add("HAVE_LCHFLAGS", "chflags") _add("HAVE_LCHMOD", "chmod") + _add("MS_WINDOWS", "chmod") if _exists("lchown"): # mac os x10.3 _add("HAVE_LCHOWN", "chown") _add("HAVE_LINKAT", "link") diff --git a/Lib/tempfile.py b/Lib/tempfile.py index cbfc172a789b25..b5a15f7b72c872 100644 --- a/Lib/tempfile.py +++ b/Lib/tempfile.py @@ -273,7 +273,7 @@ def _dont_follow_symlinks(func, path, *args): # Pass follow_symlinks=False, unless not supported on this platform. if func in _os.supports_follow_symlinks: func(path, *args, follow_symlinks=False) - elif _os.name == 'nt' or not _os.path.islink(path): + elif not _os.path.islink(path): func(path, *args) def _resetperms(path): diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 887420f8caccfb..55cc5e4c6e4f03 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -1019,7 +1019,7 @@ def test_chmod_file_symlink(self): self.check_lchmod_link(posix.chmod, target, link) else: self.check_chmod_link(posix.chmod, target, link) - self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) + self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) @os_helper.skip_unless_symlink def test_chmod_dir_symlink(self): @@ -1031,7 +1031,7 @@ def test_chmod_dir_symlink(self): self.check_lchmod_link(posix.chmod, target, link) else: self.check_chmod_link(posix.chmod, target, link) - self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) + self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) @unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()') @os_helper.skip_unless_symlink diff --git a/Misc/NEWS.d/next/Library/2023-12-13-17-08-21.gh-issue-59616.JNlWSs.rst b/Misc/NEWS.d/next/Library/2023-12-13-17-08-21.gh-issue-59616.JNlWSs.rst new file mode 100644 index 00000000000000..793ae63b4c1ff5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-13-17-08-21.gh-issue-59616.JNlWSs.rst @@ -0,0 +1,3 @@ +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. diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 9d6cd337f4a2f4..f36872a1eb7a0f 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -493,7 +493,8 @@ os_fchdir(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *k #endif /* defined(HAVE_FCHDIR) */ PyDoc_STRVAR(os_chmod__doc__, -"chmod($module, /, path, mode, *, dir_fd=None, follow_symlinks=True)\n" +"chmod($module, /, path, mode, *, dir_fd=None,\n" +" follow_symlinks=(os.name != \'nt\'))\n" "--\n" "\n" "Change the access permissions of a file.\n" @@ -562,7 +563,7 @@ os_chmod(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kw path_t path = PATH_T_INITIALIZE("chmod", "path", 0, PATH_HAVE_FCHMOD); int mode; int dir_fd = DEFAULT_DIR_FD; - int follow_symlinks = 1; + int follow_symlinks = CHMOD_DEFAULT_FOLLOW_SYMLINKS; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 0, argsbuf); if (!args) { @@ -677,7 +678,7 @@ os_fchmod(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *k #endif /* defined(HAVE_FCHMOD) */ -#if defined(HAVE_LCHMOD) +#if (defined(HAVE_LCHMOD) || defined(MS_WINDOWS)) PyDoc_STRVAR(os_lchmod__doc__, "lchmod($module, /, path, mode)\n" @@ -747,7 +748,7 @@ os_lchmod(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *k return return_value; } -#endif /* defined(HAVE_LCHMOD) */ +#endif /* (defined(HAVE_LCHMOD) || defined(MS_WINDOWS)) */ #if defined(HAVE_CHFLAGS) @@ -12421,4 +12422,4 @@ os__supports_virtual_terminal(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF #define OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF #endif /* !defined(OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF) */ -/*[clinic end generated code: output=ff0ec3371de19904 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=1be15e60a553b40d input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index ddbb4cd43babfc..b464a28e63b8ac 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -3309,6 +3309,29 @@ os_fchdir_impl(PyObject *module, int fd) } #endif /* HAVE_FCHDIR */ +#ifdef MS_WINDOWS +# define CHMOD_DEFAULT_FOLLOW_SYMLINKS 0 +#else +# define CHMOD_DEFAULT_FOLLOW_SYMLINKS 1 +#endif + +#ifdef MS_WINDOWS +static int +win32_lchmod(LPCWSTR path, int mode) +{ + DWORD attr = GetFileAttributesW(path); + if (attr == INVALID_FILE_ATTRIBUTES) { + return 0; + } + if (mode & _S_IWRITE) { + attr &= ~FILE_ATTRIBUTE_READONLY; + } + else { + attr |= FILE_ATTRIBUTE_READONLY; + } + return SetFileAttributesW(path, attr); +} +#endif /*[clinic input] os.chmod @@ -3331,7 +3354,8 @@ os.chmod and path should be relative; path will then be relative to that directory. - follow_symlinks: bool = True + follow_symlinks: bool(c_default="CHMOD_DEFAULT_FOLLOW_SYMLINKS", \ + py_default="(os.name != 'nt')") = CHMOD_DEFAULT_FOLLOW_SYMLINKS If False, and the last element of the path is a symbolic link, chmod will modify the symbolic link itself instead of the file the link points to. @@ -3348,20 +3372,16 @@ dir_fd and follow_symlinks may not be implemented on your platform. static PyObject * os_chmod_impl(PyObject *module, path_t *path, int mode, int dir_fd, int follow_symlinks) -/*[clinic end generated code: output=5cf6a94915cc7bff input=674a14bc998de09d]*/ +/*[clinic end generated code: output=5cf6a94915cc7bff input=fcf115d174b9f3d8]*/ { int result; -#ifdef MS_WINDOWS - DWORD attr; -#endif - #ifdef HAVE_FCHMODAT int fchmodat_nofollow_unsupported = 0; int fchmodat_unsupported = 0; #endif -#if !(defined(HAVE_FCHMODAT) || defined(HAVE_LCHMOD)) +#if !(defined(HAVE_FCHMODAT) || defined(HAVE_LCHMOD) || defined(MS_WINDOWS)) if (follow_symlinks_specified("chmod", follow_symlinks)) return NULL; #endif @@ -3372,19 +3392,36 @@ os_chmod_impl(PyObject *module, path_t *path, int mode, int dir_fd, } #ifdef MS_WINDOWS + result = 0; Py_BEGIN_ALLOW_THREADS - attr = GetFileAttributesW(path->wide); - if (attr == INVALID_FILE_ATTRIBUTES) - result = 0; + if (follow_symlinks) { + HANDLE hfile; + FILE_BASIC_INFO info; + + hfile = CreateFileW(path->wide, + FILE_READ_ATTRIBUTES|FILE_WRITE_ATTRIBUTES, + 0, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (hfile != INVALID_HANDLE_VALUE) { + if (GetFileInformationByHandleEx(hfile, FileBasicInfo, + &info, sizeof(info))) + { + if (mode & _S_IWRITE) { + info.FileAttributes &= ~FILE_ATTRIBUTE_READONLY; + } + else { + info.FileAttributes |= FILE_ATTRIBUTE_READONLY; + } + result = SetFileInformationByHandle(hfile, FileBasicInfo, + &info, sizeof(info)); + } + (void)CloseHandle(hfile); + } + } else { - if (mode & _S_IWRITE) - attr &= ~FILE_ATTRIBUTE_READONLY; - else - attr |= FILE_ATTRIBUTE_READONLY; - result = SetFileAttributesW(path->wide, attr); + result = win32_lchmod(path->wide, mode); } Py_END_ALLOW_THREADS - if (!result) { return path_error(path); } @@ -3514,7 +3551,7 @@ os_fchmod_impl(PyObject *module, int fd, int mode) #endif /* HAVE_FCHMOD */ -#ifdef HAVE_LCHMOD +#if defined(HAVE_LCHMOD) || defined(MS_WINDOWS) /*[clinic input] os.lchmod @@ -3535,6 +3572,15 @@ os_lchmod_impl(PyObject *module, path_t *path, int mode) if (PySys_Audit("os.chmod", "Oii", path->object, mode, -1) < 0) { return NULL; } +#ifdef MS_WINDOWS + Py_BEGIN_ALLOW_THREADS + res = win32_lchmod(path->wide, mode); + Py_END_ALLOW_THREADS + if (!res) { + path_error(path); + return NULL; + } +#else /* MS_WINDOWS */ Py_BEGIN_ALLOW_THREADS res = lchmod(path->narrow, mode); Py_END_ALLOW_THREADS @@ -3542,9 +3588,10 @@ os_lchmod_impl(PyObject *module, path_t *path, int mode) path_error(path); return NULL; } +#endif /* MS_WINDOWS */ Py_RETURN_NONE; } -#endif /* HAVE_LCHMOD */ +#endif /* HAVE_LCHMOD || MS_WINDOWS */ #ifdef HAVE_CHFLAGS diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 5ec088765f3e01..a9bf110291eadd 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -3737,7 +3737,7 @@ def converter_init(self, *, accept: TypeSet = {object}) -> None: self.format_unit = 'i' elif accept != {object}: fail(f"bool_converter: illegal 'accept' argument {accept!r}") - if self.default is not unspecified: + if self.default is not unspecified and self.default is not unknown: self.default = bool(self.default) self.c_default = str(int(self.default)) From 12f0bbd6e08bcc1e7165f2641716f7685c1db35c Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 14 Dec 2023 13:36:48 +0200 Subject: [PATCH 11/62] gh-112730: Update docs for colour env vars (#112837) --- Doc/using/cmdline.rst | 5 ++++- Doc/whatsnew/3.13.rst | 3 ++- Misc/python.man | 3 +++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst index dac4956b551dd3..e032a1971bc6d6 100644 --- a/Doc/using/cmdline.rst +++ b/Doc/using/cmdline.rst @@ -610,7 +610,9 @@ Miscellaneous options .. versionadded:: 3.13 The ``-X presite`` option. -Controlling Color +.. _using-on-controlling-color: + +Controlling color ~~~~~~~~~~~~~~~~~ The Python interpreter is configured by default to use colors to highlight @@ -1133,6 +1135,7 @@ conflict. If this variable is set to ``1``, the interpreter will colorize various kinds of output. Setting it to ``0`` deactivates this behavior. + See also :ref:`using-on-controlling-color`. .. versionadded:: 3.13 diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index bd2ae653100a43..e22257853d8333 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -91,7 +91,8 @@ Improved Error Messages * The interpreter now colorizes error messages when displaying tracebacks by default. This feature can be controlled via the new :envvar:`PYTHON_COLORS` environment variable as well as the canonical ``NO_COLOR`` and ``FORCE_COLOR`` environment - variables. (Contributed by Pablo Galindo Salgado in :gh:`112730`.) + variables. See also :ref:`using-on-controlling-color`. + (Contributed by Pablo Galindo Salgado in :gh:`112730`.) Other Language Changes ====================== diff --git a/Misc/python.man b/Misc/python.man index 9f89c94adf5028..14cbd85c60bfa9 100644 --- a/Misc/python.man +++ b/Misc/python.man @@ -601,6 +601,9 @@ show how long each import takes. This is exactly equivalent to setting .IP PYTHONBREAKPOINT If this environment variable is set to 0, it disables the default debugger. It can be set to the callable of your debugger of choice. +.IP PYTHON_COLORS +If this variable is set to 1, the interpreter will colorize various kinds of +output. Setting it to 0 deactivates this behavior. .SS Debug-mode variables Setting these variables only has an effect in a debug build of Python, that is, if Python was configured with the From 1161c14e8c68296fc465cd48970b32be9bee012e Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 14 Dec 2023 14:24:24 +0200 Subject: [PATCH 12/62] gh-112716: Fix SystemError when __builtins__ is not a dict (GH-112770) It was raised in two cases: * in the import statement when looking up __import__ * in pickling some builtin type when looking up built-ins iter, getattr, etc. --- Lib/test/test_builtin.py | 26 +++++++++++++++++++ ...-12-05-20-41-58.gh-issue-112716.hOcx0Y.rst | 2 ++ Python/ceval.c | 4 +-- 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-05-20-41-58.gh-issue-112716.hOcx0Y.rst diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 5e66d58fd2cb18..e15492783aeec1 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -837,6 +837,32 @@ class customdict(dict): # this one should not do anything fancy self.assertRaisesRegex(NameError, "name 'superglobal' is not defined", exec, code, {'__builtins__': customdict()}) + def test_eval_builtins_mapping(self): + code = compile("superglobal", "test", "eval") + # works correctly + ns = {'__builtins__': types.MappingProxyType({'superglobal': 1})} + self.assertEqual(eval(code, ns), 1) + # custom builtins mapping is missing key + ns = {'__builtins__': types.MappingProxyType({})} + self.assertRaisesRegex(NameError, "name 'superglobal' is not defined", + eval, code, ns) + + def test_exec_builtins_mapping_import(self): + code = compile("import foo.bar", "test", "exec") + ns = {'__builtins__': types.MappingProxyType({})} + self.assertRaisesRegex(ImportError, "__import__ not found", exec, code, ns) + ns = {'__builtins__': types.MappingProxyType({'__import__': lambda *args: args})} + exec(code, ns) + self.assertEqual(ns['foo'], ('foo.bar', ns, ns, None, 0)) + + def test_eval_builtins_mapping_reduce(self): + # list_iterator.__reduce__() calls _PyEval_GetBuiltin("iter") + code = compile("x.__reduce__()", "test", "eval") + ns = {'__builtins__': types.MappingProxyType({}), 'x': iter([1, 2])} + self.assertRaisesRegex(AttributeError, "iter", eval, code, ns) + ns = {'__builtins__': types.MappingProxyType({'iter': iter}), 'x': iter([1, 2])} + self.assertEqual(eval(code, ns), (iter, ([1, 2],), 0)) + def test_exec_redirected(self): savestdout = sys.stdout sys.stdout = None # Whatever that cannot flush() diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-05-20-41-58.gh-issue-112716.hOcx0Y.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-05-20-41-58.gh-issue-112716.hOcx0Y.rst new file mode 100644 index 00000000000000..44d63269c5424a --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-12-05-20-41-58.gh-issue-112716.hOcx0Y.rst @@ -0,0 +1,2 @@ +Fix SystemError in the ``import`` statement and in ``__reduce__()`` methods +of builtin types when ``__builtins__`` is not a dict. diff --git a/Python/ceval.c b/Python/ceval.c index d92ab926f84963..8e0be7056919ea 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -2417,7 +2417,7 @@ PyObject * _PyEval_GetBuiltin(PyObject *name) { PyObject *attr; - if (PyDict_GetItemRef(PyEval_GetBuiltins(), name, &attr) == 0) { + if (PyMapping_GetOptionalItem(PyEval_GetBuiltins(), name, &attr) == 0) { PyErr_SetObject(PyExc_AttributeError, name); } return attr; @@ -2570,7 +2570,7 @@ import_name(PyThreadState *tstate, _PyInterpreterFrame *frame, PyObject *name, PyObject *fromlist, PyObject *level) { PyObject *import_func; - if (PyDict_GetItemRef(frame->f_builtins, &_Py_ID(__import__), &import_func) < 0) { + if (PyMapping_GetOptionalItem(frame->f_builtins, &_Py_ID(__import__), &import_func) < 0) { return NULL; } if (import_func == NULL) { From 4b3cb082da82da744f5db0b7315aa80558c51557 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 14 Dec 2023 13:30:57 +0000 Subject: [PATCH 13/62] gh-101100: Fix Sphinx nitpicks in `library/inspect.rst` and `reference/simple_stmts.rst` (#113107) --- Doc/conf.py | 4 ++++ Doc/reference/simple_stmts.rst | 6 +++--- Doc/tools/.nitignore | 2 -- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index f2d36fdc70430c..0d7c0b553eaa74 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -245,6 +245,10 @@ # be resolved, as the method is currently undocumented. For context, see # https://github.com/python/cpython/pull/103289. ('py:meth', '_SubParsersAction.add_parser'), + # Attributes that definitely should be documented better, + # but are deferred for now: + ('py:attr', '__annotations__'), + ('py:attr', '__wrapped__'), ] # gh-106948: Copy standard C types declared in the "c:type" domain to the diff --git a/Doc/reference/simple_stmts.rst b/Doc/reference/simple_stmts.rst index 34c3a620b87223..04132c78ce77a6 100644 --- a/Doc/reference/simple_stmts.rst +++ b/Doc/reference/simple_stmts.rst @@ -214,7 +214,7 @@ Assignment of an object to a single target is recursively defined as follows. object. This can either replace an existing key/value pair with the same key value, or insert a new key/value pair (if no key with the same value existed). - For user-defined objects, the :meth:`__setitem__` method is called with + For user-defined objects, the :meth:`~object.__setitem__` method is called with appropriate arguments. .. index:: pair: slicing; assignment @@ -351,7 +351,7 @@ If the right hand side is present, an annotated assignment performs the actual assignment before evaluating annotations (where applicable). If the right hand side is not present for an expression target, then the interpreter evaluates the target except for the last -:meth:`__setitem__` or :meth:`__setattr__` call. +:meth:`~object.__setitem__` or :meth:`~object.__setattr__` call. .. seealso:: @@ -932,7 +932,7 @@ That is not a future statement; it's an ordinary import statement with no special semantics or syntax restrictions. Code compiled by calls to the built-in functions :func:`exec` and :func:`compile` -that occur in a module :mod:`M` containing a future statement will, by default, +that occur in a module :mod:`!M` containing a future statement will, by default, use the new syntax or semantics associated with the future statement. This can be controlled by optional arguments to :func:`compile` --- see the documentation of that function for details. diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 75d50fee33a064..c49fcf71dd3326 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -59,7 +59,6 @@ Doc/library/http.client.rst Doc/library/http.cookiejar.rst Doc/library/http.server.rst Doc/library/importlib.rst -Doc/library/inspect.rst Doc/library/locale.rst Doc/library/logging.config.rst Doc/library/logging.handlers.rst @@ -116,7 +115,6 @@ Doc/reference/compound_stmts.rst Doc/reference/datamodel.rst Doc/reference/expressions.rst Doc/reference/import.rst -Doc/reference/simple_stmts.rst Doc/tutorial/datastructures.rst Doc/using/windows.rst Doc/whatsnew/2.0.rst From d9e1b5794a8fade21773d18f91a07f80b55c839c Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 14 Dec 2023 14:10:35 +0000 Subject: [PATCH 14/62] gh-101100: Fix Sphinx nitpicks in `library/traceback.rst` (#113106) --- Doc/library/exceptions.rst | 2 +- Doc/library/traceback.rst | 110 +++++++++++++++++++++++++------------ Doc/tools/.nitignore | 1 - 3 files changed, 75 insertions(+), 38 deletions(-) diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index 04686b6db0036c..f7891f2732bdb1 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -44,7 +44,7 @@ Exception context __suppress_context__ (exception attribute) Three attributes on exception objects provide information about the context in -which an the exception was raised: +which the exception was raised: .. attribute:: BaseException.__context__ BaseException.__cause__ diff --git a/Doc/library/traceback.rst b/Doc/library/traceback.rst index dfbd04de243a3c..ab83e0df10b709 100644 --- a/Doc/library/traceback.rst +++ b/Doc/library/traceback.rst @@ -18,7 +18,8 @@ interpreter. The module uses :ref:`traceback objects ` --- these are objects of type :class:`types.TracebackType`, -which are assigned to the ``__traceback__`` field of :class:`BaseException` instances. +which are assigned to the :attr:`~BaseException.__traceback__` field of +:class:`BaseException` instances. .. seealso:: @@ -32,11 +33,13 @@ The module defines the following functions: .. function:: print_tb(tb, limit=None, file=None) - Print up to *limit* stack trace entries from traceback object *tb* (starting + Print up to *limit* stack trace entries from + :ref:`traceback object ` *tb* (starting from the caller's frame) if *limit* is positive. Otherwise, print the last ``abs(limit)`` entries. If *limit* is omitted or ``None``, all entries are printed. If *file* is omitted or ``None``, the output goes to - ``sys.stderr``; otherwise it should be an open file or file-like object to + :data:`sys.stderr`; otherwise it should be an open + :term:`file ` or :term:`file-like object` to receive the output. .. versionchanged:: 3.5 @@ -46,7 +49,8 @@ The module defines the following functions: .. function:: print_exception(exc, /[, value, tb], limit=None, \ file=None, chain=True) - Print exception information and stack trace entries from traceback object + Print exception information and stack trace entries from + :ref:`traceback object ` *tb* to *file*. This differs from :func:`print_tb` in the following ways: @@ -98,7 +102,8 @@ The module defines the following functions: Print up to *limit* stack trace entries (starting from the invocation point) if *limit* is positive. Otherwise, print the last ``abs(limit)`` entries. If *limit* is omitted or ``None``, all entries are printed. - The optional *f* argument can be used to specify an alternate stack frame + The optional *f* argument can be used to specify an alternate + :ref:`stack frame ` to start. The optional *file* argument has the same meaning as for :func:`print_tb`. @@ -109,20 +114,20 @@ The module defines the following functions: .. function:: extract_tb(tb, limit=None) Return a :class:`StackSummary` object representing a list of "pre-processed" - stack trace entries extracted from the traceback object *tb*. It is useful + stack trace entries extracted from the + :ref:`traceback object ` *tb*. It is useful for alternate formatting of stack traces. The optional *limit* argument has the same meaning as for :func:`print_tb`. A "pre-processed" stack trace entry is a :class:`FrameSummary` object containing attributes :attr:`~FrameSummary.filename`, :attr:`~FrameSummary.lineno`, :attr:`~FrameSummary.name`, and :attr:`~FrameSummary.line` representing the - information that is usually printed for a stack trace. The - :attr:`~FrameSummary.line` is a string with leading and trailing - whitespace stripped; if the source is not available it is ``None``. + information that is usually printed for a stack trace. .. function:: extract_stack(f=None, limit=None) - Extract the raw traceback from the current stack frame. The return value has + Extract the raw traceback from the current + :ref:`stack frame `. The return value has the same format as for :func:`extract_tb`. The optional *f* and *limit* arguments have the same meaning as for :func:`print_stack`. @@ -140,7 +145,7 @@ The module defines the following functions: .. function:: format_exception_only(exc, /[, value], *, show_group=False) Format the exception part of a traceback using an exception value such as - given by ``sys.last_value``. The return value is a list of strings, each + given by :data:`sys.last_value`. The return value is a list of strings, each ending in a newline. The list contains the exception's message, which is normally a single string; however, for :exc:`SyntaxError` exceptions, it contains several lines that (when printed) display detailed information @@ -160,7 +165,8 @@ The module defines the following functions: positional-only. .. versionchanged:: 3.11 - The returned list now includes any notes attached to the exception. + The returned list now includes any + :attr:`notes ` attached to the exception. .. versionchanged:: 3.13 *show_group* parameter was added. @@ -199,14 +205,17 @@ The module defines the following functions: .. function:: clear_frames(tb) - Clears the local variables of all the stack frames in a traceback *tb* - by calling the :meth:`clear` method of each frame object. + Clears the local variables of all the stack frames in a + :ref:`traceback ` *tb* + by calling the :meth:`~frame.clear` method of each + :ref:`frame object `. .. versionadded:: 3.4 .. function:: walk_stack(f) - Walk a stack following ``f.f_back`` from the given frame, yielding the frame + Walk a stack following :attr:`f.f_back ` from the given frame, + yielding the frame and line number for each frame. If *f* is ``None``, the current stack is used. This helper is used with :meth:`StackSummary.extract`. @@ -222,12 +231,12 @@ The module defines the following functions: The module also defines the following classes: -:class:`TracebackException` Objects ------------------------------------ +:class:`!TracebackException` Objects +------------------------------------ .. versionadded:: 3.5 -:class:`TracebackException` objects are created from actual exceptions to +:class:`!TracebackException` objects are created from actual exceptions to capture data for later printing in a lightweight fashion. .. class:: TracebackException(exc_type, exc_value, exc_traceback, *, limit=None, lookup_lines=True, capture_locals=False, compact=False, max_group_width=15, max_group_depth=10) @@ -379,33 +388,34 @@ capture data for later printing in a lightweight fashion. well, recursively, with indentation relative to their nesting depth. .. versionchanged:: 3.11 - The exception's notes are now included in the output. + The exception's :attr:`notes ` are now + included in the output. .. versionchanged:: 3.13 Added the *show_group* parameter. -:class:`StackSummary` Objects ------------------------------ +:class:`!StackSummary` Objects +------------------------------ .. versionadded:: 3.5 -:class:`StackSummary` objects represent a call stack ready for formatting. +:class:`!StackSummary` objects represent a call stack ready for formatting. .. class:: StackSummary .. classmethod:: extract(frame_gen, *, limit=None, lookup_lines=True, capture_locals=False) - Construct a :class:`StackSummary` object from a frame generator (such as + Construct a :class:`!StackSummary` object from a frame generator (such as is returned by :func:`~traceback.walk_stack` or :func:`~traceback.walk_tb`). If *limit* is supplied, only this many frames are taken from *frame_gen*. If *lookup_lines* is ``False``, the returned :class:`FrameSummary` objects will not have read their lines in yet, making the cost of - creating the :class:`StackSummary` cheaper (which may be valuable if it + creating the :class:`!StackSummary` cheaper (which may be valuable if it may not actually get formatted). If *capture_locals* is ``True`` the - local variables in each :class:`FrameSummary` are captured as object + local variables in each :class:`!FrameSummary` are captured as object representations. .. versionchanged:: 3.12 @@ -414,14 +424,16 @@ capture data for later printing in a lightweight fashion. .. classmethod:: from_list(a_list) - Construct a :class:`StackSummary` object from a supplied list of + Construct a :class:`!StackSummary` object from a supplied list of :class:`FrameSummary` objects or old-style list of tuples. Each tuple - should be a 4-tuple with filename, lineno, name, line as the elements. + should be a 4-tuple with *filename*, *lineno*, *name*, *line* as the + elements. .. method:: format() Returns a list of strings ready for printing. Each string in the - resulting list corresponds to a single frame from the stack. + resulting list corresponds to a single :ref:`frame ` from + the stack. Each string ends in a newline; the strings may contain internal newlines as well, for those items with source text lines. @@ -434,7 +446,8 @@ capture data for later printing in a lightweight fashion. .. method:: format_frame_summary(frame_summary) - Returns a string for printing one of the frames involved in the stack. + Returns a string for printing one of the :ref:`frames ` + involved in the stack. This method is called for each :class:`FrameSummary` object to be printed by :meth:`StackSummary.format`. If it returns ``None``, the frame is omitted from the output. @@ -442,25 +455,50 @@ capture data for later printing in a lightweight fashion. .. versionadded:: 3.11 -:class:`FrameSummary` Objects ------------------------------ +:class:`!FrameSummary` Objects +------------------------------ .. versionadded:: 3.5 -A :class:`FrameSummary` object represents a single frame in a traceback. +A :class:`!FrameSummary` object represents a single :ref:`frame ` +in a :ref:`traceback `. .. class:: FrameSummary(filename, lineno, name, lookup_line=True, locals=None, line=None) - Represent a single frame in the traceback or stack that is being formatted - or printed. It may optionally have a stringified version of the frames + Represents a single :ref:`frame ` in the + :ref:`traceback ` or stack that is being formatted + or printed. It may optionally have a stringified version of the frame's locals included in it. If *lookup_line* is ``False``, the source code is not - looked up until the :class:`FrameSummary` has the :attr:`~FrameSummary.line` - attribute accessed (which also happens when casting it to a tuple). + looked up until the :class:`!FrameSummary` has the :attr:`~FrameSummary.line` + attribute accessed (which also happens when casting it to a :class:`tuple`). :attr:`~FrameSummary.line` may be directly provided, and will prevent line lookups happening at all. *locals* is an optional local variable dictionary, and if supplied the variable representations are stored in the summary for later display. + :class:`!FrameSummary` instances have the following attributes: + + .. attribute:: FrameSummary.filename + + The filename of the source code for this frame. Equivalent to accessing + :attr:`f.f_code.co_filename ` on a + :ref:`frame object ` *f*. + + .. attribute:: FrameSummary.lineno + + The line number of the source code for this frame. + + .. attribute:: FrameSummary.name + + Equivalent to accessing :attr:`f.f_code.co_name ` on + a :ref:`frame object ` *f*. + + .. attribute:: FrameSummary.line + + A string representing the source code for this frame, with leading and + trailing whitespace stripped. + If the source is not available, it is ``None``. + .. _traceback-example: Traceback Examples diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index c49fcf71dd3326..4d1d31d44fcf75 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -96,7 +96,6 @@ Doc/library/test.rst Doc/library/tkinter.rst Doc/library/tkinter.scrolledtext.rst Doc/library/tkinter.ttk.rst -Doc/library/traceback.rst Doc/library/unittest.mock.rst Doc/library/unittest.rst Doc/library/urllib.parse.rst From 6873555955497e9adbc72fc0e38df5261844aafd Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 14 Dec 2023 14:26:44 +0000 Subject: [PATCH 15/62] GH-112354: Treat _EXIT_TRACE like an unconditional side exit (GH-113104) --- Include/internal/pycore_opcode_metadata.h | 2 +- Python/bytecodes.c | 2 +- Python/ceval.c | 19 ++----------------- Python/ceval_macros.h | 2 -- Python/executor_cases.c.h | 2 +- 5 files changed, 5 insertions(+), 22 deletions(-) diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 2c512d97c421c9..4670c34a833963 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1689,7 +1689,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[OPCODE_METADATA_SIZE] = { [_JUMP_TO_TOP] = { true, INSTR_FMT_IX, HAS_EVAL_BREAK_FLAG }, [_SET_IP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, [_SAVE_RETURN_OFFSET] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, - [_EXIT_TRACE] = { true, INSTR_FMT_IX, 0 }, + [_EXIT_TRACE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, [_INSERT] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, [_CHECK_VALIDITY] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, }; diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 1ae83422730f8f..68bb15c2b536eb 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -4027,7 +4027,7 @@ dummy_func( op(_EXIT_TRACE, (--)) { TIER_TWO_ONLY - GOTO_TIER_ONE(); + DEOPT_IF(1); } op(_INSERT, (unused[oparg], top -- top, unused[oparg])) { diff --git a/Python/ceval.c b/Python/ceval.c index 8e0be7056919ea..27304d31e27949 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1063,31 +1063,16 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int // Jump here from DEOPT_IF() deoptimize: - // On DEOPT_IF we just repeat the last instruction. - // This presumes nothing was popped from the stack (nor pushed). - frame->instr_ptr = next_uop[-1].target + _PyCode_CODE(_PyFrame_GetCode(frame)); + next_instr = next_uop[-1].target + _PyCode_CODE(_PyFrame_GetCode(frame)); DPRINTF(2, "DEOPT: [UOp %d (%s), oparg %d, operand %" PRIu64 ", target %d @ %d -> %s]\n", uopcode, _PyUOpName(uopcode), next_uop[-1].oparg, next_uop[-1].operand, next_uop[-1].target, (int)(next_uop - current_executor->trace - 1), _PyOpcode_OpName[frame->instr_ptr->op.code]); OPT_HIST(trace_uop_execution_counter, trace_run_length_hist); UOP_STAT_INC(uopcode, miss); - frame->return_offset = 0; // Dispatch to frame->instr_ptr - _PyFrame_SetStackPointer(frame, stack_pointer); Py_DECREF(current_executor); - // Fall through -// Jump here from ENTER_EXECUTOR -enter_tier_one: - next_instr = frame->instr_ptr; - goto resume_frame; + DISPATCH(); -// Jump here from _EXIT_TRACE -exit_trace: - _PyFrame_SetStackPointer(frame, stack_pointer); - frame->instr_ptr = next_uop[-1].target + _PyCode_CODE(_PyFrame_GetCode(frame)); - Py_DECREF(current_executor); - OPT_HIST(trace_uop_execution_counter, trace_run_length_hist); - goto enter_tier_one; } #if defined(__GNUC__) # pragma GCC diagnostic pop diff --git a/Python/ceval_macros.h b/Python/ceval_macros.h index ac44aecae046d8..a3606b17b71c62 100644 --- a/Python/ceval_macros.h +++ b/Python/ceval_macros.h @@ -392,8 +392,6 @@ stack_pointer = _PyFrame_GetStackPointer(frame); #define GOTO_TIER_TWO() goto enter_tier_two; -#define GOTO_TIER_ONE() goto exit_trace; - #define CURRENT_OPARG() (next_uop[-1].oparg) #define CURRENT_OPERAND() (next_uop[-1].operand) diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 14d9dd6e95e533..2519a4ee546a5e 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -3516,7 +3516,7 @@ case _EXIT_TRACE: { TIER_TWO_ONLY - GOTO_TIER_ONE(); + if (1) goto deoptimize; break; } From fd81afc624402816e3455fd8e932ac7702e4d988 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Thu, 14 Dec 2023 15:16:39 +0000 Subject: [PATCH 16/62] gh-86179: Avoid making case-only changes when calculating realpath() during initialization (GH-113077) --- Modules/getpath.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Modules/getpath.c b/Modules/getpath.c index 422056b1fb6de4..afa9273ebc59b7 100644 --- a/Modules/getpath.c +++ b/Modules/getpath.c @@ -506,12 +506,17 @@ getpath_realpath(PyObject *Py_UNUSED(self) , PyObject *args) HANDLE hFile; wchar_t resolved[MAXPATHLEN+1]; int len = 0, err; + Py_ssize_t pathlen; PyObject *result; - wchar_t *path = PyUnicode_AsWideCharString(pathobj, NULL); + wchar_t *path = PyUnicode_AsWideCharString(pathobj, &pathlen); if (!path) { return NULL; } + if (wcslen(path) != pathlen) { + PyErr_SetString(PyExc_ValueError, "path contains embedded nulls"); + return NULL; + } Py_BEGIN_ALLOW_THREADS hFile = CreateFileW(path, 0, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); @@ -535,7 +540,11 @@ getpath_realpath(PyObject *Py_UNUSED(self) , PyObject *args) len -= 4; } } - result = PyUnicode_FromWideChar(p, len); + if (CompareStringOrdinal(path, (int)pathlen, p, len, TRUE) == CSTR_EQUAL) { + result = Py_NewRef(pathobj); + } else { + result = PyUnicode_FromWideChar(p, len); + } } else { result = Py_NewRef(pathobj); } From fb4cb7ce310e28e25d3adf33b29cb97c637097ed Mon Sep 17 00:00:00 2001 From: jeremy-dolan <129558107+jeremy-dolan@users.noreply.github.com> Date: Thu, 14 Dec 2023 10:40:24 -0500 Subject: [PATCH 17/62] gh-113113: doc: use less ambiguously named variable (gh-113114) --- Doc/tutorial/controlflow.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/tutorial/controlflow.rst b/Doc/tutorial/controlflow.rst index aa9caa101da40a..77444f9cb8358d 100644 --- a/Doc/tutorial/controlflow.rst +++ b/Doc/tutorial/controlflow.rst @@ -559,10 +559,10 @@ defined to allow. For example:: def ask_ok(prompt, retries=4, reminder='Please try again!'): while True: - ok = input(prompt) - if ok in ('y', 'ye', 'yes'): + reply = input(prompt) + if reply in {'y', 'ye', 'yes'}: return True - if ok in ('n', 'no', 'nop', 'nope'): + if reply in {'n', 'no', 'nop', 'nope'}: return False retries = retries - 1 if retries < 0: From e24eccbc1cf5f22743cd5cda733cd04891155d54 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 14 Dec 2023 16:41:52 +0000 Subject: [PATCH 18/62] GH-111485: Sort metadata tables for easier checking of future diffs (GH-113101) --- Include/internal/pycore_opcode_metadata.h | 3042 ++++++++++----------- Tools/cases_generator/generate_cases.py | 14 +- 2 files changed, 1529 insertions(+), 1527 deletions(-) diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 4670c34a833963..36b6cd52d2b272 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -31,636 +31,636 @@ extern int _PyOpcode_num_popped(int opcode, int oparg, bool jump); #ifdef NEED_OPCODE_METADATA int _PyOpcode_num_popped(int opcode, int oparg, bool jump) { switch(opcode) { - case NOP: - return 0; - case RESUME: - return 0; - case RESUME_CHECK: - return 0; - case INSTRUMENTED_RESUME: - return 0; - case LOAD_CLOSURE: - return 0; - case LOAD_FAST_CHECK: - return 0; - case LOAD_FAST: - return 0; - case LOAD_FAST_AND_CLEAR: - return 0; - case LOAD_FAST_LOAD_FAST: - return 0; - case LOAD_CONST: - return 0; - case STORE_FAST: - return 1; - case STORE_FAST_MAYBE_NULL: - return 1; - case STORE_FAST_LOAD_FAST: + case BEFORE_ASYNC_WITH: return 1; - case STORE_FAST_STORE_FAST: - return 2; - case POP_TOP: + case BEFORE_WITH: return 1; - case PUSH_NULL: - return 0; - case END_FOR: - return 2; - case INSTRUMENTED_END_FOR: - return 2; - case END_SEND: + case BINARY_OP: return 2; - case INSTRUMENTED_END_SEND: + case BINARY_OP_ADD_FLOAT: return 2; - case UNARY_NEGATIVE: - return 1; - case UNARY_NOT: - return 1; - case _SPECIALIZE_TO_BOOL: - return 1; - case _TO_BOOL: - return 1; - case TO_BOOL: - return 1; - case TO_BOOL_BOOL: - return 1; - case TO_BOOL_INT: - return 1; - case TO_BOOL_LIST: - return 1; - case TO_BOOL_NONE: - return 1; - case TO_BOOL_STR: - return 1; - case TO_BOOL_ALWAYS_TRUE: - return 1; - case UNARY_INVERT: - return 1; - case _GUARD_BOTH_INT: + case BINARY_OP_ADD_INT: return 2; - case _BINARY_OP_MULTIPLY_INT: + case BINARY_OP_ADD_UNICODE: return 2; - case _BINARY_OP_ADD_INT: + case BINARY_OP_INPLACE_ADD_UNICODE: return 2; - case _BINARY_OP_SUBTRACT_INT: + case BINARY_OP_MULTIPLY_FLOAT: return 2; case BINARY_OP_MULTIPLY_INT: return 2; - case BINARY_OP_ADD_INT: + case BINARY_OP_SUBTRACT_FLOAT: return 2; case BINARY_OP_SUBTRACT_INT: return 2; - case _GUARD_BOTH_FLOAT: - return 2; - case _BINARY_OP_MULTIPLY_FLOAT: - return 2; - case _BINARY_OP_ADD_FLOAT: - return 2; - case _BINARY_OP_SUBTRACT_FLOAT: - return 2; - case BINARY_OP_MULTIPLY_FLOAT: + case BINARY_SLICE: + return 3; + case BINARY_SUBSCR: return 2; - case BINARY_OP_ADD_FLOAT: + case BINARY_SUBSCR_DICT: return 2; - case BINARY_OP_SUBTRACT_FLOAT: + case BINARY_SUBSCR_GETITEM: return 2; - case _GUARD_BOTH_UNICODE: + case BINARY_SUBSCR_LIST_INT: return 2; - case _BINARY_OP_ADD_UNICODE: + case BINARY_SUBSCR_STR_INT: return 2; - case BINARY_OP_ADD_UNICODE: + case BINARY_SUBSCR_TUPLE_INT: return 2; - case _BINARY_OP_INPLACE_ADD_UNICODE: + case BUILD_CONST_KEY_MAP: + return oparg + 1; + case BUILD_LIST: + return oparg; + case BUILD_MAP: + return oparg*2; + case BUILD_SET: + return oparg; + case BUILD_SLICE: + return ((oparg == 3) ? 1 : 0) + 2; + case BUILD_STRING: + return oparg; + case BUILD_TUPLE: + return oparg; + case CACHE: + return 0; + case CALL: + return oparg + 2; + case CALL_ALLOC_AND_ENTER_INIT: + return oparg + 2; + case CALL_BOUND_METHOD_EXACT_ARGS: + return oparg + 2; + case CALL_BUILTIN_CLASS: + return oparg + 2; + case CALL_BUILTIN_FAST: + return oparg + 2; + case CALL_BUILTIN_FAST_WITH_KEYWORDS: + return oparg + 2; + case CALL_BUILTIN_O: + return oparg + 2; + case CALL_FUNCTION_EX: + return ((oparg & 1) ? 1 : 0) + 3; + case CALL_INTRINSIC_1: + return 1; + case CALL_INTRINSIC_2: return 2; - case BINARY_OP_INPLACE_ADD_UNICODE: + case CALL_ISINSTANCE: + return oparg + 2; + case CALL_KW: + return oparg + 3; + case CALL_LEN: + return oparg + 2; + case CALL_LIST_APPEND: + return oparg + 2; + case CALL_METHOD_DESCRIPTOR_FAST: + return oparg + 2; + case CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS: + return oparg + 2; + case CALL_METHOD_DESCRIPTOR_NOARGS: + return oparg + 2; + case CALL_METHOD_DESCRIPTOR_O: + return oparg + 2; + case CALL_PY_EXACT_ARGS: + return oparg + 2; + case CALL_PY_WITH_DEFAULTS: + return oparg + 2; + case CALL_STR_1: + return oparg + 2; + case CALL_TUPLE_1: + return oparg + 2; + case CALL_TYPE_1: + return oparg + 2; + case CHECK_EG_MATCH: return 2; - case _SPECIALIZE_BINARY_SUBSCR: + case CHECK_EXC_MATCH: return 2; - case _BINARY_SUBSCR: + case CLEANUP_THROW: + return 3; + case COMPARE_OP: return 2; - case BINARY_SUBSCR: + case COMPARE_OP_FLOAT: return 2; - case BINARY_SLICE: - return 3; - case STORE_SLICE: - return 4; - case BINARY_SUBSCR_LIST_INT: + case COMPARE_OP_INT: return 2; - case BINARY_SUBSCR_STR_INT: + case COMPARE_OP_STR: return 2; - case BINARY_SUBSCR_TUPLE_INT: + case CONTAINS_OP: return 2; - case BINARY_SUBSCR_DICT: + case CONVERT_VALUE: + return 1; + case COPY: + return (oparg-1) + 1; + case COPY_FREE_VARS: + return 0; + case DELETE_ATTR: + return 1; + case DELETE_DEREF: + return 0; + case DELETE_FAST: + return 0; + case DELETE_GLOBAL: + return 0; + case DELETE_NAME: + return 0; + case DELETE_SUBSCR: return 2; - case BINARY_SUBSCR_GETITEM: + case DICT_MERGE: + return (oparg - 1) + 5; + case DICT_UPDATE: + return (oparg - 1) + 2; + case END_ASYNC_FOR: return 2; - case LIST_APPEND: - return (oparg-1) + 2; - case SET_ADD: - return (oparg-1) + 2; - case _SPECIALIZE_STORE_SUBSCR: + case END_FOR: return 2; - case _STORE_SUBSCR: - return 3; - case STORE_SUBSCR: - return 3; - case STORE_SUBSCR_LIST_INT: - return 3; - case STORE_SUBSCR_DICT: - return 3; - case DELETE_SUBSCR: + case END_SEND: return 2; - case CALL_INTRINSIC_1: + case ENTER_EXECUTOR: + return 0; + case EXIT_INIT_CHECK: return 1; - case CALL_INTRINSIC_2: + case EXTENDED_ARG: + return 0; + case FORMAT_SIMPLE: + return 1; + case FORMAT_WITH_SPEC: return 2; - case RAISE_VARARGS: - return oparg; - case INTERPRETER_EXIT: + case FOR_ITER: return 1; - case _POP_FRAME: + case FOR_ITER_GEN: return 1; - case RETURN_VALUE: + case FOR_ITER_LIST: return 1; - case INSTRUMENTED_RETURN_VALUE: + case FOR_ITER_RANGE: + return 1; + case FOR_ITER_TUPLE: return 1; - case RETURN_CONST: - return 0; - case INSTRUMENTED_RETURN_CONST: - return 0; case GET_AITER: return 1; case GET_ANEXT: return 1; case GET_AWAITABLE: return 1; - case _SPECIALIZE_SEND: - return 2; - case _SEND: - return 2; - case SEND: - return 2; - case SEND_GEN: - return 2; - case INSTRUMENTED_YIELD_VALUE: + case GET_ITER: return 1; - case YIELD_VALUE: + case GET_LEN: return 1; - case POP_EXCEPT: + case GET_YIELD_FROM_ITER: return 1; - case RERAISE: - return oparg + 1; - case END_ASYNC_FOR: + case IMPORT_FROM: + return 1; + case IMPORT_NAME: return 2; - case CLEANUP_THROW: + case INSTRUMENTED_CALL: + return 0; + case INSTRUMENTED_CALL_FUNCTION_EX: + return 0; + case INSTRUMENTED_CALL_KW: + return 0; + case INSTRUMENTED_END_FOR: + return 2; + case INSTRUMENTED_END_SEND: + return 2; + case INSTRUMENTED_FOR_ITER: + return 0; + case INSTRUMENTED_INSTRUCTION: + return 0; + case INSTRUMENTED_JUMP_BACKWARD: + return 0; + case INSTRUMENTED_JUMP_FORWARD: + return 0; + case INSTRUMENTED_LOAD_SUPER_ATTR: return 3; - case LOAD_ASSERTION_ERROR: + case INSTRUMENTED_POP_JUMP_IF_FALSE: return 0; - case LOAD_BUILD_CLASS: + case INSTRUMENTED_POP_JUMP_IF_NONE: return 0; - case STORE_NAME: + case INSTRUMENTED_POP_JUMP_IF_NOT_NONE: + return 0; + case INSTRUMENTED_POP_JUMP_IF_TRUE: + return 0; + case INSTRUMENTED_RESUME: + return 0; + case INSTRUMENTED_RETURN_CONST: + return 0; + case INSTRUMENTED_RETURN_VALUE: return 1; - case DELETE_NAME: + case INSTRUMENTED_YIELD_VALUE: + return 1; + case INTERPRETER_EXIT: + return 1; + case IS_OP: + return 2; + case JUMP: return 0; - case _SPECIALIZE_UNPACK_SEQUENCE: + case JUMP_BACKWARD: + return 0; + case JUMP_BACKWARD_NO_INTERRUPT: + return 0; + case JUMP_FORWARD: + return 0; + case JUMP_NO_INTERRUPT: + return 0; + case LIST_APPEND: + return (oparg-1) + 2; + case LIST_EXTEND: + return (oparg-1) + 2; + case LOAD_ASSERTION_ERROR: + return 0; + case LOAD_ATTR: return 1; - case _UNPACK_SEQUENCE: + case LOAD_ATTR_CLASS: return 1; - case UNPACK_SEQUENCE: + case LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN: return 1; - case UNPACK_SEQUENCE_TWO_TUPLE: + case LOAD_ATTR_INSTANCE_VALUE: return 1; - case UNPACK_SEQUENCE_TUPLE: + case LOAD_ATTR_METHOD_LAZY_DICT: return 1; - case UNPACK_SEQUENCE_LIST: + case LOAD_ATTR_METHOD_NO_DICT: return 1; - case UNPACK_EX: + case LOAD_ATTR_METHOD_WITH_VALUES: return 1; - case _SPECIALIZE_STORE_ATTR: + case LOAD_ATTR_MODULE: return 1; - case _STORE_ATTR: - return 2; - case STORE_ATTR: - return 2; - case DELETE_ATTR: + case LOAD_ATTR_NONDESCRIPTOR_NO_DICT: return 1; - case STORE_GLOBAL: + case LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: return 1; - case DELETE_GLOBAL: - return 0; - case LOAD_LOCALS: - return 0; - case LOAD_FROM_DICT_OR_GLOBALS: + case LOAD_ATTR_PROPERTY: return 1; - case LOAD_NAME: + case LOAD_ATTR_SLOT: + return 1; + case LOAD_ATTR_WITH_HINT: + return 1; + case LOAD_BUILD_CLASS: return 0; - case _SPECIALIZE_LOAD_GLOBAL: + case LOAD_CLOSURE: return 0; - case _LOAD_GLOBAL: + case LOAD_CONST: return 0; - case LOAD_GLOBAL: + case LOAD_DEREF: return 0; - case _GUARD_GLOBALS_VERSION: + case LOAD_FAST: return 0; - case _GUARD_BUILTINS_VERSION: + case LOAD_FAST_AND_CLEAR: return 0; - case _LOAD_GLOBAL_MODULE: + case LOAD_FAST_CHECK: return 0; - case _LOAD_GLOBAL_BUILTINS: + case LOAD_FAST_LOAD_FAST: return 0; - case LOAD_GLOBAL_MODULE: + case LOAD_FROM_DICT_OR_DEREF: + return 1; + case LOAD_FROM_DICT_OR_GLOBALS: + return 1; + case LOAD_GLOBAL: return 0; case LOAD_GLOBAL_BUILTIN: return 0; - case DELETE_FAST: - return 0; - case MAKE_CELL: - return 0; - case DELETE_DEREF: + case LOAD_GLOBAL_MODULE: return 0; - case LOAD_FROM_DICT_OR_DEREF: - return 1; - case LOAD_DEREF: + case LOAD_LOCALS: return 0; - case STORE_DEREF: + case LOAD_METHOD: return 1; - case COPY_FREE_VARS: - return 0; - case BUILD_STRING: - return oparg; - case BUILD_TUPLE: - return oparg; - case BUILD_LIST: - return oparg; - case LIST_EXTEND: - return (oparg-1) + 2; - case SET_UPDATE: - return (oparg-1) + 2; - case BUILD_SET: - return oparg; - case BUILD_MAP: - return oparg*2; - case SETUP_ANNOTATIONS: + case LOAD_NAME: return 0; - case BUILD_CONST_KEY_MAP: - return oparg + 1; - case DICT_UPDATE: - return (oparg - 1) + 2; - case DICT_MERGE: - return (oparg - 1) + 5; - case MAP_ADD: - return (oparg - 1) + 3; - case INSTRUMENTED_LOAD_SUPER_ATTR: - return 3; - case _SPECIALIZE_LOAD_SUPER_ATTR: + case LOAD_SUPER_ATTR: return 3; - case _LOAD_SUPER_ATTR: + case LOAD_SUPER_ATTR_ATTR: return 3; - case LOAD_SUPER_ATTR: + case LOAD_SUPER_ATTR_METHOD: return 3; case LOAD_SUPER_METHOD: return 3; - case LOAD_ZERO_SUPER_METHOD: - return 3; case LOAD_ZERO_SUPER_ATTR: return 3; - case LOAD_SUPER_ATTR_ATTR: - return 3; - case LOAD_SUPER_ATTR_METHOD: + case LOAD_ZERO_SUPER_METHOD: return 3; - case _SPECIALIZE_LOAD_ATTR: + case MAKE_CELL: + return 0; + case MAKE_FUNCTION: return 1; - case _LOAD_ATTR: + case MAP_ADD: + return (oparg - 1) + 3; + case MATCH_CLASS: + return 3; + case MATCH_KEYS: + return 2; + case MATCH_MAPPING: return 1; - case LOAD_ATTR: + case MATCH_SEQUENCE: return 1; - case LOAD_METHOD: + case NOP: + return 0; + case POP_BLOCK: + return 0; + case POP_EXCEPT: return 1; - case _GUARD_TYPE_VERSION: + case POP_JUMP_IF_FALSE: return 1; - case _CHECK_MANAGED_OBJECT_HAS_VALUES: + case POP_JUMP_IF_NONE: return 1; - case _LOAD_ATTR_INSTANCE_VALUE: + case POP_JUMP_IF_NOT_NONE: return 1; - case LOAD_ATTR_INSTANCE_VALUE: + case POP_JUMP_IF_TRUE: return 1; - case _CHECK_ATTR_MODULE: + case POP_TOP: return 1; - case _LOAD_ATTR_MODULE: + case PUSH_EXC_INFO: return 1; - case LOAD_ATTR_MODULE: + case PUSH_NULL: + return 0; + case RAISE_VARARGS: + return oparg; + case RERAISE: + return oparg + 1; + case RESERVED: + return 0; + case RESUME: + return 0; + case RESUME_CHECK: + return 0; + case RETURN_CONST: + return 0; + case RETURN_GENERATOR: + return 0; + case RETURN_VALUE: return 1; - case _CHECK_ATTR_WITH_HINT: + case SEND: + return 2; + case SEND_GEN: + return 2; + case SETUP_ANNOTATIONS: + return 0; + case SETUP_CLEANUP: + return 0; + case SETUP_FINALLY: + return 0; + case SETUP_WITH: + return 0; + case SET_ADD: + return (oparg-1) + 2; + case SET_FUNCTION_ATTRIBUTE: + return 2; + case SET_UPDATE: + return (oparg-1) + 2; + case STORE_ATTR: + return 2; + case STORE_ATTR_INSTANCE_VALUE: + return 2; + case STORE_ATTR_SLOT: + return 2; + case STORE_ATTR_WITH_HINT: + return 2; + case STORE_DEREF: return 1; - case _LOAD_ATTR_WITH_HINT: + case STORE_FAST: return 1; - case LOAD_ATTR_WITH_HINT: + case STORE_FAST_LOAD_FAST: return 1; - case _LOAD_ATTR_SLOT: + case STORE_FAST_MAYBE_NULL: return 1; - case LOAD_ATTR_SLOT: + case STORE_FAST_STORE_FAST: + return 2; + case STORE_GLOBAL: return 1; - case _CHECK_ATTR_CLASS: + case STORE_NAME: return 1; - case _LOAD_ATTR_CLASS: + case STORE_SLICE: + return 4; + case STORE_SUBSCR: + return 3; + case STORE_SUBSCR_DICT: + return 3; + case STORE_SUBSCR_LIST_INT: + return 3; + case SWAP: + return (oparg-2) + 2; + case TO_BOOL: return 1; - case LOAD_ATTR_CLASS: + case TO_BOOL_ALWAYS_TRUE: return 1; - case LOAD_ATTR_PROPERTY: + case TO_BOOL_BOOL: return 1; - case LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN: + case TO_BOOL_INT: return 1; - case _GUARD_DORV_VALUES: + case TO_BOOL_LIST: return 1; - case _STORE_ATTR_INSTANCE_VALUE: - return 2; - case STORE_ATTR_INSTANCE_VALUE: - return 2; - case STORE_ATTR_WITH_HINT: - return 2; - case _STORE_ATTR_SLOT: - return 2; - case STORE_ATTR_SLOT: - return 2; - case _SPECIALIZE_COMPARE_OP: - return 2; - case _COMPARE_OP: + case TO_BOOL_NONE: + return 1; + case TO_BOOL_STR: + return 1; + case UNARY_INVERT: + return 1; + case UNARY_NEGATIVE: + return 1; + case UNARY_NOT: + return 1; + case UNPACK_EX: + return 1; + case UNPACK_SEQUENCE: + return 1; + case UNPACK_SEQUENCE_LIST: + return 1; + case UNPACK_SEQUENCE_TUPLE: + return 1; + case UNPACK_SEQUENCE_TWO_TUPLE: + return 1; + case WITH_EXCEPT_START: + return 4; + case YIELD_VALUE: + return 1; + case _BINARY_OP: return 2; - case COMPARE_OP: + case _BINARY_OP_ADD_FLOAT: return 2; - case COMPARE_OP_FLOAT: + case _BINARY_OP_ADD_INT: return 2; - case COMPARE_OP_INT: + case _BINARY_OP_ADD_UNICODE: return 2; - case COMPARE_OP_STR: + case _BINARY_OP_INPLACE_ADD_UNICODE: return 2; - case IS_OP: + case _BINARY_OP_MULTIPLY_FLOAT: return 2; - case CONTAINS_OP: + case _BINARY_OP_MULTIPLY_INT: return 2; - case CHECK_EG_MATCH: + case _BINARY_OP_SUBTRACT_FLOAT: return 2; - case CHECK_EXC_MATCH: + case _BINARY_OP_SUBTRACT_INT: return 2; - case IMPORT_NAME: + case _BINARY_SUBSCR: return 2; - case IMPORT_FROM: - return 1; - case JUMP_FORWARD: - return 0; - case JUMP_BACKWARD: - return 0; - case JUMP: - return 0; - case JUMP_NO_INTERRUPT: - return 0; - case ENTER_EXECUTOR: - return 0; - case _POP_JUMP_IF_FALSE: - return 1; - case _POP_JUMP_IF_TRUE: - return 1; - case _IS_NONE: + case _CALL: + return oparg + 2; + case _CHECK_ATTR_CLASS: return 1; - case POP_JUMP_IF_TRUE: + case _CHECK_ATTR_METHOD_LAZY_DICT: return 1; - case POP_JUMP_IF_FALSE: + case _CHECK_ATTR_MODULE: return 1; - case POP_JUMP_IF_NONE: + case _CHECK_ATTR_WITH_HINT: return 1; - case POP_JUMP_IF_NOT_NONE: + case _CHECK_CALL_BOUND_METHOD_EXACT_ARGS: + return oparg + 2; + case _CHECK_FUNCTION_EXACT_ARGS: + return oparg + 2; + case _CHECK_MANAGED_OBJECT_HAS_VALUES: return 1; - case JUMP_BACKWARD_NO_INTERRUPT: + case _CHECK_PEP_523: return 0; - case GET_LEN: - return 1; - case MATCH_CLASS: - return 3; - case MATCH_MAPPING: - return 1; - case MATCH_SEQUENCE: - return 1; - case MATCH_KEYS: + case _CHECK_STACK_SPACE: + return oparg + 2; + case _CHECK_VALIDITY: + return 0; + case _COMPARE_OP: return 2; - case GET_ITER: - return 1; - case GET_YIELD_FROM_ITER: - return 1; - case _SPECIALIZE_FOR_ITER: - return 1; + case _EXIT_TRACE: + return 0; case _FOR_ITER: return 1; case _FOR_ITER_TIER_TWO: return 1; - case FOR_ITER: + case _GUARD_BOTH_FLOAT: + return 2; + case _GUARD_BOTH_INT: + return 2; + case _GUARD_BOTH_UNICODE: + return 2; + case _GUARD_BUILTINS_VERSION: + return 0; + case _GUARD_DORV_VALUES: return 1; - case INSTRUMENTED_FOR_ITER: + case _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT: + return 1; + case _GUARD_GLOBALS_VERSION: return 0; - case _ITER_CHECK_LIST: + case _GUARD_IS_FALSE_POP: return 1; - case _ITER_JUMP_LIST: + case _GUARD_IS_NONE_POP: return 1; - case _GUARD_NOT_EXHAUSTED_LIST: + case _GUARD_IS_NOT_NONE_POP: return 1; - case _ITER_NEXT_LIST: + case _GUARD_IS_TRUE_POP: return 1; - case FOR_ITER_LIST: + case _GUARD_KEYS_VERSION: return 1; - case _ITER_CHECK_TUPLE: + case _GUARD_NOT_EXHAUSTED_LIST: return 1; - case _ITER_JUMP_TUPLE: + case _GUARD_NOT_EXHAUSTED_RANGE: return 1; case _GUARD_NOT_EXHAUSTED_TUPLE: return 1; - case _ITER_NEXT_TUPLE: + case _GUARD_TYPE_VERSION: return 1; - case FOR_ITER_TUPLE: + case _INIT_CALL_BOUND_METHOD_EXACT_ARGS: + return oparg + 2; + case _INIT_CALL_PY_EXACT_ARGS: + return oparg + 2; + case _INSERT: + return oparg + 1; + case _IS_NONE: + return 1; + case _ITER_CHECK_LIST: return 1; case _ITER_CHECK_RANGE: return 1; - case _ITER_JUMP_RANGE: + case _ITER_CHECK_TUPLE: return 1; - case _GUARD_NOT_EXHAUSTED_RANGE: + case _ITER_JUMP_LIST: return 1; - case _ITER_NEXT_RANGE: + case _ITER_JUMP_RANGE: return 1; - case FOR_ITER_RANGE: + case _ITER_JUMP_TUPLE: return 1; - case FOR_ITER_GEN: + case _ITER_NEXT_LIST: return 1; - case BEFORE_ASYNC_WITH: + case _ITER_NEXT_RANGE: return 1; - case BEFORE_WITH: + case _ITER_NEXT_TUPLE: return 1; - case WITH_EXCEPT_START: - return 4; - case SETUP_FINALLY: - return 0; - case SETUP_CLEANUP: - return 0; - case SETUP_WITH: - return 0; - case POP_BLOCK: + case _JUMP_TO_TOP: return 0; - case PUSH_EXC_INFO: - return 1; - case _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT: + case _LOAD_ATTR: return 1; - case _GUARD_KEYS_VERSION: + case _LOAD_ATTR_CLASS: return 1; - case _LOAD_ATTR_METHOD_WITH_VALUES: + case _LOAD_ATTR_INSTANCE_VALUE: return 1; - case LOAD_ATTR_METHOD_WITH_VALUES: + case _LOAD_ATTR_METHOD_LAZY_DICT: return 1; case _LOAD_ATTR_METHOD_NO_DICT: return 1; - case LOAD_ATTR_METHOD_NO_DICT: + case _LOAD_ATTR_METHOD_WITH_VALUES: + return 1; + case _LOAD_ATTR_MODULE: + return 1; + case _LOAD_ATTR_NONDESCRIPTOR_NO_DICT: return 1; case _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: return 1; - case LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: + case _LOAD_ATTR_SLOT: return 1; - case _LOAD_ATTR_NONDESCRIPTOR_NO_DICT: + case _LOAD_ATTR_WITH_HINT: return 1; - case LOAD_ATTR_NONDESCRIPTOR_NO_DICT: + case _LOAD_GLOBAL: + return 0; + case _LOAD_GLOBAL_BUILTINS: + return 0; + case _LOAD_GLOBAL_MODULE: + return 0; + case _LOAD_SUPER_ATTR: + return 3; + case _POP_FRAME: return 1; - case _CHECK_ATTR_METHOD_LAZY_DICT: + case _POP_JUMP_IF_FALSE: return 1; - case _LOAD_ATTR_METHOD_LAZY_DICT: + case _POP_JUMP_IF_TRUE: return 1; - case LOAD_ATTR_METHOD_LAZY_DICT: + case _PUSH_FRAME: return 1; - case INSTRUMENTED_CALL: + case _SAVE_RETURN_OFFSET: return 0; + case _SEND: + return 2; + case _SET_IP: + return 0; + case _SPECIALIZE_BINARY_OP: + return 2; + case _SPECIALIZE_BINARY_SUBSCR: + return 2; case _SPECIALIZE_CALL: return oparg + 2; - case _CALL: - return oparg + 2; - case CALL: - return oparg + 2; - case _CHECK_CALL_BOUND_METHOD_EXACT_ARGS: - return oparg + 2; - case _INIT_CALL_BOUND_METHOD_EXACT_ARGS: - return oparg + 2; - case _CHECK_PEP_523: - return 0; - case _CHECK_FUNCTION_EXACT_ARGS: - return oparg + 2; - case _CHECK_STACK_SPACE: - return oparg + 2; - case _INIT_CALL_PY_EXACT_ARGS: - return oparg + 2; - case _PUSH_FRAME: + case _SPECIALIZE_COMPARE_OP: + return 2; + case _SPECIALIZE_FOR_ITER: return 1; - case CALL_BOUND_METHOD_EXACT_ARGS: - return oparg + 2; - case CALL_PY_EXACT_ARGS: - return oparg + 2; - case CALL_PY_WITH_DEFAULTS: - return oparg + 2; - case CALL_TYPE_1: - return oparg + 2; - case CALL_STR_1: - return oparg + 2; - case CALL_TUPLE_1: - return oparg + 2; - case CALL_ALLOC_AND_ENTER_INIT: - return oparg + 2; - case EXIT_INIT_CHECK: + case _SPECIALIZE_LOAD_ATTR: return 1; - case CALL_BUILTIN_CLASS: - return oparg + 2; - case CALL_BUILTIN_O: - return oparg + 2; - case CALL_BUILTIN_FAST: - return oparg + 2; - case CALL_BUILTIN_FAST_WITH_KEYWORDS: - return oparg + 2; - case CALL_LEN: - return oparg + 2; - case CALL_ISINSTANCE: - return oparg + 2; - case CALL_LIST_APPEND: - return oparg + 2; - case CALL_METHOD_DESCRIPTOR_O: - return oparg + 2; - case CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS: - return oparg + 2; - case CALL_METHOD_DESCRIPTOR_NOARGS: - return oparg + 2; - case CALL_METHOD_DESCRIPTOR_FAST: - return oparg + 2; - case INSTRUMENTED_CALL_KW: - return 0; - case CALL_KW: - return oparg + 3; - case INSTRUMENTED_CALL_FUNCTION_EX: + case _SPECIALIZE_LOAD_GLOBAL: return 0; - case CALL_FUNCTION_EX: - return ((oparg & 1) ? 1 : 0) + 3; - case MAKE_FUNCTION: + case _SPECIALIZE_LOAD_SUPER_ATTR: + return 3; + case _SPECIALIZE_SEND: + return 2; + case _SPECIALIZE_STORE_ATTR: return 1; - case SET_FUNCTION_ATTRIBUTE: + case _SPECIALIZE_STORE_SUBSCR: return 2; - case RETURN_GENERATOR: - return 0; - case BUILD_SLICE: - return ((oparg == 3) ? 1 : 0) + 2; - case CONVERT_VALUE: + case _SPECIALIZE_TO_BOOL: return 1; - case FORMAT_SIMPLE: + case _SPECIALIZE_UNPACK_SEQUENCE: return 1; - case FORMAT_WITH_SPEC: - return 2; - case COPY: - return (oparg-1) + 1; - case _SPECIALIZE_BINARY_OP: + case _STORE_ATTR: return 2; - case _BINARY_OP: + case _STORE_ATTR_INSTANCE_VALUE: return 2; - case BINARY_OP: + case _STORE_ATTR_SLOT: return 2; - case SWAP: - return (oparg-2) + 2; - case INSTRUMENTED_INSTRUCTION: - return 0; - case INSTRUMENTED_JUMP_FORWARD: - return 0; - case INSTRUMENTED_JUMP_BACKWARD: - return 0; - case INSTRUMENTED_POP_JUMP_IF_TRUE: - return 0; - case INSTRUMENTED_POP_JUMP_IF_FALSE: - return 0; - case INSTRUMENTED_POP_JUMP_IF_NONE: - return 0; - case INSTRUMENTED_POP_JUMP_IF_NOT_NONE: - return 0; - case EXTENDED_ARG: - return 0; - case CACHE: - return 0; - case RESERVED: - return 0; - case _GUARD_IS_TRUE_POP: - return 1; - case _GUARD_IS_FALSE_POP: - return 1; - case _GUARD_IS_NONE_POP: + case _STORE_SUBSCR: + return 3; + case _TO_BOOL: return 1; - case _GUARD_IS_NOT_NONE_POP: + case _UNPACK_SEQUENCE: return 1; - case _JUMP_TO_TOP: - return 0; - case _SET_IP: - return 0; - case _SAVE_RETURN_OFFSET: - return 0; - case _EXIT_TRACE: - return 0; - case _INSERT: - return oparg + 1; - case _CHECK_VALIDITY: - return 0; default: return -1; } @@ -671,636 +671,636 @@ extern int _PyOpcode_num_pushed(int opcode, int oparg, bool jump); #ifdef NEED_OPCODE_METADATA int _PyOpcode_num_pushed(int opcode, int oparg, bool jump) { switch(opcode) { - case NOP: - return 0; - case RESUME: - return 0; - case RESUME_CHECK: - return 0; - case INSTRUMENTED_RESUME: - return 0; - case LOAD_CLOSURE: - return 1; - case LOAD_FAST_CHECK: + case BEFORE_ASYNC_WITH: + return 2; + case BEFORE_WITH: + return 2; + case BINARY_OP: return 1; - case LOAD_FAST: + case BINARY_OP_ADD_FLOAT: return 1; - case LOAD_FAST_AND_CLEAR: + case BINARY_OP_ADD_INT: return 1; - case LOAD_FAST_LOAD_FAST: - return 2; - case LOAD_CONST: + case BINARY_OP_ADD_UNICODE: return 1; - case STORE_FAST: - return 0; - case STORE_FAST_MAYBE_NULL: + case BINARY_OP_INPLACE_ADD_UNICODE: return 0; - case STORE_FAST_LOAD_FAST: + case BINARY_OP_MULTIPLY_FLOAT: return 1; - case STORE_FAST_STORE_FAST: - return 0; - case POP_TOP: - return 0; - case PUSH_NULL: + case BINARY_OP_MULTIPLY_INT: return 1; - case END_FOR: - return 0; - case INSTRUMENTED_END_FOR: - return 0; - case END_SEND: + case BINARY_OP_SUBTRACT_FLOAT: return 1; - case INSTRUMENTED_END_SEND: + case BINARY_OP_SUBTRACT_INT: return 1; - case UNARY_NEGATIVE: + case BINARY_SLICE: return 1; - case UNARY_NOT: + case BINARY_SUBSCR: return 1; - case _SPECIALIZE_TO_BOOL: + case BINARY_SUBSCR_DICT: return 1; - case _TO_BOOL: + case BINARY_SUBSCR_GETITEM: return 1; - case TO_BOOL: + case BINARY_SUBSCR_LIST_INT: return 1; - case TO_BOOL_BOOL: + case BINARY_SUBSCR_STR_INT: return 1; - case TO_BOOL_INT: + case BINARY_SUBSCR_TUPLE_INT: return 1; - case TO_BOOL_LIST: + case BUILD_CONST_KEY_MAP: return 1; - case TO_BOOL_NONE: + case BUILD_LIST: return 1; - case TO_BOOL_STR: + case BUILD_MAP: return 1; - case TO_BOOL_ALWAYS_TRUE: + case BUILD_SET: return 1; - case UNARY_INVERT: + case BUILD_SLICE: return 1; - case _GUARD_BOTH_INT: - return 2; - case _BINARY_OP_MULTIPLY_INT: + case BUILD_STRING: return 1; - case _BINARY_OP_ADD_INT: + case BUILD_TUPLE: return 1; - case _BINARY_OP_SUBTRACT_INT: + case CACHE: + return 0; + case CALL: return 1; - case BINARY_OP_MULTIPLY_INT: + case CALL_ALLOC_AND_ENTER_INIT: return 1; - case BINARY_OP_ADD_INT: + case CALL_BOUND_METHOD_EXACT_ARGS: + return 0; + case CALL_BUILTIN_CLASS: return 1; - case BINARY_OP_SUBTRACT_INT: + case CALL_BUILTIN_FAST: return 1; - case _GUARD_BOTH_FLOAT: - return 2; - case _BINARY_OP_MULTIPLY_FLOAT: + case CALL_BUILTIN_FAST_WITH_KEYWORDS: return 1; - case _BINARY_OP_ADD_FLOAT: + case CALL_BUILTIN_O: return 1; - case _BINARY_OP_SUBTRACT_FLOAT: + case CALL_FUNCTION_EX: return 1; - case BINARY_OP_MULTIPLY_FLOAT: + case CALL_INTRINSIC_1: return 1; - case BINARY_OP_ADD_FLOAT: + case CALL_INTRINSIC_2: return 1; - case BINARY_OP_SUBTRACT_FLOAT: + case CALL_ISINSTANCE: return 1; - case _GUARD_BOTH_UNICODE: - return 2; - case _BINARY_OP_ADD_UNICODE: + case CALL_KW: return 1; - case BINARY_OP_ADD_UNICODE: + case CALL_LEN: return 1; - case _BINARY_OP_INPLACE_ADD_UNICODE: - return 0; - case BINARY_OP_INPLACE_ADD_UNICODE: - return 0; - case _SPECIALIZE_BINARY_SUBSCR: - return 2; - case _BINARY_SUBSCR: + case CALL_LIST_APPEND: return 1; - case BINARY_SUBSCR: + case CALL_METHOD_DESCRIPTOR_FAST: return 1; - case BINARY_SLICE: + case CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS: return 1; - case STORE_SLICE: - return 0; - case BINARY_SUBSCR_LIST_INT: + case CALL_METHOD_DESCRIPTOR_NOARGS: return 1; - case BINARY_SUBSCR_STR_INT: + case CALL_METHOD_DESCRIPTOR_O: return 1; - case BINARY_SUBSCR_TUPLE_INT: + case CALL_PY_EXACT_ARGS: + return 0; + case CALL_PY_WITH_DEFAULTS: return 1; - case BINARY_SUBSCR_DICT: + case CALL_STR_1: return 1; - case BINARY_SUBSCR_GETITEM: + case CALL_TUPLE_1: return 1; - case LIST_APPEND: - return (oparg-1) + 1; - case SET_ADD: - return (oparg-1) + 1; - case _SPECIALIZE_STORE_SUBSCR: + case CALL_TYPE_1: + return 1; + case CHECK_EG_MATCH: return 2; - case _STORE_SUBSCR: + case CHECK_EXC_MATCH: + return 2; + case CLEANUP_THROW: + return 2; + case COMPARE_OP: + return 1; + case COMPARE_OP_FLOAT: + return 1; + case COMPARE_OP_INT: + return 1; + case COMPARE_OP_STR: + return 1; + case CONTAINS_OP: + return 1; + case CONVERT_VALUE: + return 1; + case COPY: + return (oparg-1) + 2; + case COPY_FREE_VARS: return 0; - case STORE_SUBSCR: + case DELETE_ATTR: return 0; - case STORE_SUBSCR_LIST_INT: + case DELETE_DEREF: return 0; - case STORE_SUBSCR_DICT: + case DELETE_FAST: return 0; - case DELETE_SUBSCR: + case DELETE_GLOBAL: return 0; - case CALL_INTRINSIC_1: - return 1; - case CALL_INTRINSIC_2: - return 1; - case RAISE_VARARGS: + case DELETE_NAME: return 0; - case INTERPRETER_EXIT: + case DELETE_SUBSCR: return 0; - case _POP_FRAME: + case DICT_MERGE: + return (oparg - 1) + 4; + case DICT_UPDATE: + return (oparg - 1) + 1; + case END_ASYNC_FOR: return 0; - case RETURN_VALUE: + case END_FOR: return 0; - case INSTRUMENTED_RETURN_VALUE: + case END_SEND: + return 1; + case ENTER_EXECUTOR: return 0; - case RETURN_CONST: + case EXIT_INIT_CHECK: return 0; - case INSTRUMENTED_RETURN_CONST: + case EXTENDED_ARG: return 0; - case GET_AITER: + case FORMAT_SIMPLE: return 1; - case GET_ANEXT: - return 2; - case GET_AWAITABLE: + case FORMAT_WITH_SPEC: return 1; - case _SPECIALIZE_SEND: + case FOR_ITER: return 2; - case _SEND: + case FOR_ITER_GEN: return 2; - case SEND: + case FOR_ITER_LIST: return 2; - case SEND_GEN: + case FOR_ITER_RANGE: return 2; - case INSTRUMENTED_YIELD_VALUE: + case FOR_ITER_TUPLE: + return 2; + case GET_AITER: return 1; - case YIELD_VALUE: + case GET_ANEXT: + return 2; + case GET_AWAITABLE: return 1; - case POP_EXCEPT: - return 0; - case RERAISE: - return oparg; - case END_ASYNC_FOR: - return 0; - case CLEANUP_THROW: + case GET_ITER: + return 1; + case GET_LEN: return 2; - case LOAD_ASSERTION_ERROR: + case GET_YIELD_FROM_ITER: return 1; - case LOAD_BUILD_CLASS: + case IMPORT_FROM: + return 2; + case IMPORT_NAME: return 1; - case STORE_NAME: + case INSTRUMENTED_CALL: return 0; - case DELETE_NAME: + case INSTRUMENTED_CALL_FUNCTION_EX: return 0; - case _SPECIALIZE_UNPACK_SEQUENCE: - return 1; - case _UNPACK_SEQUENCE: - return oparg; - case UNPACK_SEQUENCE: - return oparg; - case UNPACK_SEQUENCE_TWO_TUPLE: - return oparg; - case UNPACK_SEQUENCE_TUPLE: - return oparg; - case UNPACK_SEQUENCE_LIST: - return oparg; - case UNPACK_EX: - return (oparg & 0xFF) + (oparg >> 8) + 1; - case _SPECIALIZE_STORE_ATTR: - return 1; - case _STORE_ATTR: + case INSTRUMENTED_CALL_KW: return 0; - case STORE_ATTR: + case INSTRUMENTED_END_FOR: return 0; - case DELETE_ATTR: + case INSTRUMENTED_END_SEND: + return 1; + case INSTRUMENTED_FOR_ITER: return 0; - case STORE_GLOBAL: + case INSTRUMENTED_INSTRUCTION: return 0; - case DELETE_GLOBAL: + case INSTRUMENTED_JUMP_BACKWARD: return 0; - case LOAD_LOCALS: - return 1; - case LOAD_FROM_DICT_OR_GLOBALS: - return 1; - case LOAD_NAME: - return 1; - case _SPECIALIZE_LOAD_GLOBAL: + case INSTRUMENTED_JUMP_FORWARD: return 0; - case _LOAD_GLOBAL: + case INSTRUMENTED_LOAD_SUPER_ATTR: return ((oparg & 1) ? 1 : 0) + 1; - case LOAD_GLOBAL: - return (oparg & 1 ? 1 : 0) + 1; - case _GUARD_GLOBALS_VERSION: + case INSTRUMENTED_POP_JUMP_IF_FALSE: return 0; - case _GUARD_BUILTINS_VERSION: + case INSTRUMENTED_POP_JUMP_IF_NONE: return 0; - case _LOAD_GLOBAL_MODULE: - return ((oparg & 1) ? 1 : 0) + 1; - case _LOAD_GLOBAL_BUILTINS: - return ((oparg & 1) ? 1 : 0) + 1; - case LOAD_GLOBAL_MODULE: - return (oparg & 1 ? 1 : 0) + 1; - case LOAD_GLOBAL_BUILTIN: - return (oparg & 1 ? 1 : 0) + 1; - case DELETE_FAST: + case INSTRUMENTED_POP_JUMP_IF_NOT_NONE: return 0; - case MAKE_CELL: + case INSTRUMENTED_POP_JUMP_IF_TRUE: return 0; - case DELETE_DEREF: + case INSTRUMENTED_RESUME: return 0; - case LOAD_FROM_DICT_OR_DEREF: - return 1; - case LOAD_DEREF: - return 1; - case STORE_DEREF: + case INSTRUMENTED_RETURN_CONST: return 0; - case COPY_FREE_VARS: + case INSTRUMENTED_RETURN_VALUE: return 0; - case BUILD_STRING: - return 1; - case BUILD_TUPLE: + case INSTRUMENTED_YIELD_VALUE: return 1; - case BUILD_LIST: + case INTERPRETER_EXIT: + return 0; + case IS_OP: return 1; - case LIST_EXTEND: + case JUMP: + return 0; + case JUMP_BACKWARD: + return 0; + case JUMP_BACKWARD_NO_INTERRUPT: + return 0; + case JUMP_FORWARD: + return 0; + case JUMP_NO_INTERRUPT: + return 0; + case LIST_APPEND: return (oparg-1) + 1; - case SET_UPDATE: + case LIST_EXTEND: return (oparg-1) + 1; - case BUILD_SET: - return 1; - case BUILD_MAP: - return 1; - case SETUP_ANNOTATIONS: - return 0; - case BUILD_CONST_KEY_MAP: + case LOAD_ASSERTION_ERROR: return 1; - case DICT_UPDATE: - return (oparg - 1) + 1; - case DICT_MERGE: - return (oparg - 1) + 4; - case MAP_ADD: - return (oparg - 1) + 1; - case INSTRUMENTED_LOAD_SUPER_ATTR: - return ((oparg & 1) ? 1 : 0) + 1; - case _SPECIALIZE_LOAD_SUPER_ATTR: - return 3; - case _LOAD_SUPER_ATTR: - return ((oparg & 1) ? 1 : 0) + 1; - case LOAD_SUPER_ATTR: + case LOAD_ATTR: return (oparg & 1 ? 1 : 0) + 1; - case LOAD_SUPER_METHOD: + case LOAD_ATTR_CLASS: return (oparg & 1 ? 1 : 0) + 1; - case LOAD_ZERO_SUPER_METHOD: + case LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN: + return 1; + case LOAD_ATTR_INSTANCE_VALUE: return (oparg & 1 ? 1 : 0) + 1; - case LOAD_ZERO_SUPER_ATTR: + case LOAD_ATTR_METHOD_LAZY_DICT: + return 2; + case LOAD_ATTR_METHOD_NO_DICT: + return 2; + case LOAD_ATTR_METHOD_WITH_VALUES: + return 2; + case LOAD_ATTR_MODULE: return (oparg & 1 ? 1 : 0) + 1; - case LOAD_SUPER_ATTR_ATTR: + case LOAD_ATTR_NONDESCRIPTOR_NO_DICT: return 1; - case LOAD_SUPER_ATTR_METHOD: - return 2; - case _SPECIALIZE_LOAD_ATTR: + case LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: return 1; - case _LOAD_ATTR: - return ((oparg & 1) ? 1 : 0) + 1; - case LOAD_ATTR: + case LOAD_ATTR_PROPERTY: + return 1; + case LOAD_ATTR_SLOT: return (oparg & 1 ? 1 : 0) + 1; - case LOAD_METHOD: + case LOAD_ATTR_WITH_HINT: return (oparg & 1 ? 1 : 0) + 1; - case _GUARD_TYPE_VERSION: + case LOAD_BUILD_CLASS: return 1; - case _CHECK_MANAGED_OBJECT_HAS_VALUES: + case LOAD_CLOSURE: return 1; - case _LOAD_ATTR_INSTANCE_VALUE: - return ((oparg & 1) ? 1 : 0) + 1; - case LOAD_ATTR_INSTANCE_VALUE: - return (oparg & 1 ? 1 : 0) + 1; - case _CHECK_ATTR_MODULE: + case LOAD_CONST: return 1; - case _LOAD_ATTR_MODULE: - return ((oparg & 1) ? 1 : 0) + 1; - case LOAD_ATTR_MODULE: - return (oparg & 1 ? 1 : 0) + 1; - case _CHECK_ATTR_WITH_HINT: - return 1; - case _LOAD_ATTR_WITH_HINT: - return ((oparg & 1) ? 1 : 0) + 1; - case LOAD_ATTR_WITH_HINT: - return (oparg & 1 ? 1 : 0) + 1; - case _LOAD_ATTR_SLOT: - return ((oparg & 1) ? 1 : 0) + 1; - case LOAD_ATTR_SLOT: - return (oparg & 1 ? 1 : 0) + 1; - case _CHECK_ATTR_CLASS: + case LOAD_DEREF: return 1; - case _LOAD_ATTR_CLASS: - return ((oparg & 1) ? 1 : 0) + 1; - case LOAD_ATTR_CLASS: - return (oparg & 1 ? 1 : 0) + 1; - case LOAD_ATTR_PROPERTY: + case LOAD_FAST: return 1; - case LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN: + case LOAD_FAST_AND_CLEAR: return 1; - case _GUARD_DORV_VALUES: + case LOAD_FAST_CHECK: return 1; - case _STORE_ATTR_INSTANCE_VALUE: - return 0; - case STORE_ATTR_INSTANCE_VALUE: - return 0; - case STORE_ATTR_WITH_HINT: - return 0; - case _STORE_ATTR_SLOT: - return 0; - case STORE_ATTR_SLOT: - return 0; - case _SPECIALIZE_COMPARE_OP: + case LOAD_FAST_LOAD_FAST: return 2; - case _COMPARE_OP: + case LOAD_FROM_DICT_OR_DEREF: return 1; - case COMPARE_OP: + case LOAD_FROM_DICT_OR_GLOBALS: return 1; - case COMPARE_OP_FLOAT: + case LOAD_GLOBAL: + return (oparg & 1 ? 1 : 0) + 1; + case LOAD_GLOBAL_BUILTIN: + return (oparg & 1 ? 1 : 0) + 1; + case LOAD_GLOBAL_MODULE: + return (oparg & 1 ? 1 : 0) + 1; + case LOAD_LOCALS: return 1; - case COMPARE_OP_INT: + case LOAD_METHOD: + return (oparg & 1 ? 1 : 0) + 1; + case LOAD_NAME: return 1; - case COMPARE_OP_STR: + case LOAD_SUPER_ATTR: + return (oparg & 1 ? 1 : 0) + 1; + case LOAD_SUPER_ATTR_ATTR: return 1; - case IS_OP: + case LOAD_SUPER_ATTR_METHOD: + return 2; + case LOAD_SUPER_METHOD: + return (oparg & 1 ? 1 : 0) + 1; + case LOAD_ZERO_SUPER_ATTR: + return (oparg & 1 ? 1 : 0) + 1; + case LOAD_ZERO_SUPER_METHOD: + return (oparg & 1 ? 1 : 0) + 1; + case MAKE_CELL: + return 0; + case MAKE_FUNCTION: return 1; - case CONTAINS_OP: + case MAP_ADD: + return (oparg - 1) + 1; + case MATCH_CLASS: return 1; - case CHECK_EG_MATCH: - return 2; - case CHECK_EXC_MATCH: + case MATCH_KEYS: + return 3; + case MATCH_MAPPING: return 2; - case IMPORT_NAME: - return 1; - case IMPORT_FROM: + case MATCH_SEQUENCE: return 2; - case JUMP_FORWARD: + case NOP: return 0; - case JUMP_BACKWARD: + case POP_BLOCK: return 0; - case JUMP: + case POP_EXCEPT: return 0; - case JUMP_NO_INTERRUPT: + case POP_JUMP_IF_FALSE: return 0; - case ENTER_EXECUTOR: + case POP_JUMP_IF_NONE: return 0; - case _POP_JUMP_IF_FALSE: + case POP_JUMP_IF_NOT_NONE: return 0; - case _POP_JUMP_IF_TRUE: + case POP_JUMP_IF_TRUE: return 0; - case _IS_NONE: + case POP_TOP: + return 0; + case PUSH_EXC_INFO: + return 2; + case PUSH_NULL: return 1; - case POP_JUMP_IF_TRUE: + case RAISE_VARARGS: return 0; - case POP_JUMP_IF_FALSE: + case RERAISE: + return oparg; + case RESERVED: return 0; - case POP_JUMP_IF_NONE: + case RESUME: return 0; - case POP_JUMP_IF_NOT_NONE: + case RESUME_CHECK: return 0; - case JUMP_BACKWARD_NO_INTERRUPT: + case RETURN_CONST: return 0; - case GET_LEN: - return 2; - case MATCH_CLASS: - return 1; - case MATCH_MAPPING: + case RETURN_GENERATOR: + return 0; + case RETURN_VALUE: + return 0; + case SEND: return 2; - case MATCH_SEQUENCE: + case SEND_GEN: return 2; - case MATCH_KEYS: - return 3; - case GET_ITER: - return 1; - case GET_YIELD_FROM_ITER: + case SETUP_ANNOTATIONS: + return 0; + case SETUP_CLEANUP: + return 0; + case SETUP_FINALLY: + return 0; + case SETUP_WITH: + return 0; + case SET_ADD: + return (oparg-1) + 1; + case SET_FUNCTION_ATTRIBUTE: return 1; - case _SPECIALIZE_FOR_ITER: + case SET_UPDATE: + return (oparg-1) + 1; + case STORE_ATTR: + return 0; + case STORE_ATTR_INSTANCE_VALUE: + return 0; + case STORE_ATTR_SLOT: + return 0; + case STORE_ATTR_WITH_HINT: + return 0; + case STORE_DEREF: + return 0; + case STORE_FAST: + return 0; + case STORE_FAST_LOAD_FAST: return 1; - case _FOR_ITER: - return 2; - case _FOR_ITER_TIER_TWO: - return 2; - case FOR_ITER: - return 2; - case INSTRUMENTED_FOR_ITER: + case STORE_FAST_MAYBE_NULL: return 0; - case _ITER_CHECK_LIST: + case STORE_FAST_STORE_FAST: + return 0; + case STORE_GLOBAL: + return 0; + case STORE_NAME: + return 0; + case STORE_SLICE: + return 0; + case STORE_SUBSCR: + return 0; + case STORE_SUBSCR_DICT: + return 0; + case STORE_SUBSCR_LIST_INT: + return 0; + case SWAP: + return (oparg-2) + 2; + case TO_BOOL: return 1; - case _ITER_JUMP_LIST: + case TO_BOOL_ALWAYS_TRUE: return 1; - case _GUARD_NOT_EXHAUSTED_LIST: + case TO_BOOL_BOOL: return 1; - case _ITER_NEXT_LIST: - return 2; - case FOR_ITER_LIST: - return 2; - case _ITER_CHECK_TUPLE: + case TO_BOOL_INT: return 1; - case _ITER_JUMP_TUPLE: + case TO_BOOL_LIST: return 1; - case _GUARD_NOT_EXHAUSTED_TUPLE: + case TO_BOOL_NONE: return 1; - case _ITER_NEXT_TUPLE: - return 2; - case FOR_ITER_TUPLE: - return 2; - case _ITER_CHECK_RANGE: + case TO_BOOL_STR: return 1; - case _ITER_JUMP_RANGE: + case UNARY_INVERT: return 1; - case _GUARD_NOT_EXHAUSTED_RANGE: + case UNARY_NEGATIVE: return 1; - case _ITER_NEXT_RANGE: - return 2; - case FOR_ITER_RANGE: - return 2; - case FOR_ITER_GEN: - return 2; - case BEFORE_ASYNC_WITH: - return 2; - case BEFORE_WITH: - return 2; + case UNARY_NOT: + return 1; + case UNPACK_EX: + return (oparg & 0xFF) + (oparg >> 8) + 1; + case UNPACK_SEQUENCE: + return oparg; + case UNPACK_SEQUENCE_LIST: + return oparg; + case UNPACK_SEQUENCE_TUPLE: + return oparg; + case UNPACK_SEQUENCE_TWO_TUPLE: + return oparg; case WITH_EXCEPT_START: return 5; - case SETUP_FINALLY: - return 0; - case SETUP_CLEANUP: - return 0; - case SETUP_WITH: - return 0; - case POP_BLOCK: - return 0; - case PUSH_EXC_INFO: - return 2; - case _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT: + case YIELD_VALUE: return 1; - case _GUARD_KEYS_VERSION: + case _BINARY_OP: return 1; - case _LOAD_ATTR_METHOD_WITH_VALUES: - return 2; - case LOAD_ATTR_METHOD_WITH_VALUES: - return 2; - case _LOAD_ATTR_METHOD_NO_DICT: - return 2; - case LOAD_ATTR_METHOD_NO_DICT: - return 2; - case _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: + case _BINARY_OP_ADD_FLOAT: return 1; - case LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: + case _BINARY_OP_ADD_INT: return 1; - case _LOAD_ATTR_NONDESCRIPTOR_NO_DICT: + case _BINARY_OP_ADD_UNICODE: return 1; - case LOAD_ATTR_NONDESCRIPTOR_NO_DICT: + case _BINARY_OP_INPLACE_ADD_UNICODE: + return 0; + case _BINARY_OP_MULTIPLY_FLOAT: return 1; - case _CHECK_ATTR_METHOD_LAZY_DICT: + case _BINARY_OP_MULTIPLY_INT: + return 1; + case _BINARY_OP_SUBTRACT_FLOAT: + return 1; + case _BINARY_OP_SUBTRACT_INT: + return 1; + case _BINARY_SUBSCR: return 1; - case _LOAD_ATTR_METHOD_LAZY_DICT: - return 2; - case LOAD_ATTR_METHOD_LAZY_DICT: - return 2; - case INSTRUMENTED_CALL: - return 0; - case _SPECIALIZE_CALL: - return oparg + 2; case _CALL: return 1; - case CALL: + case _CHECK_ATTR_CLASS: + return 1; + case _CHECK_ATTR_METHOD_LAZY_DICT: + return 1; + case _CHECK_ATTR_MODULE: + return 1; + case _CHECK_ATTR_WITH_HINT: return 1; case _CHECK_CALL_BOUND_METHOD_EXACT_ARGS: return oparg + 2; - case _INIT_CALL_BOUND_METHOD_EXACT_ARGS: + case _CHECK_FUNCTION_EXACT_ARGS: return oparg + 2; + case _CHECK_MANAGED_OBJECT_HAS_VALUES: + return 1; case _CHECK_PEP_523: return 0; - case _CHECK_FUNCTION_EXACT_ARGS: - return oparg + 2; case _CHECK_STACK_SPACE: return oparg + 2; - case _INIT_CALL_PY_EXACT_ARGS: - return 1; - case _PUSH_FRAME: + case _CHECK_VALIDITY: return 0; - case CALL_BOUND_METHOD_EXACT_ARGS: + case _COMPARE_OP: + return 1; + case _EXIT_TRACE: return 0; - case CALL_PY_EXACT_ARGS: + case _FOR_ITER: + return 2; + case _FOR_ITER_TIER_TWO: + return 2; + case _GUARD_BOTH_FLOAT: + return 2; + case _GUARD_BOTH_INT: + return 2; + case _GUARD_BOTH_UNICODE: + return 2; + case _GUARD_BUILTINS_VERSION: return 0; - case CALL_PY_WITH_DEFAULTS: - return 1; - case CALL_TYPE_1: - return 1; - case CALL_STR_1: - return 1; - case CALL_TUPLE_1: + case _GUARD_DORV_VALUES: return 1; - case CALL_ALLOC_AND_ENTER_INIT: + case _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT: return 1; - case EXIT_INIT_CHECK: + case _GUARD_GLOBALS_VERSION: return 0; - case CALL_BUILTIN_CLASS: - return 1; - case CALL_BUILTIN_O: - return 1; - case CALL_BUILTIN_FAST: + case _GUARD_IS_FALSE_POP: + return 0; + case _GUARD_IS_NONE_POP: + return 0; + case _GUARD_IS_NOT_NONE_POP: + return 0; + case _GUARD_IS_TRUE_POP: + return 0; + case _GUARD_KEYS_VERSION: return 1; - case CALL_BUILTIN_FAST_WITH_KEYWORDS: + case _GUARD_NOT_EXHAUSTED_LIST: return 1; - case CALL_LEN: + case _GUARD_NOT_EXHAUSTED_RANGE: return 1; - case CALL_ISINSTANCE: + case _GUARD_NOT_EXHAUSTED_TUPLE: return 1; - case CALL_LIST_APPEND: + case _GUARD_TYPE_VERSION: return 1; - case CALL_METHOD_DESCRIPTOR_O: + case _INIT_CALL_BOUND_METHOD_EXACT_ARGS: + return oparg + 2; + case _INIT_CALL_PY_EXACT_ARGS: return 1; - case CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS: + case _INSERT: + return oparg + 1; + case _IS_NONE: return 1; - case CALL_METHOD_DESCRIPTOR_NOARGS: + case _ITER_CHECK_LIST: return 1; - case CALL_METHOD_DESCRIPTOR_FAST: + case _ITER_CHECK_RANGE: return 1; - case INSTRUMENTED_CALL_KW: - return 0; - case CALL_KW: + case _ITER_CHECK_TUPLE: return 1; - case INSTRUMENTED_CALL_FUNCTION_EX: - return 0; - case CALL_FUNCTION_EX: + case _ITER_JUMP_LIST: return 1; - case MAKE_FUNCTION: + case _ITER_JUMP_RANGE: return 1; - case SET_FUNCTION_ATTRIBUTE: + case _ITER_JUMP_TUPLE: return 1; - case RETURN_GENERATOR: + case _ITER_NEXT_LIST: + return 2; + case _ITER_NEXT_RANGE: + return 2; + case _ITER_NEXT_TUPLE: + return 2; + case _JUMP_TO_TOP: return 0; - case BUILD_SLICE: - return 1; - case CONVERT_VALUE: - return 1; - case FORMAT_SIMPLE: - return 1; - case FORMAT_WITH_SPEC: - return 1; - case COPY: - return (oparg-1) + 2; - case _SPECIALIZE_BINARY_OP: + case _LOAD_ATTR: + return ((oparg & 1) ? 1 : 0) + 1; + case _LOAD_ATTR_CLASS: + return ((oparg & 1) ? 1 : 0) + 1; + case _LOAD_ATTR_INSTANCE_VALUE: + return ((oparg & 1) ? 1 : 0) + 1; + case _LOAD_ATTR_METHOD_LAZY_DICT: return 2; - case _BINARY_OP: + case _LOAD_ATTR_METHOD_NO_DICT: + return 2; + case _LOAD_ATTR_METHOD_WITH_VALUES: + return 2; + case _LOAD_ATTR_MODULE: + return ((oparg & 1) ? 1 : 0) + 1; + case _LOAD_ATTR_NONDESCRIPTOR_NO_DICT: return 1; - case BINARY_OP: + case _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: return 1; - case SWAP: - return (oparg-2) + 2; - case INSTRUMENTED_INSTRUCTION: - return 0; - case INSTRUMENTED_JUMP_FORWARD: - return 0; - case INSTRUMENTED_JUMP_BACKWARD: - return 0; - case INSTRUMENTED_POP_JUMP_IF_TRUE: - return 0; - case INSTRUMENTED_POP_JUMP_IF_FALSE: - return 0; - case INSTRUMENTED_POP_JUMP_IF_NONE: - return 0; - case INSTRUMENTED_POP_JUMP_IF_NOT_NONE: - return 0; - case EXTENDED_ARG: - return 0; - case CACHE: + case _LOAD_ATTR_SLOT: + return ((oparg & 1) ? 1 : 0) + 1; + case _LOAD_ATTR_WITH_HINT: + return ((oparg & 1) ? 1 : 0) + 1; + case _LOAD_GLOBAL: + return ((oparg & 1) ? 1 : 0) + 1; + case _LOAD_GLOBAL_BUILTINS: + return ((oparg & 1) ? 1 : 0) + 1; + case _LOAD_GLOBAL_MODULE: + return ((oparg & 1) ? 1 : 0) + 1; + case _LOAD_SUPER_ATTR: + return ((oparg & 1) ? 1 : 0) + 1; + case _POP_FRAME: return 0; - case RESERVED: + case _POP_JUMP_IF_FALSE: return 0; - case _GUARD_IS_TRUE_POP: + case _POP_JUMP_IF_TRUE: return 0; - case _GUARD_IS_FALSE_POP: + case _PUSH_FRAME: return 0; - case _GUARD_IS_NONE_POP: + case _SAVE_RETURN_OFFSET: return 0; - case _GUARD_IS_NOT_NONE_POP: + case _SEND: + return 2; + case _SET_IP: return 0; - case _JUMP_TO_TOP: + case _SPECIALIZE_BINARY_OP: + return 2; + case _SPECIALIZE_BINARY_SUBSCR: + return 2; + case _SPECIALIZE_CALL: + return oparg + 2; + case _SPECIALIZE_COMPARE_OP: + return 2; + case _SPECIALIZE_FOR_ITER: + return 1; + case _SPECIALIZE_LOAD_ATTR: + return 1; + case _SPECIALIZE_LOAD_GLOBAL: return 0; - case _SET_IP: + case _SPECIALIZE_LOAD_SUPER_ATTR: + return 3; + case _SPECIALIZE_SEND: + return 2; + case _SPECIALIZE_STORE_ATTR: + return 1; + case _SPECIALIZE_STORE_SUBSCR: + return 2; + case _SPECIALIZE_TO_BOOL: + return 1; + case _SPECIALIZE_UNPACK_SEQUENCE: + return 1; + case _STORE_ATTR: return 0; - case _SAVE_RETURN_OFFSET: + case _STORE_ATTR_INSTANCE_VALUE: return 0; - case _EXIT_TRACE: + case _STORE_ATTR_SLOT: return 0; - case _INSERT: - return oparg + 1; - case _CHECK_VALIDITY: + case _STORE_SUBSCR: return 0; + case _TO_BOOL: + return 1; + case _UNPACK_SEQUENCE: + return oparg; default: return -1; } @@ -1377,484 +1377,484 @@ struct opcode_macro_expansion { extern const struct opcode_metadata _PyOpcode_opcode_metadata[OPCODE_METADATA_SIZE]; #ifdef NEED_OPCODE_METADATA const struct opcode_metadata _PyOpcode_opcode_metadata[OPCODE_METADATA_SIZE] = { - [NOP] = { true, INSTR_FMT_IX, 0 }, - [RESUME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [RESUME_CHECK] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [INSTRUMENTED_RESUME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_CLOSURE] = { true, 0, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, - [LOAD_FAST_CHECK] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ERROR_FLAG }, - [LOAD_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, - [LOAD_FAST_AND_CLEAR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, - [LOAD_FAST_LOAD_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, - [LOAD_CONST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_CONST_FLAG }, - [STORE_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, - [STORE_FAST_MAYBE_NULL] = { true, 0, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, - [STORE_FAST_LOAD_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, - [STORE_FAST_STORE_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, - [POP_TOP] = { true, INSTR_FMT_IX, 0 }, - [PUSH_NULL] = { true, INSTR_FMT_IX, 0 }, - [END_FOR] = { true, INSTR_FMT_IX, 0 }, - [INSTRUMENTED_END_FOR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [END_SEND] = { true, INSTR_FMT_IX, 0 }, - [INSTRUMENTED_END_SEND] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [UNARY_NEGATIVE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [UNARY_NOT] = { true, INSTR_FMT_IX, 0 }, - [_SPECIALIZE_TO_BOOL] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, - [_TO_BOOL] = { true, INSTR_FMT_IXC0, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [TO_BOOL] = { true, INSTR_FMT_IXC00, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [TO_BOOL_BOOL] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, - [TO_BOOL_INT] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, - [TO_BOOL_LIST] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, - [TO_BOOL_NONE] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, - [TO_BOOL_STR] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, - [TO_BOOL_ALWAYS_TRUE] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, - [UNARY_INVERT] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_GUARD_BOTH_INT] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_BINARY_OP_MULTIPLY_INT] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG }, - [_BINARY_OP_ADD_INT] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG }, - [_BINARY_OP_SUBTRACT_INT] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG }, - [BINARY_OP_MULTIPLY_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG }, - [BINARY_OP_ADD_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG }, - [BINARY_OP_SUBTRACT_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG }, - [_GUARD_BOTH_FLOAT] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_BINARY_OP_MULTIPLY_FLOAT] = { true, INSTR_FMT_IXC, 0 }, - [_BINARY_OP_ADD_FLOAT] = { true, INSTR_FMT_IXC, 0 }, - [_BINARY_OP_SUBTRACT_FLOAT] = { true, INSTR_FMT_IXC, 0 }, - [BINARY_OP_MULTIPLY_FLOAT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, + [BEFORE_ASYNC_WITH] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BEFORE_WITH] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BINARY_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BINARY_OP_ADD_FLOAT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, - [BINARY_OP_SUBTRACT_FLOAT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, - [_GUARD_BOTH_UNICODE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_BINARY_OP_ADD_UNICODE] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BINARY_OP_ADD_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG }, [BINARY_OP_ADD_UNICODE] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_BINARY_OP_INPLACE_ADD_UNICODE] = { true, INSTR_FMT_IXC, HAS_LOCAL_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BINARY_OP_INPLACE_ADD_UNICODE] = { true, INSTR_FMT_IXC, HAS_LOCAL_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_BINARY_SUBSCR] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, - [_BINARY_SUBSCR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [BINARY_SUBSCR] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BINARY_OP_MULTIPLY_FLOAT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, + [BINARY_OP_MULTIPLY_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG }, + [BINARY_OP_SUBTRACT_FLOAT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, + [BINARY_OP_SUBTRACT_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG }, [BINARY_SLICE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [STORE_SLICE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BINARY_SUBSCR] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BINARY_SUBSCR_DICT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BINARY_SUBSCR_GETITEM] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, [BINARY_SUBSCR_LIST_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, [BINARY_SUBSCR_STR_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, [BINARY_SUBSCR_TUPLE_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, - [BINARY_SUBSCR_DICT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [BINARY_SUBSCR_GETITEM] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [LIST_APPEND] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG }, - [SET_ADD] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_STORE_SUBSCR] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, - [_STORE_SUBSCR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [STORE_SUBSCR] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [STORE_SUBSCR_LIST_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [STORE_SUBSCR_DICT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [DELETE_SUBSCR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BUILD_CONST_KEY_MAP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BUILD_LIST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BUILD_MAP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BUILD_SET] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BUILD_SLICE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BUILD_STRING] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BUILD_TUPLE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CACHE] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, + [CALL] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_ALLOC_AND_ENTER_INIT] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_BOUND_METHOD_EXACT_ARGS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [CALL_BUILTIN_CLASS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG }, + [CALL_BUILTIN_FAST] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_BUILTIN_FAST_WITH_KEYWORDS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_BUILTIN_O] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_FUNCTION_EX] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [CALL_INTRINSIC_1] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [CALL_INTRINSIC_2] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [RAISE_VARARGS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [INTERPRETER_EXIT] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, - [_POP_FRAME] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, - [RETURN_VALUE] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, - [INSTRUMENTED_RETURN_VALUE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [RETURN_CONST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_CONST_FLAG | HAS_ESCAPES_FLAG }, - [INSTRUMENTED_RETURN_CONST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_CONST_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_ISINSTANCE] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_KW] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_LEN] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_LIST_APPEND] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG }, + [CALL_METHOD_DESCRIPTOR_FAST] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_METHOD_DESCRIPTOR_NOARGS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_METHOD_DESCRIPTOR_O] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_PY_EXACT_ARGS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [CALL_PY_WITH_DEFAULTS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [CALL_STR_1] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_TUPLE_1] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_TYPE_1] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [CHECK_EG_MATCH] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CHECK_EXC_MATCH] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CLEANUP_THROW] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [COMPARE_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [COMPARE_OP_FLOAT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [COMPARE_OP_INT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [COMPARE_OP_STR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [CONTAINS_OP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CONVERT_VALUE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG }, + [COPY] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, + [COPY_FREE_VARS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, + [DELETE_ATTR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [DELETE_DEREF] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [DELETE_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ERROR_FLAG }, + [DELETE_GLOBAL] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [DELETE_NAME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [DELETE_SUBSCR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [DICT_MERGE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [DICT_UPDATE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [END_ASYNC_FOR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [END_FOR] = { true, INSTR_FMT_IX, 0 }, + [END_SEND] = { true, INSTR_FMT_IX, 0 }, + [ENTER_EXECUTOR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [EXIT_INIT_CHECK] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [EXTENDED_ARG] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, + [FORMAT_SIMPLE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [FORMAT_WITH_SPEC] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [FOR_ITER] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [FOR_ITER_GEN] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [FOR_ITER_LIST] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_DEOPT_FLAG }, + [FOR_ITER_RANGE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [FOR_ITER_TUPLE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_DEOPT_FLAG }, [GET_AITER] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [GET_ANEXT] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [GET_AWAITABLE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_SEND] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, - [_SEND] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [SEND] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [SEND_GEN] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [GET_ITER] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [GET_LEN] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [GET_YIELD_FROM_ITER] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [IMPORT_FROM] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [IMPORT_NAME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [INSTRUMENTED_CALL] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [INSTRUMENTED_CALL_FUNCTION_EX] = { true, INSTR_FMT_IX, 0 }, + [INSTRUMENTED_CALL_KW] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [INSTRUMENTED_END_FOR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [INSTRUMENTED_END_SEND] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [INSTRUMENTED_FOR_ITER] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [INSTRUMENTED_INSTRUCTION] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [INSTRUMENTED_JUMP_BACKWARD] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG }, + [INSTRUMENTED_JUMP_FORWARD] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, + [INSTRUMENTED_LOAD_SUPER_ATTR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG }, + [INSTRUMENTED_POP_JUMP_IF_FALSE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG }, + [INSTRUMENTED_POP_JUMP_IF_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG }, + [INSTRUMENTED_POP_JUMP_IF_NOT_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG }, + [INSTRUMENTED_POP_JUMP_IF_TRUE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG }, + [INSTRUMENTED_RESUME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [INSTRUMENTED_RETURN_CONST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_CONST_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [INSTRUMENTED_RETURN_VALUE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [INSTRUMENTED_YIELD_VALUE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [YIELD_VALUE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [POP_EXCEPT] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, - [RERAISE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [END_ASYNC_FOR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CLEANUP_THROW] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [INTERPRETER_EXIT] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, + [IS_OP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, + [JUMP] = { true, 0, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [JUMP_BACKWARD] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [JUMP_BACKWARD_NO_INTERRUPT] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, + [JUMP_FORWARD] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, + [JUMP_NO_INTERRUPT] = { true, 0, HAS_ARG_FLAG | HAS_JUMP_FLAG }, + [LIST_APPEND] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG }, + [LIST_EXTEND] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_ASSERTION_ERROR] = { true, INSTR_FMT_IX, 0 }, + [LOAD_ATTR] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_ATTR_CLASS] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [LOAD_ATTR_METHOD_LAZY_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_ATTR_METHOD_NO_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_ATTR_METHOD_WITH_VALUES] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_ATTR_MODULE] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [LOAD_ATTR_PROPERTY] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_ATTR_SLOT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [LOAD_ATTR_WITH_HINT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, [LOAD_BUILD_CLASS] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [STORE_NAME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [DELETE_NAME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_UNPACK_SEQUENCE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [_UNPACK_SEQUENCE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [UNPACK_SEQUENCE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [UNPACK_SEQUENCE_TWO_TUPLE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [UNPACK_SEQUENCE_TUPLE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [UNPACK_SEQUENCE_LIST] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [UNPACK_EX] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_STORE_ATTR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ESCAPES_FLAG }, - [_STORE_ATTR] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [STORE_ATTR] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [DELETE_ATTR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [STORE_GLOBAL] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [DELETE_GLOBAL] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_LOCALS] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_CLOSURE] = { true, 0, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, + [LOAD_CONST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_CONST_FLAG }, + [LOAD_DEREF] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, + [LOAD_FAST_AND_CLEAR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, + [LOAD_FAST_CHECK] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ERROR_FLAG }, + [LOAD_FAST_LOAD_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, + [LOAD_FROM_DICT_OR_DEREF] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_FROM_DICT_OR_GLOBALS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_NAME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_LOAD_GLOBAL] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ESCAPES_FLAG }, - [_LOAD_GLOBAL] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_GLOBAL] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_GUARD_GLOBALS_VERSION] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, - [_GUARD_BUILTINS_VERSION] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, - [_LOAD_GLOBAL_MODULE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [_LOAD_GLOBAL_BUILTINS] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [LOAD_GLOBAL_MODULE] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, [LOAD_GLOBAL_BUILTIN] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [DELETE_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ERROR_FLAG }, - [MAKE_CELL] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [DELETE_DEREF] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_FROM_DICT_OR_DEREF] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_DEREF] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [STORE_DEREF] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ESCAPES_FLAG }, - [COPY_FREE_VARS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, - [BUILD_STRING] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [BUILD_TUPLE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [BUILD_LIST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LIST_EXTEND] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [SET_UPDATE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [BUILD_SET] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [BUILD_MAP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [SETUP_ANNOTATIONS] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [BUILD_CONST_KEY_MAP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [DICT_UPDATE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [DICT_MERGE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [MAP_ADD] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [INSTRUMENTED_LOAD_SUPER_ATTR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG }, - [_SPECIALIZE_LOAD_SUPER_ATTR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [_LOAD_SUPER_ATTR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_GLOBAL_MODULE] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [LOAD_LOCALS] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_METHOD] = { true, 0, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_NAME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_SUPER_ATTR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_SUPER_METHOD] = { true, 0, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ZERO_SUPER_METHOD] = { true, 0, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ZERO_SUPER_ATTR] = { true, 0, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_SUPER_ATTR_ATTR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_SUPER_ATTR_METHOD] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_LOAD_ATTR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ESCAPES_FLAG }, - [_LOAD_ATTR] = { true, INSTR_FMT_IBC0000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ATTR] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_METHOD] = { true, 0, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_GUARD_TYPE_VERSION] = { true, INSTR_FMT_IXC0, HAS_DEOPT_FLAG }, - [_CHECK_MANAGED_OBJECT_HAS_VALUES] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_LOAD_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [LOAD_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [_CHECK_ATTR_MODULE] = { true, INSTR_FMT_IXC0, HAS_DEOPT_FLAG }, - [_LOAD_ATTR_MODULE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [LOAD_ATTR_MODULE] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [_CHECK_ATTR_WITH_HINT] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [_LOAD_ATTR_WITH_HINT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ATTR_WITH_HINT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [_LOAD_ATTR_SLOT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [LOAD_ATTR_SLOT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [_CHECK_ATTR_CLASS] = { true, INSTR_FMT_IXC0, HAS_DEOPT_FLAG }, - [_LOAD_ATTR_CLASS] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG }, - [LOAD_ATTR_CLASS] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [LOAD_ATTR_PROPERTY] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [_GUARD_DORV_VALUES] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_STORE_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, - [STORE_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IXC000, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [STORE_ATTR_WITH_HINT] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [_STORE_ATTR_SLOT] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, - [STORE_ATTR_SLOT] = { true, INSTR_FMT_IXC000, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_COMPARE_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [_COMPARE_OP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [COMPARE_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [COMPARE_OP_FLOAT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [COMPARE_OP_INT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [COMPARE_OP_STR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [IS_OP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, - [CONTAINS_OP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CHECK_EG_MATCH] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CHECK_EXC_MATCH] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [IMPORT_NAME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [IMPORT_FROM] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [JUMP_FORWARD] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [JUMP_BACKWARD] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [JUMP] = { true, 0, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [JUMP_NO_INTERRUPT] = { true, 0, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [ENTER_EXECUTOR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_POP_JUMP_IF_FALSE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [_POP_JUMP_IF_TRUE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [_IS_NONE] = { true, INSTR_FMT_IX, 0 }, - [POP_JUMP_IF_TRUE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [POP_JUMP_IF_FALSE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [POP_JUMP_IF_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [POP_JUMP_IF_NOT_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [JUMP_BACKWARD_NO_INTERRUPT] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [GET_LEN] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_SUPER_METHOD] = { true, 0, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_ZERO_SUPER_ATTR] = { true, 0, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_ZERO_SUPER_METHOD] = { true, 0, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [MAKE_CELL] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [MAKE_FUNCTION] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [MAP_ADD] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [MATCH_CLASS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [MATCH_KEYS] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [MATCH_MAPPING] = { true, INSTR_FMT_IX, 0 }, [MATCH_SEQUENCE] = { true, INSTR_FMT_IX, 0 }, - [MATCH_KEYS] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [GET_ITER] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [GET_YIELD_FROM_ITER] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_FOR_ITER] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [_FOR_ITER] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_FOR_ITER_TIER_TWO] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [FOR_ITER] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [INSTRUMENTED_FOR_ITER] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_ITER_CHECK_LIST] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_ITER_JUMP_LIST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [_GUARD_NOT_EXHAUSTED_LIST] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_ITER_NEXT_LIST] = { true, INSTR_FMT_IX, 0 }, - [FOR_ITER_LIST] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_DEOPT_FLAG }, - [_ITER_CHECK_TUPLE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_ITER_JUMP_TUPLE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [_GUARD_NOT_EXHAUSTED_TUPLE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_ITER_NEXT_TUPLE] = { true, INSTR_FMT_IX, 0 }, - [FOR_ITER_TUPLE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_DEOPT_FLAG }, - [_ITER_CHECK_RANGE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_ITER_JUMP_RANGE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [_GUARD_NOT_EXHAUSTED_RANGE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_ITER_NEXT_RANGE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [FOR_ITER_RANGE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [FOR_ITER_GEN] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [BEFORE_ASYNC_WITH] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [BEFORE_WITH] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [WITH_EXCEPT_START] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [SETUP_FINALLY] = { true, 0, HAS_ARG_FLAG }, - [SETUP_CLEANUP] = { true, 0, HAS_ARG_FLAG }, - [SETUP_WITH] = { true, 0, HAS_ARG_FLAG }, + [NOP] = { true, INSTR_FMT_IX, 0 }, [POP_BLOCK] = { true, 0, 0 }, + [POP_EXCEPT] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, + [POP_JUMP_IF_FALSE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, + [POP_JUMP_IF_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, + [POP_JUMP_IF_NOT_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, + [POP_JUMP_IF_TRUE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, + [POP_TOP] = { true, INSTR_FMT_IX, 0 }, [PUSH_EXC_INFO] = { true, INSTR_FMT_IX, 0 }, - [_GUARD_DORV_VALUES_INST_ATTR_FROM_DICT] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_GUARD_KEYS_VERSION] = { true, INSTR_FMT_IXC0, HAS_DEOPT_FLAG }, - [_LOAD_ATTR_METHOD_WITH_VALUES] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ATTR_METHOD_WITH_VALUES] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [_LOAD_ATTR_METHOD_NO_DICT] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ATTR_METHOD_NO_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG }, - [LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [_LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG }, - [LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [_CHECK_ATTR_METHOD_LAZY_DICT] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_LOAD_ATTR_METHOD_LAZY_DICT] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ATTR_METHOD_LAZY_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [INSTRUMENTED_CALL] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_CALL] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [PUSH_NULL] = { true, INSTR_FMT_IX, 0 }, + [RAISE_VARARGS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [RERAISE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [RESERVED] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, + [RESUME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [RESUME_CHECK] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [RETURN_CONST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_CONST_FLAG | HAS_ESCAPES_FLAG }, + [RETURN_GENERATOR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [RETURN_VALUE] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, + [SEND] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [SEND_GEN] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [SETUP_ANNOTATIONS] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [SETUP_CLEANUP] = { true, 0, HAS_ARG_FLAG }, + [SETUP_FINALLY] = { true, 0, HAS_ARG_FLAG }, + [SETUP_WITH] = { true, 0, HAS_ARG_FLAG }, + [SET_ADD] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [SET_FUNCTION_ATTRIBUTE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [SET_UPDATE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [STORE_ATTR] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [STORE_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IXC000, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [STORE_ATTR_SLOT] = { true, INSTR_FMT_IXC000, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [STORE_ATTR_WITH_HINT] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [STORE_DEREF] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ESCAPES_FLAG }, + [STORE_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, + [STORE_FAST_LOAD_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, + [STORE_FAST_MAYBE_NULL] = { true, 0, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, + [STORE_FAST_STORE_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, + [STORE_GLOBAL] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [STORE_NAME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [STORE_SLICE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [STORE_SUBSCR] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [STORE_SUBSCR_DICT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [STORE_SUBSCR_LIST_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [SWAP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, + [TO_BOOL] = { true, INSTR_FMT_IXC00, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [TO_BOOL_ALWAYS_TRUE] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, + [TO_BOOL_BOOL] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, + [TO_BOOL_INT] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, + [TO_BOOL_LIST] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, + [TO_BOOL_NONE] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, + [TO_BOOL_STR] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, + [UNARY_INVERT] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [UNARY_NEGATIVE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [UNARY_NOT] = { true, INSTR_FMT_IX, 0 }, + [UNPACK_EX] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [UNPACK_SEQUENCE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [UNPACK_SEQUENCE_LIST] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [UNPACK_SEQUENCE_TUPLE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [UNPACK_SEQUENCE_TWO_TUPLE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [WITH_EXCEPT_START] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [YIELD_VALUE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [_BINARY_OP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG }, + [_BINARY_OP_ADD_FLOAT] = { true, INSTR_FMT_IXC, 0 }, + [_BINARY_OP_ADD_INT] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG }, + [_BINARY_OP_ADD_UNICODE] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_BINARY_OP_INPLACE_ADD_UNICODE] = { true, INSTR_FMT_IXC, HAS_LOCAL_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_BINARY_OP_MULTIPLY_FLOAT] = { true, INSTR_FMT_IXC, 0 }, + [_BINARY_OP_MULTIPLY_INT] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG }, + [_BINARY_OP_SUBTRACT_FLOAT] = { true, INSTR_FMT_IXC, 0 }, + [_BINARY_OP_SUBTRACT_INT] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG }, + [_BINARY_SUBSCR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [_CALL] = { true, INSTR_FMT_IBC0, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CALL] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_CHECK_ATTR_CLASS] = { true, INSTR_FMT_IXC0, HAS_DEOPT_FLAG }, + [_CHECK_ATTR_METHOD_LAZY_DICT] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_CHECK_ATTR_MODULE] = { true, INSTR_FMT_IXC0, HAS_DEOPT_FLAG }, + [_CHECK_ATTR_WITH_HINT] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, [_CHECK_CALL_BOUND_METHOD_EXACT_ARGS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [_INIT_CALL_BOUND_METHOD_EXACT_ARGS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, - [_CHECK_PEP_523] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, [_CHECK_FUNCTION_EXACT_ARGS] = { true, INSTR_FMT_IBC0, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [_CHECK_MANAGED_OBJECT_HAS_VALUES] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_CHECK_PEP_523] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, [_CHECK_STACK_SPACE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [_INIT_CALL_PY_EXACT_ARGS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [_PUSH_FRAME] = { true, INSTR_FMT_IX, 0 }, - [CALL_BOUND_METHOD_EXACT_ARGS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [CALL_PY_EXACT_ARGS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [CALL_PY_WITH_DEFAULTS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [CALL_TYPE_1] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [CALL_STR_1] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CALL_TUPLE_1] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CALL_ALLOC_AND_ENTER_INIT] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [EXIT_INIT_CHECK] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CALL_BUILTIN_CLASS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG }, - [CALL_BUILTIN_O] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CALL_BUILTIN_FAST] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CALL_BUILTIN_FAST_WITH_KEYWORDS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CALL_LEN] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CALL_ISINSTANCE] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CALL_LIST_APPEND] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG }, - [CALL_METHOD_DESCRIPTOR_O] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CALL_METHOD_DESCRIPTOR_NOARGS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CALL_METHOD_DESCRIPTOR_FAST] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [INSTRUMENTED_CALL_KW] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CALL_KW] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [INSTRUMENTED_CALL_FUNCTION_EX] = { true, INSTR_FMT_IX, 0 }, - [CALL_FUNCTION_EX] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [MAKE_FUNCTION] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [SET_FUNCTION_ATTRIBUTE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [RETURN_GENERATOR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [BUILD_SLICE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CONVERT_VALUE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG }, - [FORMAT_SIMPLE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [FORMAT_WITH_SPEC] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [COPY] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, - [_SPECIALIZE_BINARY_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [_BINARY_OP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG }, - [BINARY_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [SWAP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, - [INSTRUMENTED_INSTRUCTION] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [INSTRUMENTED_JUMP_FORWARD] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, - [INSTRUMENTED_JUMP_BACKWARD] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG }, - [INSTRUMENTED_POP_JUMP_IF_TRUE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG }, - [INSTRUMENTED_POP_JUMP_IF_FALSE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG }, - [INSTRUMENTED_POP_JUMP_IF_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG }, - [INSTRUMENTED_POP_JUMP_IF_NOT_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG }, - [EXTENDED_ARG] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, - [CACHE] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, - [RESERVED] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, - [_GUARD_IS_TRUE_POP] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_CHECK_VALIDITY] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_COMPARE_OP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_EXIT_TRACE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_FOR_ITER] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_FOR_ITER_TIER_TWO] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_GUARD_BOTH_FLOAT] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_GUARD_BOTH_INT] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_GUARD_BOTH_UNICODE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_GUARD_BUILTINS_VERSION] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, + [_GUARD_DORV_VALUES] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_GUARD_DORV_VALUES_INST_ATTR_FROM_DICT] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_GUARD_GLOBALS_VERSION] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, [_GUARD_IS_FALSE_POP] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, [_GUARD_IS_NONE_POP] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, [_GUARD_IS_NOT_NONE_POP] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_GUARD_IS_TRUE_POP] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_GUARD_KEYS_VERSION] = { true, INSTR_FMT_IXC0, HAS_DEOPT_FLAG }, + [_GUARD_NOT_EXHAUSTED_LIST] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_GUARD_NOT_EXHAUSTED_RANGE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_GUARD_NOT_EXHAUSTED_TUPLE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_GUARD_TYPE_VERSION] = { true, INSTR_FMT_IXC0, HAS_DEOPT_FLAG }, + [_INIT_CALL_BOUND_METHOD_EXACT_ARGS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, + [_INIT_CALL_PY_EXACT_ARGS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [_INSERT] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, + [_IS_NONE] = { true, INSTR_FMT_IX, 0 }, + [_ITER_CHECK_LIST] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_ITER_CHECK_RANGE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_ITER_CHECK_TUPLE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_ITER_JUMP_LIST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, + [_ITER_JUMP_RANGE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, + [_ITER_JUMP_TUPLE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, + [_ITER_NEXT_LIST] = { true, INSTR_FMT_IX, 0 }, + [_ITER_NEXT_RANGE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_ITER_NEXT_TUPLE] = { true, INSTR_FMT_IX, 0 }, [_JUMP_TO_TOP] = { true, INSTR_FMT_IX, HAS_EVAL_BREAK_FLAG }, - [_SET_IP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [_LOAD_ATTR] = { true, INSTR_FMT_IBC0000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_LOAD_ATTR_CLASS] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG }, + [_LOAD_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [_LOAD_ATTR_METHOD_LAZY_DICT] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [_LOAD_ATTR_METHOD_NO_DICT] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [_LOAD_ATTR_METHOD_WITH_VALUES] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [_LOAD_ATTR_MODULE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [_LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG }, + [_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG }, + [_LOAD_ATTR_SLOT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [_LOAD_ATTR_WITH_HINT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [_LOAD_GLOBAL] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_LOAD_GLOBAL_BUILTINS] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [_LOAD_GLOBAL_MODULE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [_LOAD_SUPER_ATTR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_POP_FRAME] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, + [_POP_JUMP_IF_FALSE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, + [_POP_JUMP_IF_TRUE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, + [_PUSH_FRAME] = { true, INSTR_FMT_IX, 0 }, [_SAVE_RETURN_OFFSET] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, - [_EXIT_TRACE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_INSERT] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, - [_CHECK_VALIDITY] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_SEND] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_SET_IP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [_SPECIALIZE_BINARY_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [_SPECIALIZE_BINARY_SUBSCR] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, + [_SPECIALIZE_CALL] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [_SPECIALIZE_COMPARE_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [_SPECIALIZE_FOR_ITER] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [_SPECIALIZE_LOAD_ATTR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ESCAPES_FLAG }, + [_SPECIALIZE_LOAD_GLOBAL] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ESCAPES_FLAG }, + [_SPECIALIZE_LOAD_SUPER_ATTR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [_SPECIALIZE_SEND] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, + [_SPECIALIZE_STORE_ATTR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ESCAPES_FLAG }, + [_SPECIALIZE_STORE_SUBSCR] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, + [_SPECIALIZE_TO_BOOL] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, + [_SPECIALIZE_UNPACK_SEQUENCE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [_STORE_ATTR] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_STORE_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, + [_STORE_ATTR_SLOT] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, + [_STORE_SUBSCR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_TO_BOOL] = { true, INSTR_FMT_IXC0, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_UNPACK_SEQUENCE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, }; #endif // NEED_OPCODE_METADATA extern const struct opcode_macro_expansion _PyOpcode_macro_expansion[OPCODE_MACRO_EXPANSION_SIZE]; #ifdef NEED_OPCODE_METADATA const struct opcode_macro_expansion _PyOpcode_macro_expansion[OPCODE_MACRO_EXPANSION_SIZE] = { - [NOP] = { .nuops = 1, .uops = { { NOP, 0, 0 } } }, - [RESUME_CHECK] = { .nuops = 1, .uops = { { RESUME_CHECK, 0, 0 } } }, - [LOAD_FAST_CHECK] = { .nuops = 1, .uops = { { LOAD_FAST_CHECK, 0, 0 } } }, - [LOAD_FAST] = { .nuops = 1, .uops = { { LOAD_FAST, 0, 0 } } }, - [LOAD_FAST_AND_CLEAR] = { .nuops = 1, .uops = { { LOAD_FAST_AND_CLEAR, 0, 0 } } }, - [LOAD_FAST_LOAD_FAST] = { .nuops = 2, .uops = { { LOAD_FAST, 5, 0 }, { LOAD_FAST, 6, 0 } } }, - [LOAD_CONST] = { .nuops = 1, .uops = { { LOAD_CONST, 0, 0 } } }, - [STORE_FAST] = { .nuops = 1, .uops = { { STORE_FAST, 0, 0 } } }, - [STORE_FAST_LOAD_FAST] = { .nuops = 2, .uops = { { STORE_FAST, 5, 0 }, { LOAD_FAST, 6, 0 } } }, - [STORE_FAST_STORE_FAST] = { .nuops = 2, .uops = { { STORE_FAST, 5, 0 }, { STORE_FAST, 6, 0 } } }, - [POP_TOP] = { .nuops = 1, .uops = { { POP_TOP, 0, 0 } } }, - [PUSH_NULL] = { .nuops = 1, .uops = { { PUSH_NULL, 0, 0 } } }, - [END_FOR] = { .nuops = 2, .uops = { { POP_TOP, 0, 0 }, { POP_TOP, 0, 0 } } }, - [END_SEND] = { .nuops = 1, .uops = { { END_SEND, 0, 0 } } }, - [UNARY_NEGATIVE] = { .nuops = 1, .uops = { { UNARY_NEGATIVE, 0, 0 } } }, - [UNARY_NOT] = { .nuops = 1, .uops = { { UNARY_NOT, 0, 0 } } }, - [TO_BOOL] = { .nuops = 1, .uops = { { _TO_BOOL, 0, 0 } } }, - [TO_BOOL_BOOL] = { .nuops = 1, .uops = { { TO_BOOL_BOOL, 0, 0 } } }, - [TO_BOOL_INT] = { .nuops = 1, .uops = { { TO_BOOL_INT, 0, 0 } } }, - [TO_BOOL_LIST] = { .nuops = 1, .uops = { { TO_BOOL_LIST, 0, 0 } } }, - [TO_BOOL_NONE] = { .nuops = 1, .uops = { { TO_BOOL_NONE, 0, 0 } } }, - [TO_BOOL_STR] = { .nuops = 1, .uops = { { TO_BOOL_STR, 0, 0 } } }, - [TO_BOOL_ALWAYS_TRUE] = { .nuops = 1, .uops = { { TO_BOOL_ALWAYS_TRUE, 2, 1 } } }, - [UNARY_INVERT] = { .nuops = 1, .uops = { { UNARY_INVERT, 0, 0 } } }, - [BINARY_OP_MULTIPLY_INT] = { .nuops = 2, .uops = { { _GUARD_BOTH_INT, 0, 0 }, { _BINARY_OP_MULTIPLY_INT, 0, 0 } } }, + [BEFORE_ASYNC_WITH] = { .nuops = 1, .uops = { { BEFORE_ASYNC_WITH, 0, 0 } } }, + [BEFORE_WITH] = { .nuops = 1, .uops = { { BEFORE_WITH, 0, 0 } } }, + [BINARY_OP] = { .nuops = 1, .uops = { { _BINARY_OP, 0, 0 } } }, + [BINARY_OP_ADD_FLOAT] = { .nuops = 2, .uops = { { _GUARD_BOTH_FLOAT, 0, 0 }, { _BINARY_OP_ADD_FLOAT, 0, 0 } } }, [BINARY_OP_ADD_INT] = { .nuops = 2, .uops = { { _GUARD_BOTH_INT, 0, 0 }, { _BINARY_OP_ADD_INT, 0, 0 } } }, - [BINARY_OP_SUBTRACT_INT] = { .nuops = 2, .uops = { { _GUARD_BOTH_INT, 0, 0 }, { _BINARY_OP_SUBTRACT_INT, 0, 0 } } }, + [BINARY_OP_ADD_UNICODE] = { .nuops = 2, .uops = { { _GUARD_BOTH_UNICODE, 0, 0 }, { _BINARY_OP_ADD_UNICODE, 0, 0 } } }, [BINARY_OP_MULTIPLY_FLOAT] = { .nuops = 2, .uops = { { _GUARD_BOTH_FLOAT, 0, 0 }, { _BINARY_OP_MULTIPLY_FLOAT, 0, 0 } } }, - [BINARY_OP_ADD_FLOAT] = { .nuops = 2, .uops = { { _GUARD_BOTH_FLOAT, 0, 0 }, { _BINARY_OP_ADD_FLOAT, 0, 0 } } }, + [BINARY_OP_MULTIPLY_INT] = { .nuops = 2, .uops = { { _GUARD_BOTH_INT, 0, 0 }, { _BINARY_OP_MULTIPLY_INT, 0, 0 } } }, [BINARY_OP_SUBTRACT_FLOAT] = { .nuops = 2, .uops = { { _GUARD_BOTH_FLOAT, 0, 0 }, { _BINARY_OP_SUBTRACT_FLOAT, 0, 0 } } }, - [BINARY_OP_ADD_UNICODE] = { .nuops = 2, .uops = { { _GUARD_BOTH_UNICODE, 0, 0 }, { _BINARY_OP_ADD_UNICODE, 0, 0 } } }, - [BINARY_SUBSCR] = { .nuops = 1, .uops = { { _BINARY_SUBSCR, 0, 0 } } }, + [BINARY_OP_SUBTRACT_INT] = { .nuops = 2, .uops = { { _GUARD_BOTH_INT, 0, 0 }, { _BINARY_OP_SUBTRACT_INT, 0, 0 } } }, [BINARY_SLICE] = { .nuops = 1, .uops = { { BINARY_SLICE, 0, 0 } } }, - [STORE_SLICE] = { .nuops = 1, .uops = { { STORE_SLICE, 0, 0 } } }, + [BINARY_SUBSCR] = { .nuops = 1, .uops = { { _BINARY_SUBSCR, 0, 0 } } }, + [BINARY_SUBSCR_DICT] = { .nuops = 1, .uops = { { BINARY_SUBSCR_DICT, 0, 0 } } }, [BINARY_SUBSCR_LIST_INT] = { .nuops = 1, .uops = { { BINARY_SUBSCR_LIST_INT, 0, 0 } } }, [BINARY_SUBSCR_STR_INT] = { .nuops = 1, .uops = { { BINARY_SUBSCR_STR_INT, 0, 0 } } }, [BINARY_SUBSCR_TUPLE_INT] = { .nuops = 1, .uops = { { BINARY_SUBSCR_TUPLE_INT, 0, 0 } } }, - [BINARY_SUBSCR_DICT] = { .nuops = 1, .uops = { { BINARY_SUBSCR_DICT, 0, 0 } } }, - [LIST_APPEND] = { .nuops = 1, .uops = { { LIST_APPEND, 0, 0 } } }, - [SET_ADD] = { .nuops = 1, .uops = { { SET_ADD, 0, 0 } } }, - [STORE_SUBSCR] = { .nuops = 1, .uops = { { _STORE_SUBSCR, 0, 0 } } }, - [STORE_SUBSCR_LIST_INT] = { .nuops = 1, .uops = { { STORE_SUBSCR_LIST_INT, 0, 0 } } }, - [STORE_SUBSCR_DICT] = { .nuops = 1, .uops = { { STORE_SUBSCR_DICT, 0, 0 } } }, - [DELETE_SUBSCR] = { .nuops = 1, .uops = { { DELETE_SUBSCR, 0, 0 } } }, - [CALL_INTRINSIC_1] = { .nuops = 1, .uops = { { CALL_INTRINSIC_1, 0, 0 } } }, - [CALL_INTRINSIC_2] = { .nuops = 1, .uops = { { CALL_INTRINSIC_2, 0, 0 } } }, - [RETURN_VALUE] = { .nuops = 1, .uops = { { _POP_FRAME, 0, 0 } } }, - [RETURN_CONST] = { .nuops = 2, .uops = { { LOAD_CONST, 0, 0 }, { _POP_FRAME, 0, 0 } } }, - [GET_AITER] = { .nuops = 1, .uops = { { GET_AITER, 0, 0 } } }, - [GET_ANEXT] = { .nuops = 1, .uops = { { GET_ANEXT, 0, 0 } } }, - [GET_AWAITABLE] = { .nuops = 1, .uops = { { GET_AWAITABLE, 0, 0 } } }, - [POP_EXCEPT] = { .nuops = 1, .uops = { { POP_EXCEPT, 0, 0 } } }, - [LOAD_ASSERTION_ERROR] = { .nuops = 1, .uops = { { LOAD_ASSERTION_ERROR, 0, 0 } } }, - [LOAD_BUILD_CLASS] = { .nuops = 1, .uops = { { LOAD_BUILD_CLASS, 0, 0 } } }, - [STORE_NAME] = { .nuops = 1, .uops = { { STORE_NAME, 0, 0 } } }, - [DELETE_NAME] = { .nuops = 1, .uops = { { DELETE_NAME, 0, 0 } } }, - [UNPACK_SEQUENCE] = { .nuops = 1, .uops = { { _UNPACK_SEQUENCE, 0, 0 } } }, - [UNPACK_SEQUENCE_TWO_TUPLE] = { .nuops = 1, .uops = { { UNPACK_SEQUENCE_TWO_TUPLE, 0, 0 } } }, - [UNPACK_SEQUENCE_TUPLE] = { .nuops = 1, .uops = { { UNPACK_SEQUENCE_TUPLE, 0, 0 } } }, - [UNPACK_SEQUENCE_LIST] = { .nuops = 1, .uops = { { UNPACK_SEQUENCE_LIST, 0, 0 } } }, - [UNPACK_EX] = { .nuops = 1, .uops = { { UNPACK_EX, 0, 0 } } }, - [STORE_ATTR] = { .nuops = 1, .uops = { { _STORE_ATTR, 0, 0 } } }, - [DELETE_ATTR] = { .nuops = 1, .uops = { { DELETE_ATTR, 0, 0 } } }, - [STORE_GLOBAL] = { .nuops = 1, .uops = { { STORE_GLOBAL, 0, 0 } } }, - [DELETE_GLOBAL] = { .nuops = 1, .uops = { { DELETE_GLOBAL, 0, 0 } } }, - [LOAD_LOCALS] = { .nuops = 1, .uops = { { LOAD_LOCALS, 0, 0 } } }, - [LOAD_FROM_DICT_OR_GLOBALS] = { .nuops = 1, .uops = { { LOAD_FROM_DICT_OR_GLOBALS, 0, 0 } } }, - [LOAD_NAME] = { .nuops = 1, .uops = { { LOAD_NAME, 0, 0 } } }, - [LOAD_GLOBAL] = { .nuops = 1, .uops = { { _LOAD_GLOBAL, 0, 0 } } }, - [LOAD_GLOBAL_MODULE] = { .nuops = 2, .uops = { { _GUARD_GLOBALS_VERSION, 1, 1 }, { _LOAD_GLOBAL_MODULE, 1, 3 } } }, - [LOAD_GLOBAL_BUILTIN] = { .nuops = 3, .uops = { { _GUARD_GLOBALS_VERSION, 1, 1 }, { _GUARD_BUILTINS_VERSION, 1, 2 }, { _LOAD_GLOBAL_BUILTINS, 1, 3 } } }, - [DELETE_FAST] = { .nuops = 1, .uops = { { DELETE_FAST, 0, 0 } } }, - [MAKE_CELL] = { .nuops = 1, .uops = { { MAKE_CELL, 0, 0 } } }, - [DELETE_DEREF] = { .nuops = 1, .uops = { { DELETE_DEREF, 0, 0 } } }, - [LOAD_FROM_DICT_OR_DEREF] = { .nuops = 1, .uops = { { LOAD_FROM_DICT_OR_DEREF, 0, 0 } } }, - [LOAD_DEREF] = { .nuops = 1, .uops = { { LOAD_DEREF, 0, 0 } } }, - [STORE_DEREF] = { .nuops = 1, .uops = { { STORE_DEREF, 0, 0 } } }, - [COPY_FREE_VARS] = { .nuops = 1, .uops = { { COPY_FREE_VARS, 0, 0 } } }, - [BUILD_STRING] = { .nuops = 1, .uops = { { BUILD_STRING, 0, 0 } } }, - [BUILD_TUPLE] = { .nuops = 1, .uops = { { BUILD_TUPLE, 0, 0 } } }, + [BUILD_CONST_KEY_MAP] = { .nuops = 1, .uops = { { BUILD_CONST_KEY_MAP, 0, 0 } } }, [BUILD_LIST] = { .nuops = 1, .uops = { { BUILD_LIST, 0, 0 } } }, - [LIST_EXTEND] = { .nuops = 1, .uops = { { LIST_EXTEND, 0, 0 } } }, - [SET_UPDATE] = { .nuops = 1, .uops = { { SET_UPDATE, 0, 0 } } }, - [BUILD_SET] = { .nuops = 1, .uops = { { BUILD_SET, 0, 0 } } }, [BUILD_MAP] = { .nuops = 1, .uops = { { BUILD_MAP, 0, 0 } } }, - [SETUP_ANNOTATIONS] = { .nuops = 1, .uops = { { SETUP_ANNOTATIONS, 0, 0 } } }, - [BUILD_CONST_KEY_MAP] = { .nuops = 1, .uops = { { BUILD_CONST_KEY_MAP, 0, 0 } } }, - [DICT_UPDATE] = { .nuops = 1, .uops = { { DICT_UPDATE, 0, 0 } } }, - [DICT_MERGE] = { .nuops = 1, .uops = { { DICT_MERGE, 0, 0 } } }, - [MAP_ADD] = { .nuops = 1, .uops = { { MAP_ADD, 0, 0 } } }, - [LOAD_SUPER_ATTR_ATTR] = { .nuops = 1, .uops = { { LOAD_SUPER_ATTR_ATTR, 0, 0 } } }, - [LOAD_SUPER_ATTR_METHOD] = { .nuops = 1, .uops = { { LOAD_SUPER_ATTR_METHOD, 0, 0 } } }, - [LOAD_ATTR] = { .nuops = 1, .uops = { { _LOAD_ATTR, 0, 0 } } }, - [LOAD_ATTR_INSTANCE_VALUE] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_MANAGED_OBJECT_HAS_VALUES, 0, 0 }, { _LOAD_ATTR_INSTANCE_VALUE, 1, 3 } } }, - [LOAD_ATTR_MODULE] = { .nuops = 2, .uops = { { _CHECK_ATTR_MODULE, 2, 1 }, { _LOAD_ATTR_MODULE, 1, 3 } } }, - [LOAD_ATTR_WITH_HINT] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_ATTR_WITH_HINT, 0, 0 }, { _LOAD_ATTR_WITH_HINT, 1, 3 } } }, - [LOAD_ATTR_SLOT] = { .nuops = 2, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _LOAD_ATTR_SLOT, 1, 3 } } }, - [LOAD_ATTR_CLASS] = { .nuops = 2, .uops = { { _CHECK_ATTR_CLASS, 2, 1 }, { _LOAD_ATTR_CLASS, 4, 5 } } }, - [STORE_ATTR_INSTANCE_VALUE] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _GUARD_DORV_VALUES, 0, 0 }, { _STORE_ATTR_INSTANCE_VALUE, 1, 3 } } }, - [STORE_ATTR_SLOT] = { .nuops = 2, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _STORE_ATTR_SLOT, 1, 3 } } }, + [BUILD_SET] = { .nuops = 1, .uops = { { BUILD_SET, 0, 0 } } }, + [BUILD_SLICE] = { .nuops = 1, .uops = { { BUILD_SLICE, 0, 0 } } }, + [BUILD_STRING] = { .nuops = 1, .uops = { { BUILD_STRING, 0, 0 } } }, + [BUILD_TUPLE] = { .nuops = 1, .uops = { { BUILD_TUPLE, 0, 0 } } }, + [CALL_BOUND_METHOD_EXACT_ARGS] = { .nuops = 8, .uops = { { _CHECK_PEP_523, 0, 0 }, { _CHECK_CALL_BOUND_METHOD_EXACT_ARGS, 0, 0 }, { _INIT_CALL_BOUND_METHOD_EXACT_ARGS, 0, 0 }, { _CHECK_FUNCTION_EXACT_ARGS, 2, 1 }, { _CHECK_STACK_SPACE, 0, 0 }, { _INIT_CALL_PY_EXACT_ARGS, 0, 0 }, { _SAVE_RETURN_OFFSET, 7, 3 }, { _PUSH_FRAME, 0, 0 } } }, + [CALL_BUILTIN_CLASS] = { .nuops = 1, .uops = { { CALL_BUILTIN_CLASS, 0, 0 } } }, + [CALL_BUILTIN_FAST] = { .nuops = 1, .uops = { { CALL_BUILTIN_FAST, 0, 0 } } }, + [CALL_BUILTIN_FAST_WITH_KEYWORDS] = { .nuops = 1, .uops = { { CALL_BUILTIN_FAST_WITH_KEYWORDS, 0, 0 } } }, + [CALL_BUILTIN_O] = { .nuops = 1, .uops = { { CALL_BUILTIN_O, 0, 0 } } }, + [CALL_INTRINSIC_1] = { .nuops = 1, .uops = { { CALL_INTRINSIC_1, 0, 0 } } }, + [CALL_INTRINSIC_2] = { .nuops = 1, .uops = { { CALL_INTRINSIC_2, 0, 0 } } }, + [CALL_ISINSTANCE] = { .nuops = 1, .uops = { { CALL_ISINSTANCE, 0, 0 } } }, + [CALL_LEN] = { .nuops = 1, .uops = { { CALL_LEN, 0, 0 } } }, + [CALL_METHOD_DESCRIPTOR_FAST] = { .nuops = 1, .uops = { { CALL_METHOD_DESCRIPTOR_FAST, 0, 0 } } }, + [CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = { .nuops = 1, .uops = { { CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS, 0, 0 } } }, + [CALL_METHOD_DESCRIPTOR_NOARGS] = { .nuops = 1, .uops = { { CALL_METHOD_DESCRIPTOR_NOARGS, 0, 0 } } }, + [CALL_METHOD_DESCRIPTOR_O] = { .nuops = 1, .uops = { { CALL_METHOD_DESCRIPTOR_O, 0, 0 } } }, + [CALL_PY_EXACT_ARGS] = { .nuops = 6, .uops = { { _CHECK_PEP_523, 0, 0 }, { _CHECK_FUNCTION_EXACT_ARGS, 2, 1 }, { _CHECK_STACK_SPACE, 0, 0 }, { _INIT_CALL_PY_EXACT_ARGS, 0, 0 }, { _SAVE_RETURN_OFFSET, 7, 3 }, { _PUSH_FRAME, 0, 0 } } }, + [CALL_STR_1] = { .nuops = 1, .uops = { { CALL_STR_1, 0, 0 } } }, + [CALL_TUPLE_1] = { .nuops = 1, .uops = { { CALL_TUPLE_1, 0, 0 } } }, + [CALL_TYPE_1] = { .nuops = 1, .uops = { { CALL_TYPE_1, 0, 0 } } }, + [CHECK_EG_MATCH] = { .nuops = 1, .uops = { { CHECK_EG_MATCH, 0, 0 } } }, + [CHECK_EXC_MATCH] = { .nuops = 1, .uops = { { CHECK_EXC_MATCH, 0, 0 } } }, [COMPARE_OP] = { .nuops = 1, .uops = { { _COMPARE_OP, 0, 0 } } }, [COMPARE_OP_FLOAT] = { .nuops = 1, .uops = { { COMPARE_OP_FLOAT, 0, 0 } } }, [COMPARE_OP_INT] = { .nuops = 1, .uops = { { COMPARE_OP_INT, 0, 0 } } }, [COMPARE_OP_STR] = { .nuops = 1, .uops = { { COMPARE_OP_STR, 0, 0 } } }, - [IS_OP] = { .nuops = 1, .uops = { { IS_OP, 0, 0 } } }, [CONTAINS_OP] = { .nuops = 1, .uops = { { CONTAINS_OP, 0, 0 } } }, - [CHECK_EG_MATCH] = { .nuops = 1, .uops = { { CHECK_EG_MATCH, 0, 0 } } }, - [CHECK_EXC_MATCH] = { .nuops = 1, .uops = { { CHECK_EXC_MATCH, 0, 0 } } }, - [POP_JUMP_IF_TRUE] = { .nuops = 1, .uops = { { _POP_JUMP_IF_TRUE, 0, 0 } } }, - [POP_JUMP_IF_FALSE] = { .nuops = 1, .uops = { { _POP_JUMP_IF_FALSE, 0, 0 } } }, - [POP_JUMP_IF_NONE] = { .nuops = 2, .uops = { { _IS_NONE, 0, 0 }, { _POP_JUMP_IF_TRUE, 0, 0 } } }, - [POP_JUMP_IF_NOT_NONE] = { .nuops = 2, .uops = { { _IS_NONE, 0, 0 }, { _POP_JUMP_IF_FALSE, 0, 0 } } }, - [GET_LEN] = { .nuops = 1, .uops = { { GET_LEN, 0, 0 } } }, - [MATCH_CLASS] = { .nuops = 1, .uops = { { MATCH_CLASS, 0, 0 } } }, - [MATCH_MAPPING] = { .nuops = 1, .uops = { { MATCH_MAPPING, 0, 0 } } }, - [MATCH_SEQUENCE] = { .nuops = 1, .uops = { { MATCH_SEQUENCE, 0, 0 } } }, - [MATCH_KEYS] = { .nuops = 1, .uops = { { MATCH_KEYS, 0, 0 } } }, - [GET_ITER] = { .nuops = 1, .uops = { { GET_ITER, 0, 0 } } }, - [GET_YIELD_FROM_ITER] = { .nuops = 1, .uops = { { GET_YIELD_FROM_ITER, 0, 0 } } }, + [CONVERT_VALUE] = { .nuops = 1, .uops = { { CONVERT_VALUE, 0, 0 } } }, + [COPY] = { .nuops = 1, .uops = { { COPY, 0, 0 } } }, + [COPY_FREE_VARS] = { .nuops = 1, .uops = { { COPY_FREE_VARS, 0, 0 } } }, + [DELETE_ATTR] = { .nuops = 1, .uops = { { DELETE_ATTR, 0, 0 } } }, + [DELETE_DEREF] = { .nuops = 1, .uops = { { DELETE_DEREF, 0, 0 } } }, + [DELETE_FAST] = { .nuops = 1, .uops = { { DELETE_FAST, 0, 0 } } }, + [DELETE_GLOBAL] = { .nuops = 1, .uops = { { DELETE_GLOBAL, 0, 0 } } }, + [DELETE_NAME] = { .nuops = 1, .uops = { { DELETE_NAME, 0, 0 } } }, + [DELETE_SUBSCR] = { .nuops = 1, .uops = { { DELETE_SUBSCR, 0, 0 } } }, + [DICT_MERGE] = { .nuops = 1, .uops = { { DICT_MERGE, 0, 0 } } }, + [DICT_UPDATE] = { .nuops = 1, .uops = { { DICT_UPDATE, 0, 0 } } }, + [END_FOR] = { .nuops = 2, .uops = { { POP_TOP, 0, 0 }, { POP_TOP, 0, 0 } } }, + [END_SEND] = { .nuops = 1, .uops = { { END_SEND, 0, 0 } } }, + [EXIT_INIT_CHECK] = { .nuops = 1, .uops = { { EXIT_INIT_CHECK, 0, 0 } } }, + [FORMAT_SIMPLE] = { .nuops = 1, .uops = { { FORMAT_SIMPLE, 0, 0 } } }, + [FORMAT_WITH_SPEC] = { .nuops = 1, .uops = { { FORMAT_WITH_SPEC, 0, 0 } } }, [FOR_ITER] = { .nuops = 1, .uops = { { _FOR_ITER, 0, 0 } } }, [FOR_ITER_LIST] = { .nuops = 3, .uops = { { _ITER_CHECK_LIST, 0, 0 }, { _ITER_JUMP_LIST, 0, 0 }, { _ITER_NEXT_LIST, 0, 0 } } }, - [FOR_ITER_TUPLE] = { .nuops = 3, .uops = { { _ITER_CHECK_TUPLE, 0, 0 }, { _ITER_JUMP_TUPLE, 0, 0 }, { _ITER_NEXT_TUPLE, 0, 0 } } }, [FOR_ITER_RANGE] = { .nuops = 3, .uops = { { _ITER_CHECK_RANGE, 0, 0 }, { _ITER_JUMP_RANGE, 0, 0 }, { _ITER_NEXT_RANGE, 0, 0 } } }, - [BEFORE_ASYNC_WITH] = { .nuops = 1, .uops = { { BEFORE_ASYNC_WITH, 0, 0 } } }, - [BEFORE_WITH] = { .nuops = 1, .uops = { { BEFORE_WITH, 0, 0 } } }, - [WITH_EXCEPT_START] = { .nuops = 1, .uops = { { WITH_EXCEPT_START, 0, 0 } } }, - [PUSH_EXC_INFO] = { .nuops = 1, .uops = { { PUSH_EXC_INFO, 0, 0 } } }, - [LOAD_ATTR_METHOD_WITH_VALUES] = { .nuops = 4, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT, 0, 0 }, { _GUARD_KEYS_VERSION, 2, 3 }, { _LOAD_ATTR_METHOD_WITH_VALUES, 4, 5 } } }, + [FOR_ITER_TUPLE] = { .nuops = 3, .uops = { { _ITER_CHECK_TUPLE, 0, 0 }, { _ITER_JUMP_TUPLE, 0, 0 }, { _ITER_NEXT_TUPLE, 0, 0 } } }, + [GET_AITER] = { .nuops = 1, .uops = { { GET_AITER, 0, 0 } } }, + [GET_ANEXT] = { .nuops = 1, .uops = { { GET_ANEXT, 0, 0 } } }, + [GET_AWAITABLE] = { .nuops = 1, .uops = { { GET_AWAITABLE, 0, 0 } } }, + [GET_ITER] = { .nuops = 1, .uops = { { GET_ITER, 0, 0 } } }, + [GET_LEN] = { .nuops = 1, .uops = { { GET_LEN, 0, 0 } } }, + [GET_YIELD_FROM_ITER] = { .nuops = 1, .uops = { { GET_YIELD_FROM_ITER, 0, 0 } } }, + [IS_OP] = { .nuops = 1, .uops = { { IS_OP, 0, 0 } } }, + [LIST_APPEND] = { .nuops = 1, .uops = { { LIST_APPEND, 0, 0 } } }, + [LIST_EXTEND] = { .nuops = 1, .uops = { { LIST_EXTEND, 0, 0 } } }, + [LOAD_ASSERTION_ERROR] = { .nuops = 1, .uops = { { LOAD_ASSERTION_ERROR, 0, 0 } } }, + [LOAD_ATTR] = { .nuops = 1, .uops = { { _LOAD_ATTR, 0, 0 } } }, + [LOAD_ATTR_CLASS] = { .nuops = 2, .uops = { { _CHECK_ATTR_CLASS, 2, 1 }, { _LOAD_ATTR_CLASS, 4, 5 } } }, + [LOAD_ATTR_INSTANCE_VALUE] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_MANAGED_OBJECT_HAS_VALUES, 0, 0 }, { _LOAD_ATTR_INSTANCE_VALUE, 1, 3 } } }, + [LOAD_ATTR_METHOD_LAZY_DICT] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_ATTR_METHOD_LAZY_DICT, 0, 0 }, { _LOAD_ATTR_METHOD_LAZY_DICT, 4, 5 } } }, [LOAD_ATTR_METHOD_NO_DICT] = { .nuops = 2, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _LOAD_ATTR_METHOD_NO_DICT, 4, 5 } } }, - [LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = { .nuops = 4, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT, 0, 0 }, { _GUARD_KEYS_VERSION, 2, 3 }, { _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES, 4, 5 } } }, + [LOAD_ATTR_METHOD_WITH_VALUES] = { .nuops = 4, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT, 0, 0 }, { _GUARD_KEYS_VERSION, 2, 3 }, { _LOAD_ATTR_METHOD_WITH_VALUES, 4, 5 } } }, + [LOAD_ATTR_MODULE] = { .nuops = 2, .uops = { { _CHECK_ATTR_MODULE, 2, 1 }, { _LOAD_ATTR_MODULE, 1, 3 } } }, [LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = { .nuops = 2, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _LOAD_ATTR_NONDESCRIPTOR_NO_DICT, 4, 5 } } }, - [LOAD_ATTR_METHOD_LAZY_DICT] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_ATTR_METHOD_LAZY_DICT, 0, 0 }, { _LOAD_ATTR_METHOD_LAZY_DICT, 4, 5 } } }, - [CALL_BOUND_METHOD_EXACT_ARGS] = { .nuops = 8, .uops = { { _CHECK_PEP_523, 0, 0 }, { _CHECK_CALL_BOUND_METHOD_EXACT_ARGS, 0, 0 }, { _INIT_CALL_BOUND_METHOD_EXACT_ARGS, 0, 0 }, { _CHECK_FUNCTION_EXACT_ARGS, 2, 1 }, { _CHECK_STACK_SPACE, 0, 0 }, { _INIT_CALL_PY_EXACT_ARGS, 0, 0 }, { _SAVE_RETURN_OFFSET, 7, 3 }, { _PUSH_FRAME, 0, 0 } } }, - [CALL_PY_EXACT_ARGS] = { .nuops = 6, .uops = { { _CHECK_PEP_523, 0, 0 }, { _CHECK_FUNCTION_EXACT_ARGS, 2, 1 }, { _CHECK_STACK_SPACE, 0, 0 }, { _INIT_CALL_PY_EXACT_ARGS, 0, 0 }, { _SAVE_RETURN_OFFSET, 7, 3 }, { _PUSH_FRAME, 0, 0 } } }, - [CALL_TYPE_1] = { .nuops = 1, .uops = { { CALL_TYPE_1, 0, 0 } } }, - [CALL_STR_1] = { .nuops = 1, .uops = { { CALL_STR_1, 0, 0 } } }, - [CALL_TUPLE_1] = { .nuops = 1, .uops = { { CALL_TUPLE_1, 0, 0 } } }, - [EXIT_INIT_CHECK] = { .nuops = 1, .uops = { { EXIT_INIT_CHECK, 0, 0 } } }, - [CALL_BUILTIN_CLASS] = { .nuops = 1, .uops = { { CALL_BUILTIN_CLASS, 0, 0 } } }, - [CALL_BUILTIN_O] = { .nuops = 1, .uops = { { CALL_BUILTIN_O, 0, 0 } } }, - [CALL_BUILTIN_FAST] = { .nuops = 1, .uops = { { CALL_BUILTIN_FAST, 0, 0 } } }, - [CALL_BUILTIN_FAST_WITH_KEYWORDS] = { .nuops = 1, .uops = { { CALL_BUILTIN_FAST_WITH_KEYWORDS, 0, 0 } } }, - [CALL_LEN] = { .nuops = 1, .uops = { { CALL_LEN, 0, 0 } } }, - [CALL_ISINSTANCE] = { .nuops = 1, .uops = { { CALL_ISINSTANCE, 0, 0 } } }, - [CALL_METHOD_DESCRIPTOR_O] = { .nuops = 1, .uops = { { CALL_METHOD_DESCRIPTOR_O, 0, 0 } } }, - [CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = { .nuops = 1, .uops = { { CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS, 0, 0 } } }, - [CALL_METHOD_DESCRIPTOR_NOARGS] = { .nuops = 1, .uops = { { CALL_METHOD_DESCRIPTOR_NOARGS, 0, 0 } } }, - [CALL_METHOD_DESCRIPTOR_FAST] = { .nuops = 1, .uops = { { CALL_METHOD_DESCRIPTOR_FAST, 0, 0 } } }, + [LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = { .nuops = 4, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT, 0, 0 }, { _GUARD_KEYS_VERSION, 2, 3 }, { _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES, 4, 5 } } }, + [LOAD_ATTR_SLOT] = { .nuops = 2, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _LOAD_ATTR_SLOT, 1, 3 } } }, + [LOAD_ATTR_WITH_HINT] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_ATTR_WITH_HINT, 0, 0 }, { _LOAD_ATTR_WITH_HINT, 1, 3 } } }, + [LOAD_BUILD_CLASS] = { .nuops = 1, .uops = { { LOAD_BUILD_CLASS, 0, 0 } } }, + [LOAD_CONST] = { .nuops = 1, .uops = { { LOAD_CONST, 0, 0 } } }, + [LOAD_DEREF] = { .nuops = 1, .uops = { { LOAD_DEREF, 0, 0 } } }, + [LOAD_FAST] = { .nuops = 1, .uops = { { LOAD_FAST, 0, 0 } } }, + [LOAD_FAST_AND_CLEAR] = { .nuops = 1, .uops = { { LOAD_FAST_AND_CLEAR, 0, 0 } } }, + [LOAD_FAST_CHECK] = { .nuops = 1, .uops = { { LOAD_FAST_CHECK, 0, 0 } } }, + [LOAD_FAST_LOAD_FAST] = { .nuops = 2, .uops = { { LOAD_FAST, 5, 0 }, { LOAD_FAST, 6, 0 } } }, + [LOAD_FROM_DICT_OR_DEREF] = { .nuops = 1, .uops = { { LOAD_FROM_DICT_OR_DEREF, 0, 0 } } }, + [LOAD_FROM_DICT_OR_GLOBALS] = { .nuops = 1, .uops = { { LOAD_FROM_DICT_OR_GLOBALS, 0, 0 } } }, + [LOAD_GLOBAL] = { .nuops = 1, .uops = { { _LOAD_GLOBAL, 0, 0 } } }, + [LOAD_GLOBAL_BUILTIN] = { .nuops = 3, .uops = { { _GUARD_GLOBALS_VERSION, 1, 1 }, { _GUARD_BUILTINS_VERSION, 1, 2 }, { _LOAD_GLOBAL_BUILTINS, 1, 3 } } }, + [LOAD_GLOBAL_MODULE] = { .nuops = 2, .uops = { { _GUARD_GLOBALS_VERSION, 1, 1 }, { _LOAD_GLOBAL_MODULE, 1, 3 } } }, + [LOAD_LOCALS] = { .nuops = 1, .uops = { { LOAD_LOCALS, 0, 0 } } }, + [LOAD_NAME] = { .nuops = 1, .uops = { { LOAD_NAME, 0, 0 } } }, + [LOAD_SUPER_ATTR_ATTR] = { .nuops = 1, .uops = { { LOAD_SUPER_ATTR_ATTR, 0, 0 } } }, + [LOAD_SUPER_ATTR_METHOD] = { .nuops = 1, .uops = { { LOAD_SUPER_ATTR_METHOD, 0, 0 } } }, + [MAKE_CELL] = { .nuops = 1, .uops = { { MAKE_CELL, 0, 0 } } }, [MAKE_FUNCTION] = { .nuops = 1, .uops = { { MAKE_FUNCTION, 0, 0 } } }, + [MAP_ADD] = { .nuops = 1, .uops = { { MAP_ADD, 0, 0 } } }, + [MATCH_CLASS] = { .nuops = 1, .uops = { { MATCH_CLASS, 0, 0 } } }, + [MATCH_KEYS] = { .nuops = 1, .uops = { { MATCH_KEYS, 0, 0 } } }, + [MATCH_MAPPING] = { .nuops = 1, .uops = { { MATCH_MAPPING, 0, 0 } } }, + [MATCH_SEQUENCE] = { .nuops = 1, .uops = { { MATCH_SEQUENCE, 0, 0 } } }, + [NOP] = { .nuops = 1, .uops = { { NOP, 0, 0 } } }, + [POP_EXCEPT] = { .nuops = 1, .uops = { { POP_EXCEPT, 0, 0 } } }, + [POP_JUMP_IF_FALSE] = { .nuops = 1, .uops = { { _POP_JUMP_IF_FALSE, 0, 0 } } }, + [POP_JUMP_IF_NONE] = { .nuops = 2, .uops = { { _IS_NONE, 0, 0 }, { _POP_JUMP_IF_TRUE, 0, 0 } } }, + [POP_JUMP_IF_NOT_NONE] = { .nuops = 2, .uops = { { _IS_NONE, 0, 0 }, { _POP_JUMP_IF_FALSE, 0, 0 } } }, + [POP_JUMP_IF_TRUE] = { .nuops = 1, .uops = { { _POP_JUMP_IF_TRUE, 0, 0 } } }, + [POP_TOP] = { .nuops = 1, .uops = { { POP_TOP, 0, 0 } } }, + [PUSH_EXC_INFO] = { .nuops = 1, .uops = { { PUSH_EXC_INFO, 0, 0 } } }, + [PUSH_NULL] = { .nuops = 1, .uops = { { PUSH_NULL, 0, 0 } } }, + [RESUME_CHECK] = { .nuops = 1, .uops = { { RESUME_CHECK, 0, 0 } } }, + [RETURN_CONST] = { .nuops = 2, .uops = { { LOAD_CONST, 0, 0 }, { _POP_FRAME, 0, 0 } } }, + [RETURN_VALUE] = { .nuops = 1, .uops = { { _POP_FRAME, 0, 0 } } }, + [SETUP_ANNOTATIONS] = { .nuops = 1, .uops = { { SETUP_ANNOTATIONS, 0, 0 } } }, + [SET_ADD] = { .nuops = 1, .uops = { { SET_ADD, 0, 0 } } }, [SET_FUNCTION_ATTRIBUTE] = { .nuops = 1, .uops = { { SET_FUNCTION_ATTRIBUTE, 0, 0 } } }, - [BUILD_SLICE] = { .nuops = 1, .uops = { { BUILD_SLICE, 0, 0 } } }, - [CONVERT_VALUE] = { .nuops = 1, .uops = { { CONVERT_VALUE, 0, 0 } } }, - [FORMAT_SIMPLE] = { .nuops = 1, .uops = { { FORMAT_SIMPLE, 0, 0 } } }, - [FORMAT_WITH_SPEC] = { .nuops = 1, .uops = { { FORMAT_WITH_SPEC, 0, 0 } } }, - [COPY] = { .nuops = 1, .uops = { { COPY, 0, 0 } } }, - [BINARY_OP] = { .nuops = 1, .uops = { { _BINARY_OP, 0, 0 } } }, + [SET_UPDATE] = { .nuops = 1, .uops = { { SET_UPDATE, 0, 0 } } }, + [STORE_ATTR] = { .nuops = 1, .uops = { { _STORE_ATTR, 0, 0 } } }, + [STORE_ATTR_INSTANCE_VALUE] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _GUARD_DORV_VALUES, 0, 0 }, { _STORE_ATTR_INSTANCE_VALUE, 1, 3 } } }, + [STORE_ATTR_SLOT] = { .nuops = 2, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _STORE_ATTR_SLOT, 1, 3 } } }, + [STORE_DEREF] = { .nuops = 1, .uops = { { STORE_DEREF, 0, 0 } } }, + [STORE_FAST] = { .nuops = 1, .uops = { { STORE_FAST, 0, 0 } } }, + [STORE_FAST_LOAD_FAST] = { .nuops = 2, .uops = { { STORE_FAST, 5, 0 }, { LOAD_FAST, 6, 0 } } }, + [STORE_FAST_STORE_FAST] = { .nuops = 2, .uops = { { STORE_FAST, 5, 0 }, { STORE_FAST, 6, 0 } } }, + [STORE_GLOBAL] = { .nuops = 1, .uops = { { STORE_GLOBAL, 0, 0 } } }, + [STORE_NAME] = { .nuops = 1, .uops = { { STORE_NAME, 0, 0 } } }, + [STORE_SLICE] = { .nuops = 1, .uops = { { STORE_SLICE, 0, 0 } } }, + [STORE_SUBSCR] = { .nuops = 1, .uops = { { _STORE_SUBSCR, 0, 0 } } }, + [STORE_SUBSCR_DICT] = { .nuops = 1, .uops = { { STORE_SUBSCR_DICT, 0, 0 } } }, + [STORE_SUBSCR_LIST_INT] = { .nuops = 1, .uops = { { STORE_SUBSCR_LIST_INT, 0, 0 } } }, [SWAP] = { .nuops = 1, .uops = { { SWAP, 0, 0 } } }, + [TO_BOOL] = { .nuops = 1, .uops = { { _TO_BOOL, 0, 0 } } }, + [TO_BOOL_ALWAYS_TRUE] = { .nuops = 1, .uops = { { TO_BOOL_ALWAYS_TRUE, 2, 1 } } }, + [TO_BOOL_BOOL] = { .nuops = 1, .uops = { { TO_BOOL_BOOL, 0, 0 } } }, + [TO_BOOL_INT] = { .nuops = 1, .uops = { { TO_BOOL_INT, 0, 0 } } }, + [TO_BOOL_LIST] = { .nuops = 1, .uops = { { TO_BOOL_LIST, 0, 0 } } }, + [TO_BOOL_NONE] = { .nuops = 1, .uops = { { TO_BOOL_NONE, 0, 0 } } }, + [TO_BOOL_STR] = { .nuops = 1, .uops = { { TO_BOOL_STR, 0, 0 } } }, + [UNARY_INVERT] = { .nuops = 1, .uops = { { UNARY_INVERT, 0, 0 } } }, + [UNARY_NEGATIVE] = { .nuops = 1, .uops = { { UNARY_NEGATIVE, 0, 0 } } }, + [UNARY_NOT] = { .nuops = 1, .uops = { { UNARY_NOT, 0, 0 } } }, + [UNPACK_EX] = { .nuops = 1, .uops = { { UNPACK_EX, 0, 0 } } }, + [UNPACK_SEQUENCE] = { .nuops = 1, .uops = { { _UNPACK_SEQUENCE, 0, 0 } } }, + [UNPACK_SEQUENCE_LIST] = { .nuops = 1, .uops = { { UNPACK_SEQUENCE_LIST, 0, 0 } } }, + [UNPACK_SEQUENCE_TUPLE] = { .nuops = 1, .uops = { { UNPACK_SEQUENCE_TUPLE, 0, 0 } } }, + [UNPACK_SEQUENCE_TWO_TUPLE] = { .nuops = 1, .uops = { { UNPACK_SEQUENCE_TWO_TUPLE, 0, 0 } } }, + [WITH_EXCEPT_START] = { .nuops = 1, .uops = { { WITH_EXCEPT_START, 0, 0 } } }, }; #endif // NEED_OPCODE_METADATA @@ -1863,153 +1863,124 @@ extern const char * const _PyOpcode_uop_name[OPCODE_UOP_NAME_SIZE]; const char * const _PyOpcode_uop_name[OPCODE_UOP_NAME_SIZE] = { [_EXIT_TRACE] = "_EXIT_TRACE", [_SET_IP] = "_SET_IP", - [_SPECIALIZE_TO_BOOL] = "_SPECIALIZE_TO_BOOL", - [_TO_BOOL] = "_TO_BOOL", - [_GUARD_BOTH_INT] = "_GUARD_BOTH_INT", - [_BINARY_OP_MULTIPLY_INT] = "_BINARY_OP_MULTIPLY_INT", - [_BINARY_OP_ADD_INT] = "_BINARY_OP_ADD_INT", - [_BINARY_OP_SUBTRACT_INT] = "_BINARY_OP_SUBTRACT_INT", - [_GUARD_BOTH_FLOAT] = "_GUARD_BOTH_FLOAT", - [_BINARY_OP_MULTIPLY_FLOAT] = "_BINARY_OP_MULTIPLY_FLOAT", + [_BINARY_OP] = "_BINARY_OP", [_BINARY_OP_ADD_FLOAT] = "_BINARY_OP_ADD_FLOAT", - [_BINARY_OP_SUBTRACT_FLOAT] = "_BINARY_OP_SUBTRACT_FLOAT", - [_GUARD_BOTH_UNICODE] = "_GUARD_BOTH_UNICODE", + [_BINARY_OP_ADD_INT] = "_BINARY_OP_ADD_INT", [_BINARY_OP_ADD_UNICODE] = "_BINARY_OP_ADD_UNICODE", [_BINARY_OP_INPLACE_ADD_UNICODE] = "_BINARY_OP_INPLACE_ADD_UNICODE", - [_SPECIALIZE_BINARY_SUBSCR] = "_SPECIALIZE_BINARY_SUBSCR", + [_BINARY_OP_MULTIPLY_FLOAT] = "_BINARY_OP_MULTIPLY_FLOAT", + [_BINARY_OP_MULTIPLY_INT] = "_BINARY_OP_MULTIPLY_INT", + [_BINARY_OP_SUBTRACT_FLOAT] = "_BINARY_OP_SUBTRACT_FLOAT", + [_BINARY_OP_SUBTRACT_INT] = "_BINARY_OP_SUBTRACT_INT", [_BINARY_SUBSCR] = "_BINARY_SUBSCR", - [_SPECIALIZE_STORE_SUBSCR] = "_SPECIALIZE_STORE_SUBSCR", - [_STORE_SUBSCR] = "_STORE_SUBSCR", - [_POP_FRAME] = "_POP_FRAME", - [_SPECIALIZE_SEND] = "_SPECIALIZE_SEND", - [_SEND] = "_SEND", - [_SPECIALIZE_UNPACK_SEQUENCE] = "_SPECIALIZE_UNPACK_SEQUENCE", - [_UNPACK_SEQUENCE] = "_UNPACK_SEQUENCE", - [_SPECIALIZE_STORE_ATTR] = "_SPECIALIZE_STORE_ATTR", - [_STORE_ATTR] = "_STORE_ATTR", - [_SPECIALIZE_LOAD_GLOBAL] = "_SPECIALIZE_LOAD_GLOBAL", - [_LOAD_GLOBAL] = "_LOAD_GLOBAL", - [_GUARD_GLOBALS_VERSION] = "_GUARD_GLOBALS_VERSION", - [_GUARD_BUILTINS_VERSION] = "_GUARD_BUILTINS_VERSION", - [_LOAD_GLOBAL_MODULE] = "_LOAD_GLOBAL_MODULE", - [_LOAD_GLOBAL_BUILTINS] = "_LOAD_GLOBAL_BUILTINS", - [_SPECIALIZE_LOAD_SUPER_ATTR] = "_SPECIALIZE_LOAD_SUPER_ATTR", - [_LOAD_SUPER_ATTR] = "_LOAD_SUPER_ATTR", - [_SPECIALIZE_LOAD_ATTR] = "_SPECIALIZE_LOAD_ATTR", - [_LOAD_ATTR] = "_LOAD_ATTR", - [_GUARD_TYPE_VERSION] = "_GUARD_TYPE_VERSION", - [_CHECK_MANAGED_OBJECT_HAS_VALUES] = "_CHECK_MANAGED_OBJECT_HAS_VALUES", - [_LOAD_ATTR_INSTANCE_VALUE] = "_LOAD_ATTR_INSTANCE_VALUE", + [_CALL] = "_CALL", + [_CHECK_ATTR_CLASS] = "_CHECK_ATTR_CLASS", + [_CHECK_ATTR_METHOD_LAZY_DICT] = "_CHECK_ATTR_METHOD_LAZY_DICT", [_CHECK_ATTR_MODULE] = "_CHECK_ATTR_MODULE", - [_LOAD_ATTR_MODULE] = "_LOAD_ATTR_MODULE", [_CHECK_ATTR_WITH_HINT] = "_CHECK_ATTR_WITH_HINT", - [_LOAD_ATTR_WITH_HINT] = "_LOAD_ATTR_WITH_HINT", - [_LOAD_ATTR_SLOT] = "_LOAD_ATTR_SLOT", - [_CHECK_ATTR_CLASS] = "_CHECK_ATTR_CLASS", - [_LOAD_ATTR_CLASS] = "_LOAD_ATTR_CLASS", - [_GUARD_DORV_VALUES] = "_GUARD_DORV_VALUES", - [_STORE_ATTR_INSTANCE_VALUE] = "_STORE_ATTR_INSTANCE_VALUE", - [_STORE_ATTR_SLOT] = "_STORE_ATTR_SLOT", - [_SPECIALIZE_COMPARE_OP] = "_SPECIALIZE_COMPARE_OP", + [_CHECK_CALL_BOUND_METHOD_EXACT_ARGS] = "_CHECK_CALL_BOUND_METHOD_EXACT_ARGS", + [_CHECK_FUNCTION_EXACT_ARGS] = "_CHECK_FUNCTION_EXACT_ARGS", + [_CHECK_MANAGED_OBJECT_HAS_VALUES] = "_CHECK_MANAGED_OBJECT_HAS_VALUES", + [_CHECK_PEP_523] = "_CHECK_PEP_523", + [_CHECK_STACK_SPACE] = "_CHECK_STACK_SPACE", + [_CHECK_VALIDITY] = "_CHECK_VALIDITY", [_COMPARE_OP] = "_COMPARE_OP", - [_POP_JUMP_IF_FALSE] = "_POP_JUMP_IF_FALSE", - [_POP_JUMP_IF_TRUE] = "_POP_JUMP_IF_TRUE", - [_IS_NONE] = "_IS_NONE", - [_SPECIALIZE_FOR_ITER] = "_SPECIALIZE_FOR_ITER", [_FOR_ITER] = "_FOR_ITER", [_FOR_ITER_TIER_TWO] = "_FOR_ITER_TIER_TWO", - [_ITER_CHECK_LIST] = "_ITER_CHECK_LIST", - [_ITER_JUMP_LIST] = "_ITER_JUMP_LIST", + [_GUARD_BOTH_FLOAT] = "_GUARD_BOTH_FLOAT", + [_GUARD_BOTH_INT] = "_GUARD_BOTH_INT", + [_GUARD_BOTH_UNICODE] = "_GUARD_BOTH_UNICODE", + [_GUARD_BUILTINS_VERSION] = "_GUARD_BUILTINS_VERSION", + [_GUARD_DORV_VALUES] = "_GUARD_DORV_VALUES", + [_GUARD_DORV_VALUES_INST_ATTR_FROM_DICT] = "_GUARD_DORV_VALUES_INST_ATTR_FROM_DICT", + [_GUARD_GLOBALS_VERSION] = "_GUARD_GLOBALS_VERSION", + [_GUARD_IS_FALSE_POP] = "_GUARD_IS_FALSE_POP", + [_GUARD_IS_NONE_POP] = "_GUARD_IS_NONE_POP", + [_GUARD_IS_NOT_NONE_POP] = "_GUARD_IS_NOT_NONE_POP", + [_GUARD_IS_TRUE_POP] = "_GUARD_IS_TRUE_POP", + [_GUARD_KEYS_VERSION] = "_GUARD_KEYS_VERSION", [_GUARD_NOT_EXHAUSTED_LIST] = "_GUARD_NOT_EXHAUSTED_LIST", - [_ITER_NEXT_LIST] = "_ITER_NEXT_LIST", - [_ITER_CHECK_TUPLE] = "_ITER_CHECK_TUPLE", - [_ITER_JUMP_TUPLE] = "_ITER_JUMP_TUPLE", + [_GUARD_NOT_EXHAUSTED_RANGE] = "_GUARD_NOT_EXHAUSTED_RANGE", [_GUARD_NOT_EXHAUSTED_TUPLE] = "_GUARD_NOT_EXHAUSTED_TUPLE", - [_ITER_NEXT_TUPLE] = "_ITER_NEXT_TUPLE", + [_GUARD_TYPE_VERSION] = "_GUARD_TYPE_VERSION", + [_INIT_CALL_BOUND_METHOD_EXACT_ARGS] = "_INIT_CALL_BOUND_METHOD_EXACT_ARGS", + [_INIT_CALL_PY_EXACT_ARGS] = "_INIT_CALL_PY_EXACT_ARGS", + [_INSERT] = "_INSERT", + [_IS_NONE] = "_IS_NONE", + [_ITER_CHECK_LIST] = "_ITER_CHECK_LIST", [_ITER_CHECK_RANGE] = "_ITER_CHECK_RANGE", + [_ITER_CHECK_TUPLE] = "_ITER_CHECK_TUPLE", + [_ITER_JUMP_LIST] = "_ITER_JUMP_LIST", [_ITER_JUMP_RANGE] = "_ITER_JUMP_RANGE", - [_GUARD_NOT_EXHAUSTED_RANGE] = "_GUARD_NOT_EXHAUSTED_RANGE", + [_ITER_JUMP_TUPLE] = "_ITER_JUMP_TUPLE", + [_ITER_NEXT_LIST] = "_ITER_NEXT_LIST", [_ITER_NEXT_RANGE] = "_ITER_NEXT_RANGE", - [_GUARD_DORV_VALUES_INST_ATTR_FROM_DICT] = "_GUARD_DORV_VALUES_INST_ATTR_FROM_DICT", - [_GUARD_KEYS_VERSION] = "_GUARD_KEYS_VERSION", - [_LOAD_ATTR_METHOD_WITH_VALUES] = "_LOAD_ATTR_METHOD_WITH_VALUES", + [_ITER_NEXT_TUPLE] = "_ITER_NEXT_TUPLE", + [_JUMP_TO_TOP] = "_JUMP_TO_TOP", + [_LOAD_ATTR] = "_LOAD_ATTR", + [_LOAD_ATTR_CLASS] = "_LOAD_ATTR_CLASS", + [_LOAD_ATTR_INSTANCE_VALUE] = "_LOAD_ATTR_INSTANCE_VALUE", + [_LOAD_ATTR_METHOD_LAZY_DICT] = "_LOAD_ATTR_METHOD_LAZY_DICT", [_LOAD_ATTR_METHOD_NO_DICT] = "_LOAD_ATTR_METHOD_NO_DICT", - [_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = "_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", + [_LOAD_ATTR_METHOD_WITH_VALUES] = "_LOAD_ATTR_METHOD_WITH_VALUES", + [_LOAD_ATTR_MODULE] = "_LOAD_ATTR_MODULE", [_LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = "_LOAD_ATTR_NONDESCRIPTOR_NO_DICT", - [_CHECK_ATTR_METHOD_LAZY_DICT] = "_CHECK_ATTR_METHOD_LAZY_DICT", - [_LOAD_ATTR_METHOD_LAZY_DICT] = "_LOAD_ATTR_METHOD_LAZY_DICT", - [_SPECIALIZE_CALL] = "_SPECIALIZE_CALL", - [_CALL] = "_CALL", - [_CHECK_CALL_BOUND_METHOD_EXACT_ARGS] = "_CHECK_CALL_BOUND_METHOD_EXACT_ARGS", - [_INIT_CALL_BOUND_METHOD_EXACT_ARGS] = "_INIT_CALL_BOUND_METHOD_EXACT_ARGS", - [_CHECK_PEP_523] = "_CHECK_PEP_523", - [_CHECK_FUNCTION_EXACT_ARGS] = "_CHECK_FUNCTION_EXACT_ARGS", - [_CHECK_STACK_SPACE] = "_CHECK_STACK_SPACE", - [_INIT_CALL_PY_EXACT_ARGS] = "_INIT_CALL_PY_EXACT_ARGS", + [_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = "_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", + [_LOAD_ATTR_SLOT] = "_LOAD_ATTR_SLOT", + [_LOAD_ATTR_WITH_HINT] = "_LOAD_ATTR_WITH_HINT", + [_LOAD_GLOBAL] = "_LOAD_GLOBAL", + [_LOAD_GLOBAL_BUILTINS] = "_LOAD_GLOBAL_BUILTINS", + [_LOAD_GLOBAL_MODULE] = "_LOAD_GLOBAL_MODULE", + [_LOAD_SUPER_ATTR] = "_LOAD_SUPER_ATTR", + [_POP_FRAME] = "_POP_FRAME", + [_POP_JUMP_IF_FALSE] = "_POP_JUMP_IF_FALSE", + [_POP_JUMP_IF_TRUE] = "_POP_JUMP_IF_TRUE", [_PUSH_FRAME] = "_PUSH_FRAME", - [_SPECIALIZE_BINARY_OP] = "_SPECIALIZE_BINARY_OP", - [_BINARY_OP] = "_BINARY_OP", - [_GUARD_IS_TRUE_POP] = "_GUARD_IS_TRUE_POP", - [_GUARD_IS_FALSE_POP] = "_GUARD_IS_FALSE_POP", - [_GUARD_IS_NONE_POP] = "_GUARD_IS_NONE_POP", - [_GUARD_IS_NOT_NONE_POP] = "_GUARD_IS_NOT_NONE_POP", - [_JUMP_TO_TOP] = "_JUMP_TO_TOP", [_SAVE_RETURN_OFFSET] = "_SAVE_RETURN_OFFSET", - [_INSERT] = "_INSERT", - [_CHECK_VALIDITY] = "_CHECK_VALIDITY", -}; -#endif // NEED_OPCODE_METADATA - -extern const char *const _PyOpcode_OpName[268]; -#ifdef NEED_OPCODE_METADATA -const char *const _PyOpcode_OpName[268] = { - [CACHE] = "CACHE", - [RESERVED] = "RESERVED", - [RESUME] = "RESUME", - [BEFORE_ASYNC_WITH] = "BEFORE_ASYNC_WITH", - [BEFORE_WITH] = "BEFORE_WITH", - [BINARY_OP_INPLACE_ADD_UNICODE] = "BINARY_OP_INPLACE_ADD_UNICODE", - [BINARY_SLICE] = "BINARY_SLICE", - [BINARY_SUBSCR] = "BINARY_SUBSCR", - [CHECK_EG_MATCH] = "CHECK_EG_MATCH", - [CHECK_EXC_MATCH] = "CHECK_EXC_MATCH", - [CLEANUP_THROW] = "CLEANUP_THROW", - [DELETE_SUBSCR] = "DELETE_SUBSCR", - [END_ASYNC_FOR] = "END_ASYNC_FOR", - [END_FOR] = "END_FOR", - [END_SEND] = "END_SEND", - [EXIT_INIT_CHECK] = "EXIT_INIT_CHECK", - [FORMAT_SIMPLE] = "FORMAT_SIMPLE", - [FORMAT_WITH_SPEC] = "FORMAT_WITH_SPEC", - [GET_AITER] = "GET_AITER", - [GET_ANEXT] = "GET_ANEXT", - [GET_ITER] = "GET_ITER", - [GET_LEN] = "GET_LEN", - [GET_YIELD_FROM_ITER] = "GET_YIELD_FROM_ITER", - [INTERPRETER_EXIT] = "INTERPRETER_EXIT", - [LOAD_ASSERTION_ERROR] = "LOAD_ASSERTION_ERROR", - [LOAD_BUILD_CLASS] = "LOAD_BUILD_CLASS", - [LOAD_LOCALS] = "LOAD_LOCALS", - [MAKE_FUNCTION] = "MAKE_FUNCTION", - [MATCH_KEYS] = "MATCH_KEYS", - [MATCH_MAPPING] = "MATCH_MAPPING", - [MATCH_SEQUENCE] = "MATCH_SEQUENCE", - [NOP] = "NOP", - [POP_EXCEPT] = "POP_EXCEPT", - [POP_TOP] = "POP_TOP", - [PUSH_EXC_INFO] = "PUSH_EXC_INFO", - [PUSH_NULL] = "PUSH_NULL", - [RETURN_GENERATOR] = "RETURN_GENERATOR", - [RETURN_VALUE] = "RETURN_VALUE", - [SETUP_ANNOTATIONS] = "SETUP_ANNOTATIONS", - [STORE_SLICE] = "STORE_SLICE", - [STORE_SUBSCR] = "STORE_SUBSCR", - [TO_BOOL] = "TO_BOOL", - [UNARY_INVERT] = "UNARY_INVERT", - [UNARY_NEGATIVE] = "UNARY_NEGATIVE", - [UNARY_NOT] = "UNARY_NOT", - [WITH_EXCEPT_START] = "WITH_EXCEPT_START", + [_SEND] = "_SEND", + [_SPECIALIZE_BINARY_OP] = "_SPECIALIZE_BINARY_OP", + [_SPECIALIZE_BINARY_SUBSCR] = "_SPECIALIZE_BINARY_SUBSCR", + [_SPECIALIZE_CALL] = "_SPECIALIZE_CALL", + [_SPECIALIZE_COMPARE_OP] = "_SPECIALIZE_COMPARE_OP", + [_SPECIALIZE_FOR_ITER] = "_SPECIALIZE_FOR_ITER", + [_SPECIALIZE_LOAD_ATTR] = "_SPECIALIZE_LOAD_ATTR", + [_SPECIALIZE_LOAD_GLOBAL] = "_SPECIALIZE_LOAD_GLOBAL", + [_SPECIALIZE_LOAD_SUPER_ATTR] = "_SPECIALIZE_LOAD_SUPER_ATTR", + [_SPECIALIZE_SEND] = "_SPECIALIZE_SEND", + [_SPECIALIZE_STORE_ATTR] = "_SPECIALIZE_STORE_ATTR", + [_SPECIALIZE_STORE_SUBSCR] = "_SPECIALIZE_STORE_SUBSCR", + [_SPECIALIZE_TO_BOOL] = "_SPECIALIZE_TO_BOOL", + [_SPECIALIZE_UNPACK_SEQUENCE] = "_SPECIALIZE_UNPACK_SEQUENCE", + [_STORE_ATTR] = "_STORE_ATTR", + [_STORE_ATTR_INSTANCE_VALUE] = "_STORE_ATTR_INSTANCE_VALUE", + [_STORE_ATTR_SLOT] = "_STORE_ATTR_SLOT", + [_STORE_SUBSCR] = "_STORE_SUBSCR", + [_TO_BOOL] = "_TO_BOOL", + [_UNPACK_SEQUENCE] = "_UNPACK_SEQUENCE", +}; +#endif // NEED_OPCODE_METADATA + +extern const char *const _PyOpcode_OpName[268]; +#ifdef NEED_OPCODE_METADATA +const char *const _PyOpcode_OpName[268] = { + [BEFORE_ASYNC_WITH] = "BEFORE_ASYNC_WITH", + [BEFORE_WITH] = "BEFORE_WITH", [BINARY_OP] = "BINARY_OP", + [BINARY_OP_ADD_FLOAT] = "BINARY_OP_ADD_FLOAT", + [BINARY_OP_ADD_INT] = "BINARY_OP_ADD_INT", + [BINARY_OP_ADD_UNICODE] = "BINARY_OP_ADD_UNICODE", + [BINARY_OP_INPLACE_ADD_UNICODE] = "BINARY_OP_INPLACE_ADD_UNICODE", + [BINARY_OP_MULTIPLY_FLOAT] = "BINARY_OP_MULTIPLY_FLOAT", + [BINARY_OP_MULTIPLY_INT] = "BINARY_OP_MULTIPLY_INT", + [BINARY_OP_SUBTRACT_FLOAT] = "BINARY_OP_SUBTRACT_FLOAT", + [BINARY_OP_SUBTRACT_INT] = "BINARY_OP_SUBTRACT_INT", + [BINARY_SLICE] = "BINARY_SLICE", + [BINARY_SUBSCR] = "BINARY_SUBSCR", + [BINARY_SUBSCR_DICT] = "BINARY_SUBSCR_DICT", + [BINARY_SUBSCR_GETITEM] = "BINARY_SUBSCR_GETITEM", + [BINARY_SUBSCR_LIST_INT] = "BINARY_SUBSCR_LIST_INT", + [BINARY_SUBSCR_STR_INT] = "BINARY_SUBSCR_STR_INT", + [BINARY_SUBSCR_TUPLE_INT] = "BINARY_SUBSCR_TUPLE_INT", [BUILD_CONST_KEY_MAP] = "BUILD_CONST_KEY_MAP", [BUILD_LIST] = "BUILD_LIST", [BUILD_MAP] = "BUILD_MAP", @@ -2017,12 +1988,37 @@ const char *const _PyOpcode_OpName[268] = { [BUILD_SLICE] = "BUILD_SLICE", [BUILD_STRING] = "BUILD_STRING", [BUILD_TUPLE] = "BUILD_TUPLE", + [CACHE] = "CACHE", [CALL] = "CALL", + [CALL_ALLOC_AND_ENTER_INIT] = "CALL_ALLOC_AND_ENTER_INIT", + [CALL_BOUND_METHOD_EXACT_ARGS] = "CALL_BOUND_METHOD_EXACT_ARGS", + [CALL_BUILTIN_CLASS] = "CALL_BUILTIN_CLASS", + [CALL_BUILTIN_FAST] = "CALL_BUILTIN_FAST", + [CALL_BUILTIN_FAST_WITH_KEYWORDS] = "CALL_BUILTIN_FAST_WITH_KEYWORDS", + [CALL_BUILTIN_O] = "CALL_BUILTIN_O", [CALL_FUNCTION_EX] = "CALL_FUNCTION_EX", [CALL_INTRINSIC_1] = "CALL_INTRINSIC_1", [CALL_INTRINSIC_2] = "CALL_INTRINSIC_2", + [CALL_ISINSTANCE] = "CALL_ISINSTANCE", [CALL_KW] = "CALL_KW", + [CALL_LEN] = "CALL_LEN", + [CALL_LIST_APPEND] = "CALL_LIST_APPEND", + [CALL_METHOD_DESCRIPTOR_FAST] = "CALL_METHOD_DESCRIPTOR_FAST", + [CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = "CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS", + [CALL_METHOD_DESCRIPTOR_NOARGS] = "CALL_METHOD_DESCRIPTOR_NOARGS", + [CALL_METHOD_DESCRIPTOR_O] = "CALL_METHOD_DESCRIPTOR_O", + [CALL_PY_EXACT_ARGS] = "CALL_PY_EXACT_ARGS", + [CALL_PY_WITH_DEFAULTS] = "CALL_PY_WITH_DEFAULTS", + [CALL_STR_1] = "CALL_STR_1", + [CALL_TUPLE_1] = "CALL_TUPLE_1", + [CALL_TYPE_1] = "CALL_TYPE_1", + [CHECK_EG_MATCH] = "CHECK_EG_MATCH", + [CHECK_EXC_MATCH] = "CHECK_EXC_MATCH", + [CLEANUP_THROW] = "CLEANUP_THROW", [COMPARE_OP] = "COMPARE_OP", + [COMPARE_OP_FLOAT] = "COMPARE_OP_FLOAT", + [COMPARE_OP_INT] = "COMPARE_OP_INT", + [COMPARE_OP_STR] = "COMPARE_OP_STR", [CONTAINS_OP] = "CONTAINS_OP", [CONVERT_VALUE] = "CONVERT_VALUE", [COPY] = "COPY", @@ -2032,21 +2028,74 @@ const char *const _PyOpcode_OpName[268] = { [DELETE_FAST] = "DELETE_FAST", [DELETE_GLOBAL] = "DELETE_GLOBAL", [DELETE_NAME] = "DELETE_NAME", + [DELETE_SUBSCR] = "DELETE_SUBSCR", [DICT_MERGE] = "DICT_MERGE", [DICT_UPDATE] = "DICT_UPDATE", + [END_ASYNC_FOR] = "END_ASYNC_FOR", + [END_FOR] = "END_FOR", + [END_SEND] = "END_SEND", [ENTER_EXECUTOR] = "ENTER_EXECUTOR", + [EXIT_INIT_CHECK] = "EXIT_INIT_CHECK", [EXTENDED_ARG] = "EXTENDED_ARG", + [FORMAT_SIMPLE] = "FORMAT_SIMPLE", + [FORMAT_WITH_SPEC] = "FORMAT_WITH_SPEC", [FOR_ITER] = "FOR_ITER", + [FOR_ITER_GEN] = "FOR_ITER_GEN", + [FOR_ITER_LIST] = "FOR_ITER_LIST", + [FOR_ITER_RANGE] = "FOR_ITER_RANGE", + [FOR_ITER_TUPLE] = "FOR_ITER_TUPLE", + [GET_AITER] = "GET_AITER", + [GET_ANEXT] = "GET_ANEXT", [GET_AWAITABLE] = "GET_AWAITABLE", + [GET_ITER] = "GET_ITER", + [GET_LEN] = "GET_LEN", + [GET_YIELD_FROM_ITER] = "GET_YIELD_FROM_ITER", [IMPORT_FROM] = "IMPORT_FROM", [IMPORT_NAME] = "IMPORT_NAME", + [INSTRUMENTED_CALL] = "INSTRUMENTED_CALL", + [INSTRUMENTED_CALL_FUNCTION_EX] = "INSTRUMENTED_CALL_FUNCTION_EX", + [INSTRUMENTED_CALL_KW] = "INSTRUMENTED_CALL_KW", + [INSTRUMENTED_END_FOR] = "INSTRUMENTED_END_FOR", + [INSTRUMENTED_END_SEND] = "INSTRUMENTED_END_SEND", + [INSTRUMENTED_FOR_ITER] = "INSTRUMENTED_FOR_ITER", + [INSTRUMENTED_INSTRUCTION] = "INSTRUMENTED_INSTRUCTION", + [INSTRUMENTED_JUMP_BACKWARD] = "INSTRUMENTED_JUMP_BACKWARD", + [INSTRUMENTED_JUMP_FORWARD] = "INSTRUMENTED_JUMP_FORWARD", + [INSTRUMENTED_LINE] = "INSTRUMENTED_LINE", + [INSTRUMENTED_LOAD_SUPER_ATTR] = "INSTRUMENTED_LOAD_SUPER_ATTR", + [INSTRUMENTED_POP_JUMP_IF_FALSE] = "INSTRUMENTED_POP_JUMP_IF_FALSE", + [INSTRUMENTED_POP_JUMP_IF_NONE] = "INSTRUMENTED_POP_JUMP_IF_NONE", + [INSTRUMENTED_POP_JUMP_IF_NOT_NONE] = "INSTRUMENTED_POP_JUMP_IF_NOT_NONE", + [INSTRUMENTED_POP_JUMP_IF_TRUE] = "INSTRUMENTED_POP_JUMP_IF_TRUE", + [INSTRUMENTED_RESUME] = "INSTRUMENTED_RESUME", + [INSTRUMENTED_RETURN_CONST] = "INSTRUMENTED_RETURN_CONST", + [INSTRUMENTED_RETURN_VALUE] = "INSTRUMENTED_RETURN_VALUE", + [INSTRUMENTED_YIELD_VALUE] = "INSTRUMENTED_YIELD_VALUE", + [INTERPRETER_EXIT] = "INTERPRETER_EXIT", [IS_OP] = "IS_OP", + [JUMP] = "JUMP", [JUMP_BACKWARD] = "JUMP_BACKWARD", [JUMP_BACKWARD_NO_INTERRUPT] = "JUMP_BACKWARD_NO_INTERRUPT", [JUMP_FORWARD] = "JUMP_FORWARD", + [JUMP_NO_INTERRUPT] = "JUMP_NO_INTERRUPT", [LIST_APPEND] = "LIST_APPEND", [LIST_EXTEND] = "LIST_EXTEND", + [LOAD_ASSERTION_ERROR] = "LOAD_ASSERTION_ERROR", [LOAD_ATTR] = "LOAD_ATTR", + [LOAD_ATTR_CLASS] = "LOAD_ATTR_CLASS", + [LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN] = "LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN", + [LOAD_ATTR_INSTANCE_VALUE] = "LOAD_ATTR_INSTANCE_VALUE", + [LOAD_ATTR_METHOD_LAZY_DICT] = "LOAD_ATTR_METHOD_LAZY_DICT", + [LOAD_ATTR_METHOD_NO_DICT] = "LOAD_ATTR_METHOD_NO_DICT", + [LOAD_ATTR_METHOD_WITH_VALUES] = "LOAD_ATTR_METHOD_WITH_VALUES", + [LOAD_ATTR_MODULE] = "LOAD_ATTR_MODULE", + [LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = "LOAD_ATTR_NONDESCRIPTOR_NO_DICT", + [LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = "LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", + [LOAD_ATTR_PROPERTY] = "LOAD_ATTR_PROPERTY", + [LOAD_ATTR_SLOT] = "LOAD_ATTR_SLOT", + [LOAD_ATTR_WITH_HINT] = "LOAD_ATTR_WITH_HINT", + [LOAD_BUILD_CLASS] = "LOAD_BUILD_CLASS", + [LOAD_CLOSURE] = "LOAD_CLOSURE", [LOAD_CONST] = "LOAD_CONST", [LOAD_DEREF] = "LOAD_DEREF", [LOAD_FAST] = "LOAD_FAST", @@ -2056,133 +2105,84 @@ const char *const _PyOpcode_OpName[268] = { [LOAD_FROM_DICT_OR_DEREF] = "LOAD_FROM_DICT_OR_DEREF", [LOAD_FROM_DICT_OR_GLOBALS] = "LOAD_FROM_DICT_OR_GLOBALS", [LOAD_GLOBAL] = "LOAD_GLOBAL", + [LOAD_GLOBAL_BUILTIN] = "LOAD_GLOBAL_BUILTIN", + [LOAD_GLOBAL_MODULE] = "LOAD_GLOBAL_MODULE", + [LOAD_LOCALS] = "LOAD_LOCALS", + [LOAD_METHOD] = "LOAD_METHOD", [LOAD_NAME] = "LOAD_NAME", [LOAD_SUPER_ATTR] = "LOAD_SUPER_ATTR", + [LOAD_SUPER_ATTR_ATTR] = "LOAD_SUPER_ATTR_ATTR", + [LOAD_SUPER_ATTR_METHOD] = "LOAD_SUPER_ATTR_METHOD", + [LOAD_SUPER_METHOD] = "LOAD_SUPER_METHOD", + [LOAD_ZERO_SUPER_ATTR] = "LOAD_ZERO_SUPER_ATTR", + [LOAD_ZERO_SUPER_METHOD] = "LOAD_ZERO_SUPER_METHOD", [MAKE_CELL] = "MAKE_CELL", + [MAKE_FUNCTION] = "MAKE_FUNCTION", [MAP_ADD] = "MAP_ADD", [MATCH_CLASS] = "MATCH_CLASS", + [MATCH_KEYS] = "MATCH_KEYS", + [MATCH_MAPPING] = "MATCH_MAPPING", + [MATCH_SEQUENCE] = "MATCH_SEQUENCE", + [NOP] = "NOP", + [POP_BLOCK] = "POP_BLOCK", + [POP_EXCEPT] = "POP_EXCEPT", [POP_JUMP_IF_FALSE] = "POP_JUMP_IF_FALSE", [POP_JUMP_IF_NONE] = "POP_JUMP_IF_NONE", [POP_JUMP_IF_NOT_NONE] = "POP_JUMP_IF_NOT_NONE", [POP_JUMP_IF_TRUE] = "POP_JUMP_IF_TRUE", + [POP_TOP] = "POP_TOP", + [PUSH_EXC_INFO] = "PUSH_EXC_INFO", + [PUSH_NULL] = "PUSH_NULL", [RAISE_VARARGS] = "RAISE_VARARGS", [RERAISE] = "RERAISE", + [RESERVED] = "RESERVED", + [RESUME] = "RESUME", + [RESUME_CHECK] = "RESUME_CHECK", [RETURN_CONST] = "RETURN_CONST", + [RETURN_GENERATOR] = "RETURN_GENERATOR", + [RETURN_VALUE] = "RETURN_VALUE", [SEND] = "SEND", + [SEND_GEN] = "SEND_GEN", + [SETUP_ANNOTATIONS] = "SETUP_ANNOTATIONS", + [SETUP_CLEANUP] = "SETUP_CLEANUP", + [SETUP_FINALLY] = "SETUP_FINALLY", + [SETUP_WITH] = "SETUP_WITH", [SET_ADD] = "SET_ADD", [SET_FUNCTION_ATTRIBUTE] = "SET_FUNCTION_ATTRIBUTE", [SET_UPDATE] = "SET_UPDATE", [STORE_ATTR] = "STORE_ATTR", + [STORE_ATTR_INSTANCE_VALUE] = "STORE_ATTR_INSTANCE_VALUE", + [STORE_ATTR_SLOT] = "STORE_ATTR_SLOT", + [STORE_ATTR_WITH_HINT] = "STORE_ATTR_WITH_HINT", [STORE_DEREF] = "STORE_DEREF", [STORE_FAST] = "STORE_FAST", [STORE_FAST_LOAD_FAST] = "STORE_FAST_LOAD_FAST", + [STORE_FAST_MAYBE_NULL] = "STORE_FAST_MAYBE_NULL", [STORE_FAST_STORE_FAST] = "STORE_FAST_STORE_FAST", [STORE_GLOBAL] = "STORE_GLOBAL", [STORE_NAME] = "STORE_NAME", - [SWAP] = "SWAP", - [UNPACK_EX] = "UNPACK_EX", - [UNPACK_SEQUENCE] = "UNPACK_SEQUENCE", - [YIELD_VALUE] = "YIELD_VALUE", - [BINARY_OP_ADD_FLOAT] = "BINARY_OP_ADD_FLOAT", - [BINARY_OP_ADD_INT] = "BINARY_OP_ADD_INT", - [BINARY_OP_ADD_UNICODE] = "BINARY_OP_ADD_UNICODE", - [BINARY_OP_MULTIPLY_FLOAT] = "BINARY_OP_MULTIPLY_FLOAT", - [BINARY_OP_MULTIPLY_INT] = "BINARY_OP_MULTIPLY_INT", - [BINARY_OP_SUBTRACT_FLOAT] = "BINARY_OP_SUBTRACT_FLOAT", - [BINARY_OP_SUBTRACT_INT] = "BINARY_OP_SUBTRACT_INT", - [BINARY_SUBSCR_DICT] = "BINARY_SUBSCR_DICT", - [BINARY_SUBSCR_GETITEM] = "BINARY_SUBSCR_GETITEM", - [BINARY_SUBSCR_LIST_INT] = "BINARY_SUBSCR_LIST_INT", - [BINARY_SUBSCR_STR_INT] = "BINARY_SUBSCR_STR_INT", - [BINARY_SUBSCR_TUPLE_INT] = "BINARY_SUBSCR_TUPLE_INT", - [CALL_ALLOC_AND_ENTER_INIT] = "CALL_ALLOC_AND_ENTER_INIT", - [CALL_BOUND_METHOD_EXACT_ARGS] = "CALL_BOUND_METHOD_EXACT_ARGS", - [CALL_BUILTIN_CLASS] = "CALL_BUILTIN_CLASS", - [CALL_BUILTIN_FAST] = "CALL_BUILTIN_FAST", - [CALL_BUILTIN_FAST_WITH_KEYWORDS] = "CALL_BUILTIN_FAST_WITH_KEYWORDS", - [CALL_BUILTIN_O] = "CALL_BUILTIN_O", - [CALL_ISINSTANCE] = "CALL_ISINSTANCE", - [CALL_LEN] = "CALL_LEN", - [CALL_LIST_APPEND] = "CALL_LIST_APPEND", - [CALL_METHOD_DESCRIPTOR_FAST] = "CALL_METHOD_DESCRIPTOR_FAST", - [CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = "CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS", - [CALL_METHOD_DESCRIPTOR_NOARGS] = "CALL_METHOD_DESCRIPTOR_NOARGS", - [CALL_METHOD_DESCRIPTOR_O] = "CALL_METHOD_DESCRIPTOR_O", - [CALL_PY_EXACT_ARGS] = "CALL_PY_EXACT_ARGS", - [CALL_PY_WITH_DEFAULTS] = "CALL_PY_WITH_DEFAULTS", - [CALL_STR_1] = "CALL_STR_1", - [CALL_TUPLE_1] = "CALL_TUPLE_1", - [CALL_TYPE_1] = "CALL_TYPE_1", - [COMPARE_OP_FLOAT] = "COMPARE_OP_FLOAT", - [COMPARE_OP_INT] = "COMPARE_OP_INT", - [COMPARE_OP_STR] = "COMPARE_OP_STR", - [FOR_ITER_GEN] = "FOR_ITER_GEN", - [FOR_ITER_LIST] = "FOR_ITER_LIST", - [FOR_ITER_RANGE] = "FOR_ITER_RANGE", - [FOR_ITER_TUPLE] = "FOR_ITER_TUPLE", - [LOAD_ATTR_CLASS] = "LOAD_ATTR_CLASS", - [LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN] = "LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN", - [LOAD_ATTR_INSTANCE_VALUE] = "LOAD_ATTR_INSTANCE_VALUE", - [LOAD_ATTR_METHOD_LAZY_DICT] = "LOAD_ATTR_METHOD_LAZY_DICT", - [LOAD_ATTR_METHOD_NO_DICT] = "LOAD_ATTR_METHOD_NO_DICT", - [LOAD_ATTR_METHOD_WITH_VALUES] = "LOAD_ATTR_METHOD_WITH_VALUES", - [LOAD_ATTR_MODULE] = "LOAD_ATTR_MODULE", - [LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = "LOAD_ATTR_NONDESCRIPTOR_NO_DICT", - [LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = "LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", - [LOAD_ATTR_PROPERTY] = "LOAD_ATTR_PROPERTY", - [LOAD_ATTR_SLOT] = "LOAD_ATTR_SLOT", - [LOAD_ATTR_WITH_HINT] = "LOAD_ATTR_WITH_HINT", - [LOAD_GLOBAL_BUILTIN] = "LOAD_GLOBAL_BUILTIN", - [LOAD_GLOBAL_MODULE] = "LOAD_GLOBAL_MODULE", - [LOAD_SUPER_ATTR_ATTR] = "LOAD_SUPER_ATTR_ATTR", - [LOAD_SUPER_ATTR_METHOD] = "LOAD_SUPER_ATTR_METHOD", - [RESUME_CHECK] = "RESUME_CHECK", - [SEND_GEN] = "SEND_GEN", - [STORE_ATTR_INSTANCE_VALUE] = "STORE_ATTR_INSTANCE_VALUE", - [STORE_ATTR_SLOT] = "STORE_ATTR_SLOT", - [STORE_ATTR_WITH_HINT] = "STORE_ATTR_WITH_HINT", + [STORE_SLICE] = "STORE_SLICE", + [STORE_SUBSCR] = "STORE_SUBSCR", [STORE_SUBSCR_DICT] = "STORE_SUBSCR_DICT", [STORE_SUBSCR_LIST_INT] = "STORE_SUBSCR_LIST_INT", + [SWAP] = "SWAP", + [TO_BOOL] = "TO_BOOL", [TO_BOOL_ALWAYS_TRUE] = "TO_BOOL_ALWAYS_TRUE", [TO_BOOL_BOOL] = "TO_BOOL_BOOL", [TO_BOOL_INT] = "TO_BOOL_INT", [TO_BOOL_LIST] = "TO_BOOL_LIST", [TO_BOOL_NONE] = "TO_BOOL_NONE", [TO_BOOL_STR] = "TO_BOOL_STR", + [UNARY_INVERT] = "UNARY_INVERT", + [UNARY_NEGATIVE] = "UNARY_NEGATIVE", + [UNARY_NOT] = "UNARY_NOT", + [UNPACK_EX] = "UNPACK_EX", + [UNPACK_SEQUENCE] = "UNPACK_SEQUENCE", [UNPACK_SEQUENCE_LIST] = "UNPACK_SEQUENCE_LIST", [UNPACK_SEQUENCE_TUPLE] = "UNPACK_SEQUENCE_TUPLE", [UNPACK_SEQUENCE_TWO_TUPLE] = "UNPACK_SEQUENCE_TWO_TUPLE", - [INSTRUMENTED_RESUME] = "INSTRUMENTED_RESUME", - [INSTRUMENTED_END_FOR] = "INSTRUMENTED_END_FOR", - [INSTRUMENTED_END_SEND] = "INSTRUMENTED_END_SEND", - [INSTRUMENTED_RETURN_VALUE] = "INSTRUMENTED_RETURN_VALUE", - [INSTRUMENTED_RETURN_CONST] = "INSTRUMENTED_RETURN_CONST", - [INSTRUMENTED_YIELD_VALUE] = "INSTRUMENTED_YIELD_VALUE", - [INSTRUMENTED_LOAD_SUPER_ATTR] = "INSTRUMENTED_LOAD_SUPER_ATTR", - [INSTRUMENTED_FOR_ITER] = "INSTRUMENTED_FOR_ITER", - [INSTRUMENTED_CALL] = "INSTRUMENTED_CALL", - [INSTRUMENTED_CALL_KW] = "INSTRUMENTED_CALL_KW", - [INSTRUMENTED_CALL_FUNCTION_EX] = "INSTRUMENTED_CALL_FUNCTION_EX", - [INSTRUMENTED_INSTRUCTION] = "INSTRUMENTED_INSTRUCTION", - [INSTRUMENTED_JUMP_FORWARD] = "INSTRUMENTED_JUMP_FORWARD", - [INSTRUMENTED_JUMP_BACKWARD] = "INSTRUMENTED_JUMP_BACKWARD", - [INSTRUMENTED_POP_JUMP_IF_TRUE] = "INSTRUMENTED_POP_JUMP_IF_TRUE", - [INSTRUMENTED_POP_JUMP_IF_FALSE] = "INSTRUMENTED_POP_JUMP_IF_FALSE", - [INSTRUMENTED_POP_JUMP_IF_NONE] = "INSTRUMENTED_POP_JUMP_IF_NONE", - [INSTRUMENTED_POP_JUMP_IF_NOT_NONE] = "INSTRUMENTED_POP_JUMP_IF_NOT_NONE", - [INSTRUMENTED_LINE] = "INSTRUMENTED_LINE", - [JUMP] = "JUMP", - [JUMP_NO_INTERRUPT] = "JUMP_NO_INTERRUPT", - [LOAD_CLOSURE] = "LOAD_CLOSURE", - [LOAD_METHOD] = "LOAD_METHOD", - [LOAD_SUPER_METHOD] = "LOAD_SUPER_METHOD", - [LOAD_ZERO_SUPER_ATTR] = "LOAD_ZERO_SUPER_ATTR", - [LOAD_ZERO_SUPER_METHOD] = "LOAD_ZERO_SUPER_METHOD", - [POP_BLOCK] = "POP_BLOCK", - [SETUP_CLEANUP] = "SETUP_CLEANUP", - [SETUP_FINALLY] = "SETUP_FINALLY", - [SETUP_WITH] = "SETUP_WITH", - [STORE_FAST_MAYBE_NULL] = "STORE_FAST_MAYBE_NULL", + [WITH_EXCEPT_START] = "WITH_EXCEPT_START", + [YIELD_VALUE] = "YIELD_VALUE", }; #endif // NEED_OPCODE_METADATA diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py index c6ed5911b846bf..50bc14a57fc584 100644 --- a/Tools/cases_generator/generate_cases.py +++ b/Tools/cases_generator/generate_cases.py @@ -209,8 +209,9 @@ def write_function( "", ): with self.out.block("switch(opcode)"): - for instr, effect in data: - self.out.emit(f"case {instr.name}:") + effects = [(instr.name, effect) for instr, effect in data] + for name, effect in sorted(effects): + self.out.emit(f"case {name}:") self.out.emit(f" return {effect};") self.out.emit("default:") self.out.emit(" return -1;") @@ -433,7 +434,8 @@ def write_metadata(self, metadata_filename: str, pymetadata_filename: str) -> No ";", ): # Write metadata for each instruction - for thing in self.everything: + sorted_things = sorted(self.everything, key = lambda t:t.name) + for thing in sorted_things: match thing: case parsing.InstDef(): self.write_metadata_for_inst(self.instrs[thing.name]) @@ -456,7 +458,7 @@ def write_metadata(self, metadata_filename: str, pymetadata_filename: str) -> No ";", ): # Write macro expansion for each non-pseudo instruction - for mac in self.macro_instrs.values(): + for mac in sorted(self.macro_instrs.values(), key=lambda t: t.name): if is_super_instruction(mac): # Special-case the heck out of super-instructions self.write_super_expansions(mac.name) @@ -475,7 +477,7 @@ def write_metadata(self, metadata_filename: str, pymetadata_filename: str) -> No "=", ";", ): - for name in self.opmap: + for name in sorted(self.opmap): self.out.emit(f'[{name}] = "{name}",') with self.metadata_item( @@ -589,7 +591,7 @@ def add(name: str) -> None: add("_EXIT_TRACE") add("_SET_IP") - for instr in self.instrs.values(): + for instr in sorted(self.instrs.values(), key=lambda t:t.name): # Skip ops that are also macros -- those are desugared inst()s if instr.name not in self.macros: add(instr.name) From a723a13bf135306cdc5999a959596bfb487e8f4f Mon Sep 17 00:00:00 2001 From: Zackery Spytz Date: Thu, 14 Dec 2023 11:06:53 -0800 Subject: [PATCH 19/62] bpo-36796: Clean the error handling in _testcapimodule.c (GH-13085) --- Modules/_testcapi/datetime.c | 46 +++++++++++++++++++++++++++++------- Modules/_testcapimodule.c | 18 +++++++++----- 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/Modules/_testcapi/datetime.c b/Modules/_testcapi/datetime.c index 88f992915fa8c1..b1796039f0d83a 100644 --- a/Modules/_testcapi/datetime.c +++ b/Modules/_testcapi/datetime.c @@ -85,17 +85,25 @@ make_timezones_capi(PyObject *self, PyObject *args) { PyObject *offset = PyDelta_FromDSU(0, -18000, 0); PyObject *name = PyUnicode_FromString("EST"); + if (offset == NULL || name == NULL) { + Py_XDECREF(offset); + Py_XDECREF(name); + return NULL; + } PyObject *est_zone_capi = PyDateTimeAPI->TimeZone_FromTimeZone(offset, name); PyObject *est_zone_macro = PyTimeZone_FromOffsetAndName(offset, name); PyObject *est_zone_macro_noname = PyTimeZone_FromOffset(offset); - - Py_DecRef(offset); - Py_DecRef(name); - + Py_DECREF(offset); + Py_DECREF(name); + if (est_zone_capi == NULL || est_zone_macro == NULL || + est_zone_macro_noname == NULL) + { + goto error; + } PyObject *rv = PyTuple_New(3); if (rv == NULL) { - return NULL; + goto error; } PyTuple_SET_ITEM(rv, 0, est_zone_capi); @@ -103,6 +111,11 @@ make_timezones_capi(PyObject *self, PyObject *args) PyTuple_SET_ITEM(rv, 2, est_zone_macro_noname); return rv; +error: + Py_XDECREF(est_zone_capi); + Py_XDECREF(est_zone_macro); + Py_XDECREF(est_zone_macro_noname); + return NULL; } static PyObject * @@ -110,6 +123,11 @@ get_timezones_offset_zero(PyObject *self, PyObject *args) { PyObject *offset = PyDelta_FromDSU(0, 0, 0); PyObject *name = PyUnicode_FromString(""); + if (offset == NULL || name == NULL) { + Py_XDECREF(offset); + Py_XDECREF(name); + return NULL; + } // These two should return the UTC singleton PyObject *utc_singleton_0 = PyTimeZone_FromOffset(offset); @@ -117,16 +135,28 @@ get_timezones_offset_zero(PyObject *self, PyObject *args) // This one will return +00:00 zone, but not the UTC singleton PyObject *non_utc_zone = PyTimeZone_FromOffsetAndName(offset, name); - - Py_DecRef(offset); - Py_DecRef(name); + Py_DECREF(offset); + Py_DECREF(name); + if (utc_singleton_0 == NULL || utc_singleton_1 == NULL || + non_utc_zone == NULL) + { + goto error; + } PyObject *rv = PyTuple_New(3); + if (rv == NULL) { + goto error; + } PyTuple_SET_ITEM(rv, 0, utc_singleton_0); PyTuple_SET_ITEM(rv, 1, utc_singleton_1); PyTuple_SET_ITEM(rv, 2, non_utc_zone); return rv; +error: + Py_XDECREF(utc_singleton_0); + Py_XDECREF(utc_singleton_1); + Py_XDECREF(non_utc_zone); + return NULL; } static PyObject * diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index b3ddfae58e6fc0..3527dfa77279ac 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -196,11 +196,11 @@ test_dict_inner(int count) for (i = 0; i < count; i++) { v = PyLong_FromLong(i); if (v == NULL) { - return -1; + goto error; } if (PyDict_SetItem(dict, v, v) < 0) { Py_DECREF(v); - return -1; + goto error; } Py_DECREF(v); } @@ -214,11 +214,12 @@ test_dict_inner(int count) assert(v != UNINITIALIZED_PTR); i = PyLong_AS_LONG(v) + 1; o = PyLong_FromLong(i); - if (o == NULL) - return -1; + if (o == NULL) { + goto error; + } if (PyDict_SetItem(dict, k, o) < 0) { Py_DECREF(o); - return -1; + goto error; } Py_DECREF(o); k = v = UNINITIALIZED_PTR; @@ -236,6 +237,9 @@ test_dict_inner(int count) } else { return 0; } +error: + Py_DECREF(dict); + return -1; } @@ -1556,7 +1560,9 @@ test_structseq_newtype_doesnt_leak(PyObject *Py_UNUSED(self), descr.n_in_sequence = 1; PyTypeObject* structseq_type = PyStructSequence_NewType(&descr); - assert(structseq_type != NULL); + if (structseq_type == NULL) { + return NULL; + } assert(PyType_Check(structseq_type)); assert(PyType_FastSubclass(structseq_type, Py_TPFLAGS_TUPLE_SUBCLASS)); Py_DECREF(structseq_type); From 22511f77c2818a138a252e6ddae89725d082f8b0 Mon Sep 17 00:00:00 2001 From: Ronald Oussoren Date: Thu, 14 Dec 2023 20:14:50 +0100 Subject: [PATCH 20/62] gh-105912: document gotcha with using os.fork on macOS (#112871) * gh-105912: document gotcha with using os.fork on macOS Using ``fork(2)`` on macOS when also using higher-level system APIs in the parent proces can crash on macOS because those system APIs are not written to handle this usage pattern. There's nothing we can do about this other than documenting the problem. Co-authored-by: Carol Willing --- Doc/library/os.rst | 10 ++++++++++ Doc/library/pty.rst | 3 +++ Doc/library/urllib.request.rst | 8 ++++++++ 3 files changed, 21 insertions(+) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index f4566a6684e14c..2ff0b73560ac4c 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -4383,6 +4383,11 @@ written in Python, such as a mail server's external command delivery program. If you use TLS sockets in an application calling ``fork()``, see the warning in the :mod:`ssl` documentation. + .. warning:: + + On macOS the use of this function is unsafe when mixed with using + higher-level system APIs, and that includes using :mod:`urllib.request`. + .. versionchanged:: 3.8 Calling ``fork()`` in a subinterpreter is no longer supported (:exc:`RuntimeError` is raised). @@ -4422,6 +4427,11 @@ written in Python, such as a mail server's external command delivery program. .. audit-event:: os.forkpty "" os.forkpty + .. warning:: + + On macOS the use of this function is unsafe when mixed with using + higher-level system APIs, and that includes using :mod:`urllib.request`. + .. versionchanged:: 3.12 If Python is able to detect that your process has multiple threads, this now raises a :exc:`DeprecationWarning`. See the diff --git a/Doc/library/pty.rst b/Doc/library/pty.rst index af9378464edb9f..bd2f5ed45cb8b4 100644 --- a/Doc/library/pty.rst +++ b/Doc/library/pty.rst @@ -33,6 +33,9 @@ The :mod:`pty` module defines the following functions: file descriptor connected to the child's controlling terminal (and also to the child's standard input and output). + .. warning:: On macOS the use of this function is unsafe when mixed with using + higher-level system APIs, and that includes using :mod:`urllib.request`. + .. function:: openpty() diff --git a/Doc/library/urllib.request.rst b/Doc/library/urllib.request.rst index 040e28e9124014..0e18db73280a63 100644 --- a/Doc/library/urllib.request.rst +++ b/Doc/library/urllib.request.rst @@ -21,6 +21,14 @@ authentication, redirections, cookies and more. The `Requests package `_ is recommended for a higher-level HTTP client interface. +.. warning:: + + On macOS it is unsafe to use this module in programs using + :func:`os.fork` because the :func:`getproxies` implementation for + macOS uses a higher-level system API. Set the environment variable + ``no_proxy`` to ``*`` to avoid this problem + (e.g. ``os.environ["no_proxy"] = "*"``). + .. include:: ../includes/wasm-notavail.rst The :mod:`urllib.request` module defines the following functions: From 93cf7358d996e0e296046526bf9bc44755d597d1 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Thu, 14 Dec 2023 13:15:29 -0600 Subject: [PATCH 21/62] Add recipe for totient() to demonstrate unique_justseen() and factor(). (gh-113131) --- Doc/library/itertools.rst | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 56c66f670c74dd..83e2a9fdb7b464 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -1128,6 +1128,14 @@ The following recipes have a more mathematical flavor: if n > 1: yield n + def totient(n): + "Count of natural numbers up to n that are coprime to n." + # https://mathworld.wolfram.com/TotientFunction.html + # totient(12) --> 4 because len([1, 5, 7, 11]) == 4 + for p in unique_justseen(factor(n)): + n = n // p * (p - 1) + return n + def nth_combination(iterable, r, index): "Equivalent to list(combinations(iterable, r))[index]" pool = tuple(iterable) @@ -1429,6 +1437,25 @@ The following recipes have a more mathematical flavor: >>> all(list(factor(n)) == sorted(factor(n)) for n in range(2_000)) True + >>> totient(0) # https://www.wolframalpha.com/input?i=totient+0 + 0 + >>> first_totients = [1, 1, 2, 2, 4, 2, 6, 4, 6, 4, 10, 4, 12, 6, 8, 8, 16, 6, + ... 18, 8, 12, 10, 22, 8, 20, 12, 18, 12, 28, 8, 30, 16, 20, 16, 24, 12, 36, 18, + ... 24, 16, 40, 12, 42, 20, 24, 22, 46, 16, 42, 20, 32, 24, 52, 18, 40, 24, 36, + ... 28, 58, 16, 60, 30, 36, 32, 48, 20, 66, 32, 44] # https://oeis.org/A000010 + ... + >>> list(map(totient, range(1, 70))) == first_totients + True + >>> reference_totient = lambda n: sum(math.gcd(t, n) == 1 for t in range(1, n+1)) + >>> all(totient(n) == reference_totient(n) for n in range(1000)) + True + >>> totient(128_884_753_939) == 128_884_753_938 # large prime + True + >>> totient(999953 * 999983) == 999952 * 999982 # large semiprime + True + >>> totient(6 ** 20) == 1 * 2**19 * 2 * 3**19 # repeated primes + True + >>> list(flatten([('a', 'b'), (), ('c', 'd', 'e'), ('f',), ('g', 'h', 'i')])) ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'] From 006355b2a966060541166608ecfec4c957b85f55 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 14 Dec 2023 19:25:55 +0000 Subject: [PATCH 22/62] gh-101100: Fix Sphinx nitpicks in `library/collections.abc.rst` (#113116) --- Doc/conf.py | 3 +- Doc/library/collections.abc.rst | 80 +++++++++++++++++---------------- Doc/tools/.nitignore | 1 - 3 files changed, 43 insertions(+), 41 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index 0d7c0b553eaa74..4077eaf5a139b0 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -245,10 +245,11 @@ # be resolved, as the method is currently undocumented. For context, see # https://github.com/python/cpython/pull/103289. ('py:meth', '_SubParsersAction.add_parser'), - # Attributes that definitely should be documented better, + # Attributes/methods/etc. that definitely should be documented better, # but are deferred for now: ('py:attr', '__annotations__'), ('py:attr', '__wrapped__'), + ('py:meth', 'index'), # list.index, tuple.index, etc. ] # gh-106948: Copy standard C types declared in the "c:type" domain to the diff --git a/Doc/library/collections.abc.rst b/Doc/library/collections.abc.rst index edc078953290d7..e0c72ff9249ee7 100644 --- a/Doc/library/collections.abc.rst +++ b/Doc/library/collections.abc.rst @@ -22,7 +22,7 @@ This module provides :term:`abstract base classes ` that can be used to test whether a class provides a particular interface; for -example, whether it is :term:`hashable` or whether it is a mapping. +example, whether it is :term:`hashable` or whether it is a :term:`mapping`. An :func:`issubclass` or :func:`isinstance` test for an interface works in one of three ways. @@ -73,7 +73,7 @@ of the API: >>> isinstance(D(), Sequence) True -In this example, class :class:`D` does not need to define +In this example, class :class:`!D` does not need to define ``__contains__``, ``__iter__``, and ``__reversed__`` because the :ref:`in-operator `, the :term:`iteration ` logic, and the :func:`reversed` function automatically fall back to @@ -183,14 +183,14 @@ ABC Inherits from Abstract Methods Mi .. rubric:: Footnotes -.. [1] These ABCs override :meth:`object.__subclasshook__` to support +.. [1] These ABCs override :meth:`~abc.ABCMeta.__subclasshook__` to support testing an interface by verifying the required methods are present and have not been set to :const:`None`. This only works for simple interfaces. More complex interfaces require registration or direct subclassing. .. [2] Checking ``isinstance(obj, Iterable)`` detects classes that are - registered as :class:`Iterable` or that have an :meth:`__iter__` + registered as :class:`Iterable` or that have an :meth:`~container.__iter__` method, but it does not detect classes that iterate with the :meth:`~object.__getitem__` method. The only reliable way to determine whether an object is :term:`iterable` is to call ``iter(obj)``. @@ -202,26 +202,27 @@ Collections Abstract Base Classes -- Detailed Descriptions .. class:: Container - ABC for classes that provide the :meth:`__contains__` method. + ABC for classes that provide the :meth:`~object.__contains__` method. .. class:: Hashable - ABC for classes that provide the :meth:`__hash__` method. + ABC for classes that provide the :meth:`~object.__hash__` method. .. class:: Sized - ABC for classes that provide the :meth:`__len__` method. + ABC for classes that provide the :meth:`~object.__len__` method. .. class:: Callable - ABC for classes that provide the :meth:`__call__` method. + ABC for classes that provide the :meth:`~object.__call__` method. .. class:: Iterable - ABC for classes that provide the :meth:`__iter__` method. + ABC for classes that provide the :meth:`~container.__iter__` method. Checking ``isinstance(obj, Iterable)`` detects classes that are registered - as :class:`Iterable` or that have an :meth:`__iter__` method, but it does + as :class:`Iterable` or that have an :meth:`~container.__iter__` method, + but it does not detect classes that iterate with the :meth:`~object.__getitem__` method. The only reliable way to determine whether an object is :term:`iterable` is to call ``iter(obj)``. @@ -240,17 +241,17 @@ Collections Abstract Base Classes -- Detailed Descriptions .. class:: Reversible - ABC for iterable classes that also provide the :meth:`__reversed__` + ABC for iterable classes that also provide the :meth:`~object.__reversed__` method. .. versionadded:: 3.6 .. class:: Generator - ABC for generator classes that implement the protocol defined in - :pep:`342` that extends iterators with the :meth:`~generator.send`, + ABC for :term:`generator` classes that implement the protocol defined in + :pep:`342` that extends :term:`iterators ` with the + :meth:`~generator.send`, :meth:`~generator.throw` and :meth:`~generator.close` methods. - See also the definition of :term:`generator`. .. versionadded:: 3.5 @@ -261,7 +262,7 @@ Collections Abstract Base Classes -- Detailed Descriptions ABCs for read-only and mutable :term:`sequences `. Implementation note: Some of the mixin methods, such as - :meth:`__iter__`, :meth:`__reversed__` and :meth:`index`, make + :meth:`~container.__iter__`, :meth:`~object.__reversed__` and :meth:`index`, make repeated calls to the underlying :meth:`~object.__getitem__` method. Consequently, if :meth:`~object.__getitem__` is implemented with constant access speed, the mixin methods will have linear performance; @@ -282,7 +283,7 @@ Collections Abstract Base Classes -- Detailed Descriptions .. class:: Set MutableSet - ABCs for read-only and mutable sets. + ABCs for read-only and mutable :ref:`sets `. .. class:: Mapping MutableMapping @@ -299,16 +300,16 @@ Collections Abstract Base Classes -- Detailed Descriptions .. class:: Awaitable ABC for :term:`awaitable` objects, which can be used in :keyword:`await` - expressions. Custom implementations must provide the :meth:`__await__` - method. + expressions. Custom implementations must provide the + :meth:`~object.__await__` method. :term:`Coroutine ` objects and instances of the :class:`~collections.abc.Coroutine` ABC are all instances of this ABC. .. note:: - In CPython, generator-based coroutines (generators decorated with - :func:`types.coroutine`) are - *awaitables*, even though they do not have an :meth:`__await__` method. + In CPython, generator-based coroutines (:term:`generators ` + decorated with :func:`@types.coroutine `) are + *awaitables*, even though they do not have an :meth:`~object.__await__` method. Using ``isinstance(gencoro, Awaitable)`` for them will return ``False``. Use :func:`inspect.isawaitable` to detect them. @@ -316,17 +317,17 @@ Collections Abstract Base Classes -- Detailed Descriptions .. class:: Coroutine - ABC for coroutine compatible classes. These implement the + ABC for :term:`coroutine` compatible classes. These implement the following methods, defined in :ref:`coroutine-objects`: :meth:`~coroutine.send`, :meth:`~coroutine.throw`, and :meth:`~coroutine.close`. Custom implementations must also implement - :meth:`__await__`. All :class:`Coroutine` instances are also instances of - :class:`Awaitable`. See also the definition of :term:`coroutine`. + :meth:`~object.__await__`. All :class:`Coroutine` instances are also + instances of :class:`Awaitable`. .. note:: - In CPython, generator-based coroutines (generators decorated with - :func:`types.coroutine`) are - *awaitables*, even though they do not have an :meth:`__await__` method. + In CPython, generator-based coroutines (:term:`generators ` + decorated with :func:`@types.coroutine `) are + *awaitables*, even though they do not have an :meth:`~object.__await__` method. Using ``isinstance(gencoro, Coroutine)`` for them will return ``False``. Use :func:`inspect.isawaitable` to detect them. @@ -334,7 +335,7 @@ Collections Abstract Base Classes -- Detailed Descriptions .. class:: AsyncIterable - ABC for classes that provide ``__aiter__`` method. See also the + ABC for classes that provide an ``__aiter__`` method. See also the definition of :term:`asynchronous iterable`. .. versionadded:: 3.5 @@ -348,7 +349,7 @@ Collections Abstract Base Classes -- Detailed Descriptions .. class:: AsyncGenerator - ABC for asynchronous generator classes that implement the protocol + ABC for :term:`asynchronous generator` classes that implement the protocol defined in :pep:`525` and :pep:`492`. .. versionadded:: 3.6 @@ -373,9 +374,9 @@ particular functionality, for example:: Several of the ABCs are also useful as mixins that make it easier to develop classes supporting container APIs. For example, to write a class supporting the full :class:`Set` API, it is only necessary to supply the three underlying -abstract methods: :meth:`__contains__`, :meth:`__iter__`, and :meth:`__len__`. -The ABC supplies the remaining methods such as :meth:`__and__` and -:meth:`isdisjoint`:: +abstract methods: :meth:`~object.__contains__`, :meth:`~container.__iter__`, and +:meth:`~object.__len__`. The ABC supplies the remaining methods such as +:meth:`!__and__` and :meth:`~frozenset.isdisjoint`:: class ListBasedSet(collections.abc.Set): ''' Alternate set implementation favoring space over speed @@ -403,23 +404,24 @@ Notes on using :class:`Set` and :class:`MutableSet` as a mixin: (1) Since some set operations create new sets, the default mixin methods need - a way to create new instances from an iterable. The class constructor is + a way to create new instances from an :term:`iterable`. The class constructor is assumed to have a signature in the form ``ClassName(iterable)``. - That assumption is factored-out to an internal classmethod called - :meth:`_from_iterable` which calls ``cls(iterable)`` to produce a new set. + That assumption is factored-out to an internal :class:`classmethod` called + :meth:`!_from_iterable` which calls ``cls(iterable)`` to produce a new set. If the :class:`Set` mixin is being used in a class with a different - constructor signature, you will need to override :meth:`_from_iterable` + constructor signature, you will need to override :meth:`!_from_iterable` with a classmethod or regular method that can construct new instances from an iterable argument. (2) To override the comparisons (presumably for speed, as the - semantics are fixed), redefine :meth:`__le__` and :meth:`__ge__`, + semantics are fixed), redefine :meth:`~object.__le__` and + :meth:`~object.__ge__`, then the other operations will automatically follow suit. (3) - The :class:`Set` mixin provides a :meth:`_hash` method to compute a hash value - for the set; however, :meth:`__hash__` is not defined because not all sets + The :class:`Set` mixin provides a :meth:`!_hash` method to compute a hash value + for the set; however, :meth:`~object.__hash__` is not defined because not all sets are :term:`hashable` or immutable. To add set hashability using mixins, inherit from both :meth:`Set` and :meth:`Hashable`, then define ``__hash__ = Set._hash``. diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 4d1d31d44fcf75..ca0cb84d850928 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -35,7 +35,6 @@ Doc/library/bdb.rst Doc/library/bisect.rst Doc/library/calendar.rst Doc/library/cmd.rst -Doc/library/collections.abc.rst Doc/library/collections.rst Doc/library/concurrent.futures.rst Doc/library/configparser.rst From becad9a2a1b5f3deaad24759daec95014218e0db Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Thu, 14 Dec 2023 14:36:40 -0600 Subject: [PATCH 23/62] Remove itertool recipe with low pedagogical value (gh-113138) --- Doc/library/itertools.rst | 64 +++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 83e2a9fdb7b464..36cea9a835f302 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -1136,24 +1136,6 @@ The following recipes have a more mathematical flavor: n = n // p * (p - 1) return n - def nth_combination(iterable, r, index): - "Equivalent to list(combinations(iterable, r))[index]" - pool = tuple(iterable) - n = len(pool) - c = math.comb(n, r) - if index < 0: - index += c - if index < 0 or index >= c: - raise IndexError - result = [] - while r: - c, n, r = c*r//n, n-1, r-1 - while index >= c: - index -= c - c, n = c*(n-r)//n, n-1 - result.append(pool[-1-n]) - return tuple(result) - .. doctest:: :hide: @@ -1577,20 +1559,6 @@ The following recipes have a more mathematical flavor: >>> first_true('ABC0DEF1', '9', str.isdigit) '0' - >>> population = 'ABCDEFGH' - >>> for r in range(len(population) + 1): - ... seq = list(combinations(population, r)) - ... for i in range(len(seq)): - ... assert nth_combination(population, r, i) == seq[i] - ... for i in range(-len(seq), 0): - ... assert nth_combination(population, r, i) == seq[i] - - >>> iterable = 'abcde' - >>> r = 3 - >>> combos = list(combinations(iterable, r)) - >>> all(nth_combination(iterable, r, i) == comb for i, comb in enumerate(combos)) - True - .. testcode:: :hide: @@ -1617,6 +1585,24 @@ The following recipes have a more mathematical flavor: for (a, _), (b, c) in pairwise(pairwise(iterable)): yield a, b, c + def nth_combination(iterable, r, index): + "Equivalent to list(combinations(iterable, r))[index]" + pool = tuple(iterable) + n = len(pool) + c = math.comb(n, r) + if index < 0: + index += c + if index < 0 or index >= c: + raise IndexError + result = [] + while r: + c, n, r = c*r//n, n-1, r-1 + while index >= c: + index -= c + c, n = c*(n-r)//n, n-1 + result.append(pool[-1-n]) + return tuple(result) + .. doctest:: :hide: @@ -1632,3 +1618,17 @@ The following recipes have a more mathematical flavor: >>> list(triplewise('ABCDEFG')) [('A', 'B', 'C'), ('B', 'C', 'D'), ('C', 'D', 'E'), ('D', 'E', 'F'), ('E', 'F', 'G')] + + >>> population = 'ABCDEFGH' + >>> for r in range(len(population) + 1): + ... seq = list(combinations(population, r)) + ... for i in range(len(seq)): + ... assert nth_combination(population, r, i) == seq[i] + ... for i in range(-len(seq), 0): + ... assert nth_combination(population, r, i) == seq[i] + + >>> iterable = 'abcde' + >>> r = 3 + >>> combos = list(combinations(iterable, r)) + >>> all(nth_combination(iterable, r, i) == comb for i, comb in enumerate(combos)) + True From 961f1043a022ddeff314f63480a232335a598d6a Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 14 Dec 2023 22:48:36 +0200 Subject: [PATCH 24/62] gh-101100: Fix Sphinx warnings in `whatsnew/2.3.rst` (#112373) --- Doc/tools/.nitignore | 1 - Doc/whatsnew/2.3.rst | 276 +++++++++++++++++++++---------------------- 2 files changed, 138 insertions(+), 139 deletions(-) diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index ca0cb84d850928..20580f78e07b5d 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -118,7 +118,6 @@ Doc/using/windows.rst Doc/whatsnew/2.0.rst Doc/whatsnew/2.1.rst Doc/whatsnew/2.2.rst -Doc/whatsnew/2.3.rst Doc/whatsnew/2.4.rst Doc/whatsnew/2.5.rst Doc/whatsnew/2.6.rst diff --git a/Doc/whatsnew/2.3.rst b/Doc/whatsnew/2.3.rst index 0c77b339a182c9..8ebcbfaf248551 100644 --- a/Doc/whatsnew/2.3.rst +++ b/Doc/whatsnew/2.3.rst @@ -40,10 +40,10 @@ new feature. PEP 218: A Standard Set Datatype ================================ -The new :mod:`sets` module contains an implementation of a set datatype. The +The new :mod:`!sets` module contains an implementation of a set datatype. The :class:`Set` class is for mutable sets, sets that can have members added and -removed. The :class:`ImmutableSet` class is for sets that can't be modified, -and instances of :class:`ImmutableSet` can therefore be used as dictionary keys. +removed. The :class:`!ImmutableSet` class is for sets that can't be modified, +and instances of :class:`!ImmutableSet` can therefore be used as dictionary keys. Sets are built on top of dictionaries, so the elements within a set must be hashable. @@ -63,10 +63,10 @@ Here's a simple example:: Set([1, 2, 5]) >>> -The union and intersection of sets can be computed with the :meth:`union` and -:meth:`intersection` methods; an alternative notation uses the bitwise operators +The union and intersection of sets can be computed with the :meth:`~frozenset.union` and +:meth:`~frozenset.intersection` methods; an alternative notation uses the bitwise operators ``&`` and ``|``. Mutable sets also have in-place versions of these methods, -:meth:`union_update` and :meth:`intersection_update`. :: +:meth:`!union_update` and :meth:`~frozenset.intersection_update`. :: >>> S1 = sets.Set([1,2,3]) >>> S2 = sets.Set([4,5,6]) @@ -87,7 +87,7 @@ It's also possible to take the symmetric difference of two sets. This is the set of all elements in the union that aren't in the intersection. Another way of putting it is that the symmetric difference contains all elements that are in exactly one set. Again, there's an alternative notation (``^``), and an -in-place version with the ungainly name :meth:`symmetric_difference_update`. :: +in-place version with the ungainly name :meth:`~frozenset.symmetric_difference_update`. :: >>> S1 = sets.Set([1,2,3,4]) >>> S2 = sets.Set([3,4,5,6]) @@ -97,7 +97,7 @@ in-place version with the ungainly name :meth:`symmetric_difference_update`. :: Set([1, 2, 5, 6]) >>> -There are also :meth:`issubset` and :meth:`issuperset` methods for checking +There are also :meth:`!issubset` and :meth:`!issuperset` methods for checking whether one set is a subset or superset of another:: >>> S1 = sets.Set([1,2,3]) @@ -166,7 +166,7 @@ statement isn't allowed inside the :keyword:`try` block of a :keyword:`!try`...\ :keyword:`!finally` statement; read :pep:`255` for a full explanation of the interaction between :keyword:`!yield` and exceptions.) -Here's a sample usage of the :func:`generate_ints` generator:: +Here's a sample usage of the :func:`!generate_ints` generator:: >>> gen = generate_ints(3) >>> gen @@ -227,7 +227,7 @@ like:: sentence := "Store it in the neighboring harbor" if (i := find("or", sentence)) > 5 then write(i) -In Icon the :func:`find` function returns the indexes at which the substring +In Icon the :func:`!find` function returns the indexes at which the substring "or" is found: 3, 23, 33. In the :keyword:`if` statement, ``i`` is first assigned a value of 3, but 3 is less than 5, so the comparison fails, and Icon retries it with the second value of 23. 23 is greater than 5, so the comparison @@ -345,7 +345,7 @@ Python now allows using arbitrary Unicode strings (within the limitations of the file system) for all functions that expect file names, most notably the :func:`open` built-in function. If a Unicode string is passed to :func:`os.listdir`, Python now returns a list of Unicode strings. A new -function, :func:`os.getcwdu`, returns the current directory as a Unicode string. +function, :func:`!os.getcwdu`, returns the current directory as a Unicode string. Byte strings still work as file names, and on Windows Python will transparently convert them to Unicode using the ``mbcs`` encoding. @@ -386,10 +386,10 @@ one followed by the platform on which Python is running. Opening a file with the mode ``'U'`` or ``'rU'`` will open a file for reading in :term:`universal newlines` mode. All three line ending conventions will be translated to a ``'\n'`` in the strings returned by the various file methods such as -:meth:`read` and :meth:`readline`. +:meth:`!read` and :meth:`!readline`. Universal newline support is also used when importing modules and when executing -a file with the :func:`execfile` function. This means that Python modules can +a file with the :func:`!execfile` function. This means that Python modules can be shared between all three operating systems without needing to convert the line-endings. @@ -450,16 +450,16 @@ records to standard error or to a file or socket, send them to the system log, or even e-mail them to a particular address; of course, it's also possible to write your own handler classes. -The :class:`Logger` class is the primary class. Most application code will deal -with one or more :class:`Logger` objects, each one used by a particular -subsystem of the application. Each :class:`Logger` is identified by a name, and +The :class:`~logging.Logger` class is the primary class. Most application code will deal +with one or more :class:`~logging.Logger` objects, each one used by a particular +subsystem of the application. Each :class:`~logging.Logger` is identified by a name, and names are organized into a hierarchy using ``.`` as the component separator. -For example, you might have :class:`Logger` instances named ``server``, +For example, you might have :class:`~logging.Logger` instances named ``server``, ``server.auth`` and ``server.network``. The latter two instances are below ``server`` in the hierarchy. This means that if you turn up the verbosity for ``server`` or direct ``server`` messages to a different handler, the changes will also apply to records logged to ``server.auth`` and ``server.network``. -There's also a root :class:`Logger` that's the parent of all other loggers. +There's also a root :class:`~logging.Logger` that's the parent of all other loggers. For simple uses, the :mod:`logging` package contains some convenience functions that always use the root log:: @@ -480,14 +480,14 @@ This produces the following output:: In the default configuration, informational and debugging messages are suppressed and the output is sent to standard error. You can enable the display -of informational and debugging messages by calling the :meth:`setLevel` method +of informational and debugging messages by calling the :meth:`~logging.Logger.setLevel` method on the root logger. -Notice the :func:`warning` call's use of string formatting operators; all of the +Notice the :func:`~logging.warning` call's use of string formatting operators; all of the functions for logging messages take the arguments ``(msg, arg1, arg2, ...)`` and log the string resulting from ``msg % (arg1, arg2, ...)``. -There's also an :func:`exception` function that records the most recent +There's also an :func:`~logging.exception` function that records the most recent traceback. Any of the other functions will also record the traceback if you specify a true value for the keyword argument *exc_info*. :: @@ -517,16 +517,16 @@ it if it doesn't exist yet. ``getLogger(None)`` returns the root logger. :: ... Log records are usually propagated up the hierarchy, so a message logged to -``server.auth`` is also seen by ``server`` and ``root``, but a :class:`Logger` -can prevent this by setting its :attr:`propagate` attribute to :const:`False`. +``server.auth`` is also seen by ``server`` and ``root``, but a :class:`~logging.Logger` +can prevent this by setting its :attr:`~logging.Logger.propagate` attribute to :const:`False`. There are more classes provided by the :mod:`logging` package that can be -customized. When a :class:`Logger` instance is told to log a message, it -creates a :class:`LogRecord` instance that is sent to any number of different -:class:`Handler` instances. Loggers and handlers can also have an attached list -of filters, and each filter can cause the :class:`LogRecord` to be ignored or +customized. When a :class:`~logging.Logger` instance is told to log a message, it +creates a :class:`~logging.LogRecord` instance that is sent to any number of different +:class:`~logging.Handler` instances. Loggers and handlers can also have an attached list +of filters, and each filter can cause the :class:`~logging.LogRecord` to be ignored or can modify the record before passing it along. When they're finally output, -:class:`LogRecord` instances are converted to text by a :class:`Formatter` +:class:`~logging.LogRecord` instances are converted to text by a :class:`~logging.Formatter` class. All of these classes can be replaced by your own specially written classes. @@ -550,7 +550,7 @@ PEP 285: A Boolean Type ======================= A Boolean type was added to Python 2.3. Two new constants were added to the -:mod:`__builtin__` module, :const:`True` and :const:`False`. (:const:`True` and +:mod:`!__builtin__` module, :const:`True` and :const:`False`. (:const:`True` and :const:`False` constants were added to the built-ins in Python 2.2.1, but the 2.2.1 versions are simply set to integer values of 1 and 0 and aren't a different type.) @@ -662,7 +662,7 @@ a central catalog server. The resulting catalog is available from https://pypi.org. To make the catalog a bit more useful, a new optional *classifiers* keyword -argument has been added to the Distutils :func:`setup` function. A list of +argument has been added to the Distutils :func:`!setup` function. A list of `Trove `_-style strings can be supplied to help classify the software. @@ -703,14 +703,14 @@ PEP 302: New Import Hooks ========================= While it's been possible to write custom import hooks ever since the -:mod:`ihooks` module was introduced in Python 1.3, no one has ever been really +:mod:`!ihooks` module was introduced in Python 1.3, no one has ever been really happy with it because writing new import hooks is difficult and messy. There -have been various proposed alternatives such as the :mod:`imputil` and :mod:`iu` +have been various proposed alternatives such as the :mod:`!imputil` and :mod:`!iu` modules, but none of them has ever gained much acceptance, and none of them were easily usable from C code. :pep:`302` borrows ideas from its predecessors, especially from Gordon -McMillan's :mod:`iu` module. Three new items are added to the :mod:`sys` +McMillan's :mod:`!iu` module. Three new items are added to the :mod:`sys` module: * ``sys.path_hooks`` is a list of callable objects; most often they'll be @@ -790,7 +790,7 @@ package is much simpler:: for line in reader: print line -The :func:`reader` function takes a number of different options. The field +The :func:`~csv.reader` function takes a number of different options. The field separator isn't limited to the comma and can be changed to any character, and so can the quoting and line-ending characters. @@ -814,7 +814,7 @@ of tuples or lists, quoting strings that contain the delimiter. PEP 307: Pickle Enhancements ============================ -The :mod:`pickle` and :mod:`cPickle` modules received some attention during the +The :mod:`pickle` and :mod:`!cPickle` modules received some attention during the 2.3 development cycle. In 2.2, new-style classes could be pickled without difficulty, but they weren't pickled very compactly; :pep:`307` quotes a trivial example where a new-style class results in a pickled string three times longer @@ -829,13 +829,13 @@ fanciest protocol available. Unpickling is no longer considered a safe operation. 2.2's :mod:`pickle` provided hooks for trying to prevent unsafe classes from being unpickled -(specifically, a :attr:`__safe_for_unpickling__` attribute), but none of this +(specifically, a :attr:`!__safe_for_unpickling__` attribute), but none of this code was ever audited and therefore it's all been ripped out in 2.3. You should not unpickle untrusted data in any version of Python. To reduce the pickling overhead for new-style classes, a new interface for customizing pickling was added using three special methods: -:meth:`__getstate__`, :meth:`__setstate__`, and :meth:`__getnewargs__`. Consult +:meth:`~object.__getstate__`, :meth:`~object.__setstate__`, and :meth:`~object.__getnewargs__`. Consult :pep:`307` for the full semantics of these methods. As a way to compress pickles yet further, it's now possible to use integer codes @@ -939,7 +939,7 @@ Or use slice objects directly in subscripts:: To simplify implementing sequences that support extended slicing, slice objects now have a method ``indices(length)`` which, given the length of a sequence, returns a ``(start, stop, step)`` tuple that can be passed directly to -:func:`range`. :meth:`indices` handles omitted and out-of-bounds indices in a +:func:`range`. :meth:`!indices` handles omitted and out-of-bounds indices in a manner consistent with regular slices (and this innocuous phrase hides a welter of confusing details!). The method is intended to be used like this:: @@ -1042,7 +1042,7 @@ Here are all of the changes that Python 2.3 makes to the core Python language. execute any assertions. * Most type objects are now callable, so you can use them to create new objects - such as functions, classes, and modules. (This means that the :mod:`new` module + such as functions, classes, and modules. (This means that the :mod:`!new` module can be deprecated in a future Python version, because you can now use the type objects available in the :mod:`types` module.) For example, you can create a new module object with the following code: @@ -1069,11 +1069,11 @@ Here are all of the changes that Python 2.3 makes to the core Python language. * Using ``None`` as a variable name will now result in a :exc:`SyntaxWarning` warning. In a future version of Python, ``None`` may finally become a keyword. -* The :meth:`xreadlines` method of file objects, introduced in Python 2.1, is no +* The :meth:`!xreadlines` method of file objects, introduced in Python 2.1, is no longer necessary because files now behave as their own iterator. - :meth:`xreadlines` was originally introduced as a faster way to loop over all + :meth:`!xreadlines` was originally introduced as a faster way to loop over all the lines in a file, but now you can simply write ``for line in file_obj``. - File objects also have a new read-only :attr:`encoding` attribute that gives the + File objects also have a new read-only :attr:`!encoding` attribute that gives the encoding used by the file; Unicode strings written to the file will be automatically converted to bytes using the given encoding. @@ -1096,12 +1096,12 @@ Here are all of the changes that Python 2.3 makes to the core Python language. switching overhead. Some multithreaded applications may suffer slower response time, but that's easily fixed by setting the limit back to a lower number using ``sys.setcheckinterval(N)``. The limit can be retrieved with the new - :func:`sys.getcheckinterval` function. + :func:`!sys.getcheckinterval` function. * One minor but far-reaching change is that the names of extension types defined by the modules included with Python now contain the module and a ``'.'`` in front of the type name. For example, in Python 2.2, if you created a socket and - printed its :attr:`__class__`, you'd get this output:: + printed its :attr:`!__class__`, you'd get this output:: >>> s = socket.socket() >>> s.__class__ @@ -1138,9 +1138,9 @@ String Changes True Note that this doesn't tell you where the substring starts; if you need that - information, use the :meth:`find` string method. + information, use the :meth:`~str.find` string method. -* The :meth:`strip`, :meth:`lstrip`, and :meth:`rstrip` string methods now have +* The :meth:`~str.strip`, :meth:`~str.lstrip`, and :meth:`~str.rstrip` string methods now have an optional argument for specifying the characters to strip. The default is still to remove all whitespace characters:: @@ -1156,13 +1156,13 @@ String Changes (Suggested by Simon Brunning and implemented by Walter Dörwald.) -* The :meth:`startswith` and :meth:`endswith` string methods now accept negative +* The :meth:`~str.startswith` and :meth:`~str.endswith` string methods now accept negative numbers for the *start* and *end* parameters. -* Another new string method is :meth:`zfill`, originally a function in the - :mod:`string` module. :meth:`zfill` pads a numeric string with zeros on the +* Another new string method is :meth:`~str.zfill`, originally a function in the + :mod:`string` module. :meth:`~str.zfill` pads a numeric string with zeros on the left until it's the specified width. Note that the ``%`` operator is still more - flexible and powerful than :meth:`zfill`. :: + flexible and powerful than :meth:`~str.zfill`. :: >>> '45'.zfill(4) '0045' @@ -1173,10 +1173,10 @@ String Changes (Contributed by Walter Dörwald.) -* A new type object, :class:`basestring`, has been added. Both 8-bit strings and +* A new type object, :class:`!basestring`, has been added. Both 8-bit strings and Unicode strings inherit from this type, so ``isinstance(obj, basestring)`` will return :const:`True` for either kind of string. It's a completely abstract - type, so you can't create :class:`basestring` instances. + type, so you can't create :class:`!basestring` instances. * Interned strings are no longer immortal and will now be garbage-collected in the usual way when the only reference to them is from the internal dictionary of @@ -1191,7 +1191,7 @@ Optimizations * The creation of new-style class instances has been made much faster; they're now faster than classic classes! -* The :meth:`sort` method of list objects has been extensively rewritten by Tim +* The :meth:`~list.sort` method of list objects has been extensively rewritten by Tim Peters, and the implementation is significantly faster. * Multiplication of large long integers is now much faster thanks to an @@ -1203,7 +1203,7 @@ Optimizations increase, depending on your compiler's idiosyncrasies. See section :ref:`23section-other` for a longer explanation. (Removed by Michael Hudson.) -* :func:`xrange` objects now have their own iterator, making ``for i in +* :func:`!xrange` objects now have their own iterator, making ``for i in xrange(n)`` slightly faster than ``for i in range(n)``. (Patch by Raymond Hettinger.) @@ -1230,21 +1230,21 @@ complete list of changes, or look through the CVS logs for all the details. operator to add another array's contents, and the ``*=`` assignment operator to repeat an array. (Contributed by Jason Orendorff.) -* The :mod:`bsddb` module has been replaced by version 4.1.6 of the `PyBSDDB +* The :mod:`!bsddb` module has been replaced by version 4.1.6 of the `PyBSDDB `_ package, providing a more complete interface to the transactional features of the BerkeleyDB library. - The old version of the module has been renamed to :mod:`bsddb185` and is no + The old version of the module has been renamed to :mod:`!bsddb185` and is no longer built automatically; you'll have to edit :file:`Modules/Setup` to enable - it. Note that the new :mod:`bsddb` package is intended to be compatible with + it. Note that the new :mod:`!bsddb` package is intended to be compatible with the old module, so be sure to file bugs if you discover any incompatibilities. When upgrading to Python 2.3, if the new interpreter is compiled with a new version of the underlying BerkeleyDB library, you will almost certainly have to convert your database files to the new version. You can do this fairly easily with the new scripts :file:`db2pickle.py` and :file:`pickle2db.py` which you will find in the distribution's :file:`Tools/scripts` directory. If you've - already been using the PyBSDDB package and importing it as :mod:`bsddb3`, you - will have to change your ``import`` statements to import it as :mod:`bsddb`. + already been using the PyBSDDB package and importing it as :mod:`!bsddb3`, you + will have to change your ``import`` statements to import it as :mod:`!bsddb`. * The new :mod:`bz2` module is an interface to the bz2 data compression library. bz2-compressed data is usually smaller than corresponding @@ -1253,11 +1253,11 @@ complete list of changes, or look through the CVS logs for all the details. * A set of standard date/time types has been added in the new :mod:`datetime` module. See the following section for more details. -* The Distutils :class:`Extension` class now supports an extra constructor +* The Distutils :class:`!Extension` class now supports an extra constructor argument named *depends* for listing additional source files that an extension depends on. This lets Distutils recompile the module if any of the dependency files are modified. For example, if :file:`sampmodule.c` includes the header - file :file:`sample.h`, you would create the :class:`Extension` object like + file :file:`sample.h`, you would create the :class:`!Extension` object like this:: ext = Extension("samp", @@ -1268,21 +1268,21 @@ complete list of changes, or look through the CVS logs for all the details. (Contributed by Jeremy Hylton.) * Other minor changes to Distutils: it now checks for the :envvar:`CC`, - :envvar:`CFLAGS`, :envvar:`CPP`, :envvar:`LDFLAGS`, and :envvar:`CPPFLAGS` + :envvar:`CFLAGS`, :envvar:`!CPP`, :envvar:`LDFLAGS`, and :envvar:`CPPFLAGS` environment variables, using them to override the settings in Python's configuration (contributed by Robert Weber). * Previously the :mod:`doctest` module would only search the docstrings of public methods and functions for test cases, but it now also examines private - ones as well. The :func:`DocTestSuite` function creates a + ones as well. The :func:`~doctest.DocTestSuite` function creates a :class:`unittest.TestSuite` object from a set of :mod:`doctest` tests. * The new ``gc.get_referents(object)`` function returns a list of all the objects referenced by *object*. -* The :mod:`getopt` module gained a new function, :func:`gnu_getopt`, that - supports the same arguments as the existing :func:`getopt` function but uses - GNU-style scanning mode. The existing :func:`getopt` stops processing options as +* The :mod:`getopt` module gained a new function, :func:`~getopt.gnu_getopt`, that + supports the same arguments as the existing :func:`~getopt.getopt` function but uses + GNU-style scanning mode. The existing :func:`~getopt.getopt` stops processing options as soon as a non-option argument is encountered, but in GNU-style mode processing continues, meaning that options and arguments can be mixed. For example:: @@ -1311,7 +1311,7 @@ complete list of changes, or look through the CVS logs for all the details. O(lg n). (See https://xlinux.nist.gov/dads//HTML/priorityque.html for more information about the priority queue data structure.) - The :mod:`heapq` module provides :func:`heappush` and :func:`heappop` functions + The :mod:`heapq` module provides :func:`~heapq.heappush` and :func:`~heapq.heappop` functions for adding and removing items while maintaining the heap property on top of some other mutable Python sequence type. Here's an example that uses a Python list:: @@ -1343,7 +1343,7 @@ complete list of changes, or look through the CVS logs for all the details. * The :mod:`itertools` contains a number of useful functions for use with iterators, inspired by various functions provided by the ML and Haskell languages. For example, ``itertools.ifilter(predicate, iterator)`` returns all - elements in the iterator for which the function :func:`predicate` returns + elements in the iterator for which the function :func:`!predicate` returns :const:`True`, and ``itertools.repeat(obj, N)`` returns ``obj`` *N* times. There are a number of other functions in the module; see the package's reference documentation for details. @@ -1356,9 +1356,9 @@ complete list of changes, or look through the CVS logs for all the details. was added to :func:`math.log` to make it easier to compute logarithms for bases other than ``e`` and ``10``. (Contributed by Raymond Hettinger.) -* Several new POSIX functions (:func:`getpgid`, :func:`killpg`, :func:`lchown`, - :func:`loadavg`, :func:`major`, :func:`makedev`, :func:`minor`, and - :func:`mknod`) were added to the :mod:`posix` module that underlies the +* Several new POSIX functions (:func:`!getpgid`, :func:`!killpg`, :func:`!lchown`, + :func:`!loadavg`, :func:`!major`, :func:`!makedev`, :func:`!minor`, and + :func:`!mknod`) were added to the :mod:`posix` module that underlies the :mod:`os` module. (Contributed by Gustavo Niemeyer, Geert Jansen, and Denis S. Otkidach.) @@ -1368,9 +1368,9 @@ complete list of changes, or look through the CVS logs for all the details. During testing, it was found that some applications will break if time stamps are floats. For compatibility, when using the tuple interface of the - :class:`stat_result` time stamps will be represented as integers. When using + :class:`~os.stat_result` time stamps will be represented as integers. When using named fields (a feature first introduced in Python 2.2), time stamps are still - represented as integers, unless :func:`os.stat_float_times` is invoked to enable + represented as integers, unless :func:`!os.stat_float_times` is invoked to enable float return values:: >>> os.stat("/tmp").st_mtime @@ -1391,7 +1391,7 @@ complete list of changes, or look through the CVS logs for all the details. automatically generate a usage message. See the following section for more details. -* The old and never-documented :mod:`linuxaudiodev` module has been deprecated, +* The old and never-documented :mod:`!linuxaudiodev` module has been deprecated, and a new version named :mod:`!ossaudiodev` has been added. The module was renamed because the OSS sound drivers can be used on platforms other than Linux, and the interface has also been tidied and brought up to date in various ways. @@ -1402,14 +1402,14 @@ complete list of changes, or look through the CVS logs for all the details. functions for getting the architecture, CPU type, the Windows OS version, and even the Linux distribution version. (Contributed by Marc-André Lemburg.) -* The parser objects provided by the :mod:`pyexpat` module can now optionally +* The parser objects provided by the :mod:`pyexpat ` module can now optionally buffer character data, resulting in fewer calls to your character data handler and therefore faster performance. Setting the parser object's - :attr:`buffer_text` attribute to :const:`True` will enable buffering. + :attr:`~xml.parsers.expat.xmlparser.buffer_text` attribute to :const:`True` will enable buffering. * The ``sample(population, k)`` function was added to the :mod:`random` - module. *population* is a sequence or :class:`xrange` object containing the - elements of a population, and :func:`sample` chooses *k* elements from the + module. *population* is a sequence or :class:`!xrange` object containing the + elements of a population, and :func:`~random.sample` chooses *k* elements from the population without replacing chosen elements. *k* can be any value up to ``len(population)``. For example:: @@ -1436,20 +1436,20 @@ complete list of changes, or look through the CVS logs for all the details. (All changes contributed by Raymond Hettinger.) * The :mod:`readline` module also gained a number of new functions: - :func:`get_history_item`, :func:`get_current_history_length`, and - :func:`redisplay`. + :func:`~readline.get_history_item`, :func:`~readline.get_current_history_length`, and + :func:`~readline.redisplay`. -* The :mod:`rexec` and :mod:`Bastion` modules have been declared dead, and +* The :mod:`!rexec` and :mod:`!Bastion` modules have been declared dead, and attempts to import them will fail with a :exc:`RuntimeError`. New-style classes provide new ways to break out of the restricted execution environment provided - by :mod:`rexec`, and no one has interest in fixing them or time to do so. If - you have applications using :mod:`rexec`, rewrite them to use something else. + by :mod:`!rexec`, and no one has interest in fixing them or time to do so. If + you have applications using :mod:`!rexec`, rewrite them to use something else. (Sticking with Python 2.2 or 2.1 will not make your applications any safer - because there are known bugs in the :mod:`rexec` module in those versions. To - repeat: if you're using :mod:`rexec`, stop using it immediately.) + because there are known bugs in the :mod:`!rexec` module in those versions. To + repeat: if you're using :mod:`!rexec`, stop using it immediately.) -* The :mod:`rotor` module has been deprecated because the algorithm it uses for +* The :mod:`!rotor` module has been deprecated because the algorithm it uses for encryption is not believed to be secure. If you need encryption, use one of the several AES Python modules that are available separately. @@ -1474,9 +1474,9 @@ complete list of changes, or look through the CVS logs for all the details. * On Windows, the :mod:`socket` module now ships with Secure Sockets Layer (SSL) support. -* The value of the C :c:macro:`PYTHON_API_VERSION` macro is now exposed at the +* The value of the C :c:macro:`!PYTHON_API_VERSION` macro is now exposed at the Python level as ``sys.api_version``. The current exception can be cleared by - calling the new :func:`sys.exc_clear` function. + calling the new :func:`!sys.exc_clear` function. * The new :mod:`tarfile` module allows reading from and writing to :program:`tar`\ -format archive files. (Contributed by Lars Gustäbel.) @@ -1486,7 +1486,7 @@ complete list of changes, or look through the CVS logs for all the details. string and returns a list containing the text split into lines of no more than the chosen width. The ``fill(text, width)`` function returns a single string, reformatted to fit into lines no longer than the chosen width. (As you - can guess, :func:`fill` is built on top of :func:`wrap`. For example:: + can guess, :func:`~textwrap.fill` is built on top of :func:`~textwrap.wrap`. For example:: >>> import textwrap >>> paragraph = "Not a whit, we defy augury: ... more text ..." @@ -1503,15 +1503,15 @@ complete list of changes, or look through the CVS logs for all the details. it will come: the readiness is all. >>> - The module also contains a :class:`TextWrapper` class that actually implements - the text wrapping strategy. Both the :class:`TextWrapper` class and the - :func:`wrap` and :func:`fill` functions support a number of additional keyword + The module also contains a :class:`~textwrap.TextWrapper` class that actually implements + the text wrapping strategy. Both the :class:`~textwrap.TextWrapper` class and the + :func:`~textwrap.wrap` and :func:`~textwrap.fill` functions support a number of additional keyword arguments for fine-tuning the formatting; consult the module's documentation for details. (Contributed by Greg Ward.) -* The :mod:`thread` and :mod:`threading` modules now have companion modules, - :mod:`dummy_thread` and :mod:`dummy_threading`, that provide a do-nothing - implementation of the :mod:`thread` module's interface for platforms where +* The :mod:`!thread` and :mod:`threading` modules now have companion modules, + :mod:`!dummy_thread` and :mod:`!dummy_threading`, that provide a do-nothing + implementation of the :mod:`!thread` module's interface for platforms where threads are not supported. The intention is to simplify thread-aware modules (ones that *don't* rely on threads to run) by putting the following code at the top:: @@ -1521,26 +1521,26 @@ complete list of changes, or look through the CVS logs for all the details. except ImportError: import dummy_threading as _threading - In this example, :mod:`_threading` is used as the module name to make it clear + In this example, :mod:`!_threading` is used as the module name to make it clear that the module being used is not necessarily the actual :mod:`threading` - module. Code can call functions and use classes in :mod:`_threading` whether or + module. Code can call functions and use classes in :mod:`!_threading` whether or not threads are supported, avoiding an :keyword:`if` statement and making the code slightly clearer. This module will not magically make multithreaded code run without threads; code that waits for another thread to return or to do something will simply hang forever. -* The :mod:`time` module's :func:`strptime` function has long been an annoyance - because it uses the platform C library's :func:`strptime` implementation, and +* The :mod:`time` module's :func:`~time.strptime` function has long been an annoyance + because it uses the platform C library's :func:`~time.strptime` implementation, and different platforms sometimes have odd bugs. Brett Cannon contributed a portable implementation that's written in pure Python and should behave identically on all platforms. * The new :mod:`timeit` module helps measure how long snippets of Python code take to execute. The :file:`timeit.py` file can be run directly from the - command line, or the module's :class:`Timer` class can be imported and used + command line, or the module's :class:`~timeit.Timer` class can be imported and used directly. Here's a short example that figures out whether it's faster to convert an 8-bit string to Unicode by appending an empty Unicode string to it or - by using the :func:`unicode` function:: + by using the :func:`!unicode` function:: import timeit @@ -1558,46 +1558,46 @@ complete list of changes, or look through the CVS logs for all the details. * The :mod:`!Tix` module has received various bug fixes and updates for the current version of the Tix package. -* The :mod:`Tkinter` module now works with a thread-enabled version of Tcl. +* The :mod:`!Tkinter` module now works with a thread-enabled version of Tcl. Tcl's threading model requires that widgets only be accessed from the thread in which they're created; accesses from another thread can cause Tcl to panic. For - certain Tcl interfaces, :mod:`Tkinter` will now automatically avoid this when a + certain Tcl interfaces, :mod:`!Tkinter` will now automatically avoid this when a widget is accessed from a different thread by marshalling a command, passing it to the correct thread, and waiting for the results. Other interfaces can't be - handled automatically but :mod:`Tkinter` will now raise an exception on such an + handled automatically but :mod:`!Tkinter` will now raise an exception on such an access so that you can at least find out about the problem. See https://mail.python.org/pipermail/python-dev/2002-December/031107.html for a more detailed explanation of this change. (Implemented by Martin von Löwis.) -* Calling Tcl methods through :mod:`_tkinter` no longer returns only strings. +* Calling Tcl methods through :mod:`!_tkinter` no longer returns only strings. Instead, if Tcl returns other objects those objects are converted to their - Python equivalent, if one exists, or wrapped with a :class:`_tkinter.Tcl_Obj` + Python equivalent, if one exists, or wrapped with a :class:`!_tkinter.Tcl_Obj` object if no Python equivalent exists. This behavior can be controlled through - the :meth:`wantobjects` method of :class:`tkapp` objects. + the :meth:`!wantobjects` method of :class:`!tkapp` objects. - When using :mod:`_tkinter` through the :mod:`Tkinter` module (as most Tkinter + When using :mod:`!_tkinter` through the :mod:`!Tkinter` module (as most Tkinter applications will), this feature is always activated. It should not cause compatibility problems, since Tkinter would always convert string results to Python types where possible. If any incompatibilities are found, the old behavior can be restored by setting - the :attr:`wantobjects` variable in the :mod:`Tkinter` module to false before - creating the first :class:`tkapp` object. :: + the :attr:`!wantobjects` variable in the :mod:`!Tkinter` module to false before + creating the first :class:`!tkapp` object. :: import Tkinter Tkinter.wantobjects = 0 Any breakage caused by this change should be reported as a bug. -* The :mod:`UserDict` module has a new :class:`DictMixin` class which defines +* The :mod:`!UserDict` module has a new :class:`!DictMixin` class which defines all dictionary methods for classes that already have a minimum mapping interface. This greatly simplifies writing classes that need to be substitutable for dictionaries, such as the classes in the :mod:`shelve` module. Adding the mix-in as a superclass provides the full dictionary interface - whenever the class defines :meth:`~object.__getitem__`, :meth:`__setitem__`, - :meth:`__delitem__`, and :meth:`keys`. For example:: + whenever the class defines :meth:`~object.__getitem__`, :meth:`~object.__setitem__`, + :meth:`~object.__delitem__`, and :meth:`!keys`. For example:: >>> import UserDict >>> class SeqDict(UserDict.DictMixin): @@ -1640,15 +1640,15 @@ complete list of changes, or look through the CVS logs for all the details. * The DOM implementation in :mod:`xml.dom.minidom` can now generate XML output in a particular encoding by providing an optional encoding argument to the - :meth:`toxml` and :meth:`toprettyxml` methods of DOM nodes. + :meth:`~xml.dom.minidom.Node.toxml` and :meth:`~xml.dom.minidom.Node.toprettyxml` methods of DOM nodes. -* The :mod:`xmlrpclib` module now supports an XML-RPC extension for handling nil +* The :mod:`!xmlrpclib` module now supports an XML-RPC extension for handling nil data values such as Python's ``None``. Nil values are always supported on unmarshalling an XML-RPC response. To generate requests containing ``None``, you must supply a true value for the *allow_none* parameter when creating a - :class:`Marshaller` instance. + :class:`!Marshaller` instance. -* The new :mod:`DocXMLRPCServer` module allows writing self-documenting XML-RPC +* The new :mod:`!DocXMLRPCServer` module allows writing self-documenting XML-RPC servers. Run it in demo mode (as a program) to see it in action. Pointing the web browser to the RPC server produces pydoc-style documentation; pointing xmlrpclib to the server allows invoking the actual methods. (Contributed by @@ -1663,8 +1663,8 @@ complete list of changes, or look through the CVS logs for all the details. The :mod:`socket` module has also been extended to transparently convert Unicode hostnames to the ACE version before passing them to the C library. - Modules that deal with hostnames such as :mod:`httplib` and :mod:`ftplib`) - also support Unicode host names; :mod:`httplib` also sends HTTP ``Host`` + Modules that deal with hostnames such as :mod:`!httplib` and :mod:`ftplib`) + also support Unicode host names; :mod:`!httplib` also sends HTTP ``Host`` headers using the ACE version of the domain name. :mod:`urllib` supports Unicode URLs with non-ASCII host names as long as the ``path`` part of the URL is ASCII only. @@ -1682,17 +1682,17 @@ Date and time types suitable for expressing timestamps were added as the :mod:`datetime` module. The types don't support different calendars or many fancy features, and just stick to the basics of representing time. -The three primary types are: :class:`date`, representing a day, month, and year; +The three primary types are: :class:`~datetime.date`, representing a day, month, and year; :class:`~datetime.time`, consisting of hour, minute, and second; and :class:`~datetime.datetime`, -which contains all the attributes of both :class:`date` and :class:`~datetime.time`. -There's also a :class:`timedelta` class representing differences between two +which contains all the attributes of both :class:`~datetime.date` and :class:`~datetime.time`. +There's also a :class:`~datetime.timedelta` class representing differences between two points in time, and time zone logic is implemented by classes inheriting from -the abstract :class:`tzinfo` class. +the abstract :class:`~datetime.tzinfo` class. -You can create instances of :class:`date` and :class:`~datetime.time` by either supplying +You can create instances of :class:`~datetime.date` and :class:`~datetime.time` by either supplying keyword arguments to the appropriate constructor, e.g. ``datetime.date(year=1972, month=10, day=15)``, or by using one of a number of -class methods. For example, the :meth:`date.today` class method returns the +class methods. For example, the :meth:`~datetime.date.today` class method returns the current local date. Once created, instances of the date/time classes are all immutable. There are a @@ -1707,8 +1707,8 @@ number of methods for producing formatted strings from objects:: >>> now.strftime('%Y %d %b') '2002 30 Dec' -The :meth:`replace` method allows modifying one or more fields of a -:class:`date` or :class:`~datetime.datetime` instance, returning a new instance:: +The :meth:`~datetime.datetime.replace` method allows modifying one or more fields of a +:class:`~datetime.date` or :class:`~datetime.datetime` instance, returning a new instance:: >>> d = datetime.datetime.now() >>> d @@ -1718,10 +1718,10 @@ The :meth:`replace` method allows modifying one or more fields of a >>> Instances can be compared, hashed, and converted to strings (the result is the -same as that of :meth:`isoformat`). :class:`date` and :class:`~datetime.datetime` -instances can be subtracted from each other, and added to :class:`timedelta` +same as that of :meth:`~datetime.datetime.isoformat`). :class:`~datetime.date` and :class:`~datetime.datetime` +instances can be subtracted from each other, and added to :class:`~datetime.timedelta` instances. The largest missing feature is that there's no standard library -support for parsing strings and getting back a :class:`date` or +support for parsing strings and getting back a :class:`~datetime.date` or :class:`~datetime.datetime`. For more information, refer to the module's reference documentation. @@ -1739,7 +1739,7 @@ command-line parsing that follows the Unix conventions, automatically creates the output for :option:`!--help`, and can perform different actions for different options. -You start by creating an instance of :class:`OptionParser` and telling it what +You start by creating an instance of :class:`~optparse.OptionParser` and telling it what your program's options are. :: import sys @@ -1753,7 +1753,7 @@ your program's options are. :: action='store', type='int', dest='length', help='set maximum length of output') -Parsing a command line is then done by calling the :meth:`parse_args` method. :: +Parsing a command line is then done by calling the :meth:`~optparse.OptionParser.parse_args` method. :: options, args = op.parse_args(sys.argv[1:]) print options @@ -1925,7 +1925,7 @@ Changes to Python's build process and to the C API include: dependence on a system version or local installation of Expat. * If you dynamically allocate type objects in your extension, you should be - aware of a change in the rules relating to the :attr:`__module__` and + aware of a change in the rules relating to the :attr:`!__module__` and :attr:`~definition.__name__` attributes. In summary, you will want to ensure the type's dictionary contains a ``'__module__'`` key; making the module name the part of the type name leading up to the final period will no longer have the desired @@ -1940,7 +1940,7 @@ Port-Specific Changes Support for a port to IBM's OS/2 using the EMX runtime environment was merged into the main Python source tree. EMX is a POSIX emulation layer over the OS/2 system APIs. The Python port for EMX tries to support all the POSIX-like -capability exposed by the EMX runtime, and mostly succeeds; :func:`fork` and +capability exposed by the EMX runtime, and mostly succeeds; :func:`!fork` and :func:`fcntl` are restricted by the limitations of the underlying emulation layer. The standard OS/2 port, which uses IBM's Visual Age compiler, also gained support for case-sensitive import semantics as part of the integration of @@ -2031,9 +2031,9 @@ code: the file's encoding (UTF-8, Latin-1, or whatever) by adding a comment to the top of the file. See section :ref:`section-encodings` for more information. -* Calling Tcl methods through :mod:`_tkinter` no longer returns only strings. +* Calling Tcl methods through :mod:`!_tkinter` no longer returns only strings. Instead, if Tcl returns other objects those objects are converted to their - Python equivalent, if one exists, or wrapped with a :class:`_tkinter.Tcl_Obj` + Python equivalent, if one exists, or wrapped with a :class:`!_tkinter.Tcl_Obj` object if no Python equivalent exists. * Large octal and hex literals such as ``0xffffffff`` now trigger a @@ -2049,10 +2049,10 @@ code: * You can no longer disable assertions by assigning to ``__debug__``. -* The Distutils :func:`setup` function has gained various new keyword arguments +* The Distutils :func:`!setup` function has gained various new keyword arguments such as *depends*. Old versions of the Distutils will abort if passed unknown keywords. A solution is to check for the presence of the new - :func:`get_distutil_options` function in your :file:`setup.py` and only uses the + :func:`!get_distutil_options` function in your :file:`setup.py` and only uses the new keywords with a version of the Distutils that supports them:: from distutils import core From 25061f5c98a47691fdb70f550943167bda77f6e0 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 14 Dec 2023 21:10:26 +0000 Subject: [PATCH 25/62] gh-101100: Cleanup `mailbox` docs (#113124) --- Doc/library/mailbox.rst | 261 +++++++++++++++++++++------------------- Doc/tools/.nitignore | 1 - 2 files changed, 139 insertions(+), 123 deletions(-) diff --git a/Doc/library/mailbox.rst b/Doc/library/mailbox.rst index fd60d163378f07..c98496d1fff993 100644 --- a/Doc/library/mailbox.rst +++ b/Doc/library/mailbox.rst @@ -13,8 +13,8 @@ This module defines two classes, :class:`Mailbox` and :class:`Message`, for accessing and manipulating on-disk mailboxes and the messages they contain. -:class:`Mailbox` offers a dictionary-like mapping from keys to messages. -:class:`Message` extends the :mod:`email.message` module's +:class:`!Mailbox` offers a dictionary-like mapping from keys to messages. +:class:`!Message` extends the :mod:`email.message` module's :class:`~email.message.Message` class with format-specific state and behavior. Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. @@ -27,37 +27,38 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. _mailbox-objects: -:class:`Mailbox` objects ------------------------- +:class:`!Mailbox` objects +------------------------- .. class:: Mailbox A mailbox, which may be inspected and modified. - The :class:`Mailbox` class defines an interface and is not intended to be + The :class:`!Mailbox` class defines an interface and is not intended to be instantiated. Instead, format-specific subclasses should inherit from - :class:`Mailbox` and your code should instantiate a particular subclass. + :class:`!Mailbox` and your code should instantiate a particular subclass. - The :class:`Mailbox` interface is dictionary-like, with small keys - corresponding to messages. Keys are issued by the :class:`Mailbox` instance - with which they will be used and are only meaningful to that :class:`Mailbox` + The :class:`!Mailbox` interface is dictionary-like, with small keys + corresponding to messages. Keys are issued by the :class:`!Mailbox` instance + with which they will be used and are only meaningful to that :class:`!Mailbox` instance. A key continues to identify a message even if the corresponding message is modified, such as by replacing it with another message. - Messages may be added to a :class:`Mailbox` instance using the set-like + Messages may be added to a :class:`!Mailbox` instance using the set-like method :meth:`add` and removed using a ``del`` statement or the set-like methods :meth:`remove` and :meth:`discard`. - :class:`Mailbox` interface semantics differ from dictionary semantics in some + :class:`!Mailbox` interface semantics differ from dictionary semantics in some noteworthy ways. Each time a message is requested, a new representation (typically a :class:`Message` instance) is generated based upon the current state of the mailbox. Similarly, when a message is added to a - :class:`Mailbox` instance, the provided message representation's contents are + :class:`!Mailbox` instance, the provided message representation's contents are copied. In neither case is a reference to the message representation kept by - the :class:`Mailbox` instance. + the :class:`!Mailbox` instance. - The default :class:`Mailbox` iterator iterates over message representations, - not keys as the default dictionary iterator does. Moreover, modification of a + The default :class:`!Mailbox` :term:`iterator` iterates over message + representations, not keys as the default :class:`dictionary ` + iterator does. Moreover, modification of a mailbox during iteration is safe and well-defined. Messages added to the mailbox after an iterator is created will not be seen by the iterator. Messages removed from the mailbox before the iterator yields them @@ -69,14 +70,15 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. Be very cautious when modifying mailboxes that might be simultaneously changed by some other process. The safest mailbox format to use for such - tasks is Maildir; try to avoid using single-file formats such as mbox for + tasks is :class:`Maildir`; try to avoid using single-file formats such as + :class:`mbox` for concurrent writing. If you're modifying a mailbox, you *must* lock it by calling the :meth:`lock` and :meth:`unlock` methods *before* reading any messages in the file or making any changes by adding or deleting a message. Failing to lock the mailbox runs the risk of losing messages or corrupting the entire mailbox. - :class:`Mailbox` instances have the following methods: + :class:`!Mailbox` instances have the following methods: .. method:: add(message) @@ -127,21 +129,23 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. method:: iterkeys() - keys() - Return an iterator over all keys if called as :meth:`iterkeys` or return a - list of keys if called as :meth:`keys`. + Return an :term:`iterator` over all keys + + + .. method:: keys() + + The same as :meth:`iterkeys`, except that a :class:`list` is returned + rather than an :term:`iterator` .. method:: itervalues() __iter__() - values() - Return an iterator over representations of all messages if called as - :meth:`itervalues` or :meth:`__iter__` or return a list of such - representations if called as :meth:`values`. The messages are represented + Return an :term:`iterator` over representations of all messages. + The messages are represented as instances of the appropriate format-specific :class:`Message` subclass - unless a custom message factory was specified when the :class:`Mailbox` + unless a custom message factory was specified when the :class:`!Mailbox` instance was initialized. .. note:: @@ -150,15 +154,25 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. iterate over keys. + .. method:: values() + + The same as :meth:`itervalues`, except that a :class:`list` is returned + rather than an :term:`iterator` + + .. method:: iteritems() - items() - Return an iterator over (*key*, *message*) pairs, where *key* is a key and - *message* is a message representation, if called as :meth:`iteritems` or - return a list of such pairs if called as :meth:`items`. The messages are + Return an :term:`iterator` over (*key*, *message*) pairs, where *key* is + a key and *message* is a message representation. The messages are represented as instances of the appropriate format-specific :class:`Message` subclass unless a custom message factory was specified - when the :class:`Mailbox` instance was initialized. + when the :class:`!Mailbox` instance was initialized. + + + .. method:: items() + + The same as :meth:`iteritems`, except that a :class:`list` of pairs is + returned rather than an :term:`iterator` of pairs. .. method:: get(key, default=None) @@ -167,9 +181,9 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. Return a representation of the message corresponding to *key*. If no such message exists, *default* is returned if the method was called as :meth:`get` and a :exc:`KeyError` exception is raised if the method was - called as :meth:`~object.__getitem__`. The message is represented as an instance + called as :meth:`!__getitem__`. The message is represented as an instance of the appropriate format-specific :class:`Message` subclass unless a - custom message factory was specified when the :class:`Mailbox` instance + custom message factory was specified when the :class:`!Mailbox` instance was initialized. @@ -198,21 +212,23 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. method:: get_file(key) - Return a file-like representation of the message corresponding to *key*, + Return a :term:`file-like ` representation of the + message corresponding to *key*, or raise a :exc:`KeyError` exception if no such message exists. The file-like object behaves as if open in binary mode. This file should be closed once it is no longer needed. .. versionchanged:: 3.2 - The file object really is a binary file; previously it was incorrectly - returned in text mode. Also, the file-like object now supports the - context management protocol: you can use a :keyword:`with` statement to - automatically close it. + The file object really is a :term:`binary file`; previously it was + incorrectly returned in text mode. Also, the :term:`file-like object` + now supports the :term:`context manager` protocol: you can use a + :keyword:`with` statement to automatically close it. .. note:: - Unlike other representations of messages, file-like representations are - not necessarily independent of the :class:`Mailbox` instance that + Unlike other representations of messages, + :term:`file-like ` representations are not + necessarily independent of the :class:`!Mailbox` instance that created them or of the underlying mailbox. More specific documentation is provided by each subclass. @@ -238,7 +254,7 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. the message. If no such message exists, return *default*. The message is represented as an instance of the appropriate format-specific :class:`Message` subclass unless a custom message factory was specified - when the :class:`Mailbox` instance was initialized. + when the :class:`!Mailbox` instance was initialized. .. method:: popitem() @@ -248,7 +264,7 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. message. If the mailbox is empty, raise a :exc:`KeyError` exception. The message is represented as an instance of the appropriate format-specific :class:`Message` subclass unless a custom message factory was specified - when the :class:`Mailbox` instance was initialized. + when the :class:`!Mailbox` instance was initialized. .. method:: update(arg) @@ -259,7 +275,7 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. *message* as if by using :meth:`__setitem__`. As with :meth:`__setitem__`, each *key* must already correspond to a message in the mailbox or else a :exc:`KeyError` exception will be raised, so in general it is incorrect - for *arg* to be a :class:`Mailbox` instance. + for *arg* to be a :class:`!Mailbox` instance. .. note:: @@ -269,7 +285,7 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. method:: flush() Write any pending changes to the filesystem. For some :class:`Mailbox` - subclasses, changes are always written immediately and :meth:`flush` does + subclasses, changes are always written immediately and :meth:`!flush` does nothing, but you should still make a habit of calling this method. @@ -290,13 +306,13 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. method:: close() Flush the mailbox, unlock it if necessary, and close any open files. For - some :class:`Mailbox` subclasses, this method does nothing. + some :class:`!Mailbox` subclasses, this method does nothing. .. _mailbox-maildir: -:class:`Maildir` -^^^^^^^^^^^^^^^^ +:class:`!Maildir` objects +^^^^^^^^^^^^^^^^^^^^^^^^^ .. class:: Maildir(dirname, factory=None, create=True) @@ -330,11 +346,11 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. Folders of the style introduced by the Courier mail transfer agent are also supported. Any subdirectory of the main mailbox is considered a folder if ``'.'`` is the first character in its name. Folder names are represented by - :class:`Maildir` without the leading ``'.'``. Each folder is itself a Maildir + :class:`!Maildir` without the leading ``'.'``. Each folder is itself a Maildir mailbox but should not contain other folders. Instead, a logical nesting is indicated using ``'.'`` to delimit levels, e.g., "Archived.2005.07". - .. note:: + .. attribute:: Maildir.colon The Maildir specification requires the use of a colon (``':'``) in certain message file names. However, some operating systems do not permit this @@ -346,9 +362,9 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. import mailbox mailbox.Maildir.colon = '!' - The :attr:`colon` attribute may also be set on a per-instance basis. + The :attr:`!colon` attribute may also be set on a per-instance basis. - :class:`Maildir` instances have all of the methods of :class:`Mailbox` in + :class:`!Maildir` instances have all of the methods of :class:`Mailbox` in addition to the following: @@ -359,14 +375,14 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. method:: get_folder(folder) - Return a :class:`Maildir` instance representing the folder whose name is + Return a :class:`!Maildir` instance representing the folder whose name is *folder*. A :exc:`NoSuchMailboxError` exception is raised if the folder does not exist. .. method:: add_folder(folder) - Create a folder whose name is *folder* and return a :class:`Maildir` + Create a folder whose name is *folder* and return a :class:`!Maildir` instance representing it. @@ -485,7 +501,7 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. versionadded:: 3.13 - Some :class:`Mailbox` methods implemented by :class:`Maildir` deserve special + Some :class:`Mailbox` methods implemented by :class:`!Maildir` deserve special remarks: @@ -516,7 +532,7 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. method:: close() - :class:`Maildir` instances do not keep any open files and the underlying + :class:`!Maildir` instances do not keep any open files and the underlying mailboxes do not support locking, so this method does nothing. @@ -539,8 +555,8 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. _mailbox-mbox: -:class:`mbox` -^^^^^^^^^^^^^ +:class:`!mbox` objects +^^^^^^^^^^^^^^^^^^^^^^ .. class:: mbox(path, factory=None, create=True) @@ -557,22 +573,22 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. each message indicated by a line whose first five characters are "From ". Several variations of the mbox format exist to address perceived shortcomings in - the original. In the interest of compatibility, :class:`mbox` implements the + the original. In the interest of compatibility, :class:`!mbox` implements the original format, which is sometimes referred to as :dfn:`mboxo`. This means that the :mailheader:`Content-Length` header, if present, is ignored and that any occurrences of "From " at the beginning of a line in a message body are transformed to ">From " when storing the message, although occurrences of ">From " are not transformed to "From " when reading the message. - Some :class:`Mailbox` methods implemented by :class:`mbox` deserve special + Some :class:`Mailbox` methods implemented by :class:`!mbox` deserve special remarks: .. method:: get_file(key) - Using the file after calling :meth:`flush` or :meth:`close` on the - :class:`mbox` instance may yield unpredictable results or raise an - exception. + Using the file after calling :meth:`~Mailbox.flush` or + :meth:`~Mailbox.close` on the :class:`!mbox` instance may yield + unpredictable results or raise an exception. .. method:: lock() @@ -596,8 +612,8 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. _mailbox-mh: -:class:`MH` -^^^^^^^^^^^ +:class:`!MH` objects +^^^^^^^^^^^^^^^^^^^^ .. class:: MH(path, factory=None, create=True) @@ -617,12 +633,12 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. messages without moving them to sub-folders. Sequences are defined in a file called :file:`.mh_sequences` in each folder. - The :class:`MH` class manipulates MH mailboxes, but it does not attempt to + The :class:`!MH` class manipulates MH mailboxes, but it does not attempt to emulate all of :program:`mh`'s behaviors. In particular, it does not modify and is not affected by the :file:`context` or :file:`.mh_profile` files that are used by :program:`mh` to store its state and configuration. - :class:`MH` instances have all of the methods of :class:`Mailbox` in addition + :class:`!MH` instances have all of the methods of :class:`Mailbox` in addition to the following: @@ -633,14 +649,14 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. method:: get_folder(folder) - Return an :class:`MH` instance representing the folder whose name is + Return an :class:`!MH` instance representing the folder whose name is *folder*. A :exc:`NoSuchMailboxError` exception is raised if the folder does not exist. .. method:: add_folder(folder) - Create a folder whose name is *folder* and return an :class:`MH` instance + Create a folder whose name is *folder* and return an :class:`!MH` instance representing it. @@ -674,7 +690,7 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. Already-issued keys are invalidated by this operation and should not be subsequently used. - Some :class:`Mailbox` methods implemented by :class:`MH` deserve special + Some :class:`Mailbox` methods implemented by :class:`!MH` deserve special remarks: @@ -710,7 +726,7 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. method:: close() - :class:`MH` instances do not keep any open files, so this method is + :class:`!MH` instances do not keep any open files, so this method is equivalent to :meth:`unlock`. @@ -726,8 +742,8 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. _mailbox-babyl: -:class:`Babyl` -^^^^^^^^^^^^^^ +:class:`!Babyl` objects +^^^^^^^^^^^^^^^^^^^^^^^ .. class:: Babyl(path, factory=None, create=True) @@ -754,7 +770,7 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. message, and a list of all user-defined labels found in the mailbox is kept in the Babyl options section. - :class:`Babyl` instances have all of the methods of :class:`Mailbox` in + :class:`!Babyl` instances have all of the methods of :class:`Mailbox` in addition to the following: @@ -769,7 +785,7 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. options section, but the Babyl section is updated whenever the mailbox is modified. - Some :class:`Mailbox` methods implemented by :class:`Babyl` deserve special + Some :class:`Mailbox` methods implemented by :class:`!Babyl` deserve special remarks: @@ -802,8 +818,8 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. _mailbox-mmdf: -:class:`MMDF` -^^^^^^^^^^^^^ +:class:`!MMDF` objects +^^^^^^^^^^^^^^^^^^^^^^ .. class:: MMDF(path, factory=None, create=True) @@ -824,15 +840,15 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. ">From " when storing messages because the extra message separator lines prevent mistaking such occurrences for the starts of subsequent messages. - Some :class:`Mailbox` methods implemented by :class:`MMDF` deserve special + Some :class:`Mailbox` methods implemented by :class:`!MMDF` deserve special remarks: .. method:: get_file(key) - Using the file after calling :meth:`flush` or :meth:`close` on the - :class:`MMDF` instance may yield unpredictable results or raise an - exception. + Using the file after calling :meth:`~Mailbox.flush` or + :meth:`~Mailbox.close` on the :class:`!MMDF` instance may yield + unpredictable results or raise an exception. .. method:: lock() @@ -854,20 +870,20 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. _mailbox-message-objects: -:class:`Message` objects ------------------------- +:class:`!Message` objects +------------------------- .. class:: Message(message=None) A subclass of the :mod:`email.message` module's - :class:`~email.message.Message`. Subclasses of :class:`mailbox.Message` add + :class:`~email.message.Message`. Subclasses of :class:`!mailbox.Message` add mailbox-format-specific state and behavior. If *message* is omitted, the new instance is created in a default, empty state. If *message* is an :class:`email.message.Message` instance, its contents are copied; furthermore, any format-specific information is converted insofar as - possible if *message* is a :class:`Message` instance. If *message* is a string, + possible if *message* is a :class:`!Message` instance. If *message* is a string, a byte string, or a file, it should contain an :rfc:`2822`\ -compliant message, which is read and parsed. Files should be open in binary mode, but text mode files @@ -882,18 +898,18 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. such as whether a message has been read by the user or marked as important is retained, because it applies to the message itself. - There is no requirement that :class:`Message` instances be used to represent + There is no requirement that :class:`!Message` instances be used to represent messages retrieved using :class:`Mailbox` instances. In some situations, the - time and memory required to generate :class:`Message` representations might - not be acceptable. For such situations, :class:`Mailbox` instances also + time and memory required to generate :class:`!Message` representations might + not be acceptable. For such situations, :class:`!Mailbox` instances also offer string and file-like representations, and a custom message factory may - be specified when a :class:`Mailbox` instance is initialized. + be specified when a :class:`!Mailbox` instance is initialized. .. _mailbox-maildirmessage: -:class:`MaildirMessage` -^^^^^^^^^^^^^^^^^^^^^^^ +:class:`!MaildirMessage` objects +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. class:: MaildirMessage(message=None) @@ -928,7 +944,7 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. | T | Trashed | Marked for subsequent deletion | +------+---------+--------------------------------+ - :class:`MaildirMessage` instances offer the following methods: + :class:`!MaildirMessage` instances offer the following methods: .. method:: get_subdir() @@ -1005,7 +1021,7 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. Set "info" to *info*, which should be a string. -When a :class:`MaildirMessage` instance is created based upon an +When a :class:`!MaildirMessage` instance is created based upon an :class:`mboxMessage` or :class:`MMDFMessage` instance, the :mailheader:`Status` and :mailheader:`X-Status` headers are omitted and the following conversions take place: @@ -1025,7 +1041,7 @@ take place: | T flag | D flag | +--------------------+----------------------------------------------+ -When a :class:`MaildirMessage` instance is created based upon an +When a :class:`!MaildirMessage` instance is created based upon an :class:`MHMessage` instance, the following conversions take place: +-------------------------------+--------------------------+ @@ -1040,7 +1056,7 @@ When a :class:`MaildirMessage` instance is created based upon an | R flag | "replied" sequence | +-------------------------------+--------------------------+ -When a :class:`MaildirMessage` instance is created based upon a +When a :class:`!MaildirMessage` instance is created based upon a :class:`BabylMessage` instance, the following conversions take place: +-------------------------------+-------------------------------+ @@ -1060,8 +1076,8 @@ When a :class:`MaildirMessage` instance is created based upon a .. _mailbox-mboxmessage: -:class:`mboxMessage` -^^^^^^^^^^^^^^^^^^^^ +:class:`!mboxMessage` objects +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. class:: mboxMessage(message=None) @@ -1097,7 +1113,7 @@ When a :class:`MaildirMessage` instance is created based upon a "D", "F", and "A" flags are stored in the :mailheader:`X-Status` header. The flags and headers typically appear in the order mentioned. - :class:`mboxMessage` instances offer the following methods: + :class:`!mboxMessage` instances offer the following methods: .. method:: get_from() @@ -1145,7 +1161,7 @@ When a :class:`MaildirMessage` instance is created based upon a remove more than one flag at a time, *flag* maybe a string of more than one character. -When an :class:`mboxMessage` instance is created based upon a +When an :class:`!mboxMessage` instance is created based upon a :class:`MaildirMessage` instance, a "From " line is generated based upon the :class:`MaildirMessage` instance's delivery date, and the following conversions take place: @@ -1164,7 +1180,7 @@ take place: | A flag | R flag | +-----------------+-------------------------------+ -When an :class:`mboxMessage` instance is created based upon an +When an :class:`!mboxMessage` instance is created based upon an :class:`MHMessage` instance, the following conversions take place: +-------------------+--------------------------+ @@ -1179,7 +1195,7 @@ When an :class:`mboxMessage` instance is created based upon an | A flag | "replied" sequence | +-------------------+--------------------------+ -When an :class:`mboxMessage` instance is created based upon a +When an :class:`!mboxMessage` instance is created based upon a :class:`BabylMessage` instance, the following conversions take place: +-------------------+-----------------------------+ @@ -1194,7 +1210,8 @@ When an :class:`mboxMessage` instance is created based upon a | A flag | "answered" label | +-------------------+-----------------------------+ -When a :class:`Message` instance is created based upon an :class:`MMDFMessage` +When a :class:`!mboxMessage` instance is created based upon an +:class:`MMDFMessage` instance, the "From " line is copied and all flags directly correspond: +-----------------+----------------------------+ @@ -1214,8 +1231,8 @@ instance, the "From " line is copied and all flags directly correspond: .. _mailbox-mhmessage: -:class:`MHMessage` -^^^^^^^^^^^^^^^^^^ +:class:`!MHMessage` objects +^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. class:: MHMessage(message=None) @@ -1239,7 +1256,7 @@ instance, the "From " line is copied and all flags directly correspond: | flagged | Marked as important | +----------+------------------------------------------+ - :class:`MHMessage` instances offer the following methods: + :class:`!MHMessage` instances offer the following methods: .. method:: get_sequences() @@ -1261,7 +1278,7 @@ instance, the "From " line is copied and all flags directly correspond: Remove *sequence* from the list of sequences that include this message. -When an :class:`MHMessage` instance is created based upon a +When an :class:`!MHMessage` instance is created based upon a :class:`MaildirMessage` instance, the following conversions take place: +--------------------+-------------------------------+ @@ -1274,7 +1291,7 @@ When an :class:`MHMessage` instance is created based upon a | "flagged" sequence | F flag | +--------------------+-------------------------------+ -When an :class:`MHMessage` instance is created based upon an +When an :class:`!MHMessage` instance is created based upon an :class:`mboxMessage` or :class:`MMDFMessage` instance, the :mailheader:`Status` and :mailheader:`X-Status` headers are omitted and the following conversions take place: @@ -1290,7 +1307,7 @@ take place: | "flagged" sequence | F flag | +--------------------+----------------------------------------------+ -When an :class:`MHMessage` instance is created based upon a +When an :class:`!MHMessage` instance is created based upon a :class:`BabylMessage` instance, the following conversions take place: +--------------------+-----------------------------+ @@ -1304,8 +1321,8 @@ When an :class:`MHMessage` instance is created based upon a .. _mailbox-babylmessage: -:class:`BabylMessage` -^^^^^^^^^^^^^^^^^^^^^ +:class:`!BabylMessage` objects +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. class:: BabylMessage(message=None) @@ -1334,11 +1351,11 @@ When an :class:`MHMessage` instance is created based upon a | resent | Resent | +-----------+------------------------------------------+ - By default, Rmail displays only visible headers. The :class:`BabylMessage` + By default, Rmail displays only visible headers. The :class:`!BabylMessage` class, though, uses the original headers because they are more complete. Visible headers may be accessed explicitly if desired. - :class:`BabylMessage` instances offer the following methods: + :class:`!BabylMessage` instances offer the following methods: .. method:: get_labels() @@ -1377,7 +1394,7 @@ When an :class:`MHMessage` instance is created based upon a .. method:: update_visible() - When a :class:`BabylMessage` instance's original headers are modified, the + When a :class:`!BabylMessage` instance's original headers are modified, the visible headers are not automatically modified to correspond. This method updates the visible headers as follows: each visible header with a corresponding original header is set to the value of the original header, @@ -1387,7 +1404,7 @@ When an :class:`MHMessage` instance is created based upon a present in the original headers but not the visible headers are added to the visible headers. -When a :class:`BabylMessage` instance is created based upon a +When a :class:`!BabylMessage` instance is created based upon a :class:`MaildirMessage` instance, the following conversions take place: +-------------------+-------------------------------+ @@ -1402,7 +1419,7 @@ When a :class:`BabylMessage` instance is created based upon a | "forwarded" label | P flag | +-------------------+-------------------------------+ -When a :class:`BabylMessage` instance is created based upon an +When a :class:`!BabylMessage` instance is created based upon an :class:`mboxMessage` or :class:`MMDFMessage` instance, the :mailheader:`Status` and :mailheader:`X-Status` headers are omitted and the following conversions take place: @@ -1418,7 +1435,7 @@ take place: | "answered" label | A flag | +------------------+----------------------------------------------+ -When a :class:`BabylMessage` instance is created based upon an +When a :class:`!BabylMessage` instance is created based upon an :class:`MHMessage` instance, the following conversions take place: +------------------+--------------------------+ @@ -1432,8 +1449,8 @@ When a :class:`BabylMessage` instance is created based upon an .. _mailbox-mmdfmessage: -:class:`MMDFMessage` -^^^^^^^^^^^^^^^^^^^^ +:class:`!MMDFMessage` objects +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. class:: MMDFMessage(message=None) @@ -1467,7 +1484,7 @@ When a :class:`BabylMessage` instance is created based upon an "D", "F", and "A" flags are stored in the :mailheader:`X-Status` header. The flags and headers typically appear in the order mentioned. - :class:`MMDFMessage` instances offer the following methods, which are + :class:`!MMDFMessage` instances offer the following methods, which are identical to those offered by :class:`mboxMessage`: @@ -1516,7 +1533,7 @@ When a :class:`BabylMessage` instance is created based upon an remove more than one flag at a time, *flag* maybe a string of more than one character. -When an :class:`MMDFMessage` instance is created based upon a +When an :class:`!MMDFMessage` instance is created based upon a :class:`MaildirMessage` instance, a "From " line is generated based upon the :class:`MaildirMessage` instance's delivery date, and the following conversions take place: @@ -1535,7 +1552,7 @@ take place: | A flag | R flag | +-----------------+-------------------------------+ -When an :class:`MMDFMessage` instance is created based upon an +When an :class:`!MMDFMessage` instance is created based upon an :class:`MHMessage` instance, the following conversions take place: +-------------------+--------------------------+ @@ -1550,7 +1567,7 @@ When an :class:`MMDFMessage` instance is created based upon an | A flag | "replied" sequence | +-------------------+--------------------------+ -When an :class:`MMDFMessage` instance is created based upon a +When an :class:`!MMDFMessage` instance is created based upon a :class:`BabylMessage` instance, the following conversions take place: +-------------------+-----------------------------+ @@ -1565,7 +1582,7 @@ When an :class:`MMDFMessage` instance is created based upon a | A flag | "answered" label | +-------------------+-----------------------------+ -When an :class:`MMDFMessage` instance is created based upon an +When an :class:`!MMDFMessage` instance is created based upon an :class:`mboxMessage` instance, the "From " line is copied and all flags directly correspond: @@ -1587,7 +1604,7 @@ correspond: Exceptions ---------- -The following exception classes are defined in the :mod:`mailbox` module: +The following exception classes are defined in the :mod:`!mailbox` module: .. exception:: Error() diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 20580f78e07b5d..d9147aaeee12bd 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -62,7 +62,6 @@ Doc/library/locale.rst Doc/library/logging.config.rst Doc/library/logging.handlers.rst Doc/library/lzma.rst -Doc/library/mailbox.rst Doc/library/mmap.rst Doc/library/multiprocessing.rst Doc/library/multiprocessing.shared_memory.rst From 5f7d7353b47ccf634b9b65f933d3fdeeb395301f Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Thu, 14 Dec 2023 17:27:39 -0600 Subject: [PATCH 26/62] Optimize unique_justseen() recipe for a common case. (gh-113147) --- Doc/library/itertools.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 36cea9a835f302..03127afe1b4460 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -1017,6 +1017,8 @@ which incur interpreter overhead. "List unique elements, preserving order. Remember only the element just seen." # unique_justseen('AAAABBBCCDAABBB') --> A B C D A B # unique_justseen('ABBcCAD', str.lower) --> A B c A D + if key is None: + return map(operator.itemgetter(0), groupby(iterable)) return map(next, map(operator.itemgetter(1), groupby(iterable, key))) From f34e22c6470d1c5d0e2a3ffb2272d375c22654b9 Mon Sep 17 00:00:00 2001 From: Furkan Onder Date: Fri, 15 Dec 2023 02:42:33 +0300 Subject: [PATCH 27/62] gh-112535: Update _Py_ThreadId() to support RISC-V (gh-113084) Update _Py_ThreadId() to support RISC-V --- Include/object.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Include/object.h b/Include/object.h index bd576b0bd43211..d22e5c2b8be2a9 100644 --- a/Include/object.h +++ b/Include/object.h @@ -283,6 +283,13 @@ _Py_ThreadId(void) // Both GCC and Clang have supported __builtin_thread_pointer // for s390 from long time ago. tid = (uintptr_t)__builtin_thread_pointer(); +#elif defined(__riscv) + #if defined(__clang__) && _Py__has_builtin(__builtin_thread_pointer) + tid = (uintptr_t)__builtin_thread_pointer(); + #else + // tp is Thread Pointer provided by the RISC-V ABI. + __asm__ ("mv %0, tp" : "=r" (tid)); + #endif #else # error "define _Py_ThreadId for this platform" #endif From 7bb00f053e0a86bb8827cc464961a4fad9278e6d Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 15 Dec 2023 08:57:23 +0000 Subject: [PATCH 28/62] gh-101100: Fix Sphinx nitpicks in `library/rlcompleter.rst` (#113125) --- Doc/library/readline.rst | 2 ++ Doc/library/rlcompleter.rst | 40 ++++++++++++++++++++----------------- Doc/tools/.nitignore | 1 - 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/Doc/library/readline.rst b/Doc/library/readline.rst index 2e0f45ced30b9c..1adafcaa02eab9 100644 --- a/Doc/library/readline.rst +++ b/Doc/library/readline.rst @@ -218,6 +218,8 @@ Startup hooks if Python was compiled for a version of the library that supports it. +.. _readline-completion: + Completion ---------- diff --git a/Doc/library/rlcompleter.rst b/Doc/library/rlcompleter.rst index 40b09ce897880e..8287699c5f013e 100644 --- a/Doc/library/rlcompleter.rst +++ b/Doc/library/rlcompleter.rst @@ -10,12 +10,14 @@ -------------- -The :mod:`rlcompleter` module defines a completion function suitable for the -:mod:`readline` module by completing valid Python identifiers and keywords. +The :mod:`!rlcompleter` module defines a completion function suitable to be +passed to :func:`~readline.set_completer` in the :mod:`readline` module. When this module is imported on a Unix platform with the :mod:`readline` module available, an instance of the :class:`Completer` class is automatically created -and its :meth:`complete` method is set as the :mod:`readline` completer. +and its :meth:`~Completer.complete` method is set as the +:ref:`readline completer `. The method provides +completion of valid Python :ref:`identifiers and keywords `. Example:: @@ -28,7 +30,7 @@ Example:: readline.__name__ readline.parse_and_bind( >>> readline. -The :mod:`rlcompleter` module is designed for use with Python's +The :mod:`!rlcompleter` module is designed for use with Python's :ref:`interactive mode `. Unless Python is run with the :option:`-S` option, the module is automatically imported and configured (see :ref:`rlcompleter-config`). @@ -39,23 +41,25 @@ this module can still be used for custom purposes. .. _completer-objects: -Completer Objects ------------------ +.. class:: Completer -Completer objects have the following method: + Completer objects have the following method: + .. method:: Completer.complete(text, state) -.. method:: Completer.complete(text, state) + Return the next possible completion for *text*. - Return the *state*\ th completion for *text*. + When called by the :mod:`readline` module, this method is called + successively with ``state == 0, 1, 2, ...`` until the method returns + ``None``. - If called for *text* that doesn't include a period character (``'.'``), it will - complete from names currently defined in :mod:`__main__`, :mod:`builtins` and - keywords (as defined by the :mod:`keyword` module). - - If called for a dotted name, it will try to evaluate anything without obvious - side-effects (functions will not be evaluated, but it can generate calls to - :meth:`__getattr__`) up to the last part, and find matches for the rest via the - :func:`dir` function. Any exception raised during the evaluation of the - expression is caught, silenced and :const:`None` is returned. + If called for *text* that doesn't include a period character (``'.'``), it will + complete from names currently defined in :mod:`__main__`, :mod:`builtins` and + keywords (as defined by the :mod:`keyword` module). + If called for a dotted name, it will try to evaluate anything without obvious + side-effects (functions will not be evaluated, but it can generate calls to + :meth:`~object.__getattr__`) up to the last part, and find matches for the + rest via the :func:`dir` function. Any exception raised during the + evaluation of the expression is caught, silenced and :const:`None` is + returned. diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index d9147aaeee12bd..c91e698ff0753a 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -79,7 +79,6 @@ Doc/library/pyexpat.rst Doc/library/random.rst Doc/library/readline.rst Doc/library/resource.rst -Doc/library/rlcompleter.rst Doc/library/select.rst Doc/library/signal.rst Doc/library/smtplib.rst From 55ef998a8dead3874e8390284081290c1ccb46e2 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Fri, 15 Dec 2023 12:28:22 +0000 Subject: [PATCH 29/62] gh-112720: Move dis's cache output code to the Formatter, labels lookup to the arg_resolver. Reduce the number of parameters passed around. (#113108) --- Lib/dis.py | 168 +++++++++++++++++++++++-------------------- Lib/test/test_dis.py | 17 +++-- 2 files changed, 104 insertions(+), 81 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index 183091cb0d6098..1a2f1032d500af 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -113,7 +113,14 @@ def dis(x=None, *, file=None, depth=None, show_caches=False, adaptive=False, elif hasattr(x, 'co_code'): # Code object _disassemble_recursive(x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets) elif isinstance(x, (bytes, bytearray)): # Raw bytecode - _disassemble_bytes(x, file=file, show_caches=show_caches, show_offsets=show_offsets) + labels_map = _make_labels_map(x) + label_width = 4 + len(str(len(labels_map))) + formatter = Formatter(file=file, + offset_width=len(str(max(len(x) - 2, 9999))) if show_offsets else 0, + label_width=label_width, + show_caches=show_caches) + arg_resolver = ArgResolver(labels_map=labels_map) + _disassemble_bytes(x, arg_resolver=arg_resolver, formatter=formatter) elif isinstance(x, str): # Source code _disassemble_str(x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets) else: @@ -394,23 +401,41 @@ def __str__(self): class Formatter: def __init__(self, file=None, lineno_width=0, offset_width=0, label_width=0, - line_offset=0): + line_offset=0, show_caches=False): """Create a Formatter *file* where to write the output *lineno_width* sets the width of the line number field (0 omits it) *offset_width* sets the width of the instruction offset field *label_width* sets the width of the label field + *show_caches* is a boolean indicating whether to display cache lines - *line_offset* the line number (within the code unit) """ self.file = file self.lineno_width = lineno_width self.offset_width = offset_width self.label_width = label_width - + self.show_caches = show_caches def print_instruction(self, instr, mark_as_current=False): + self.print_instruction_line(instr, mark_as_current) + if self.show_caches and instr.cache_info: + offset = instr.offset + for name, size, data in instr.cache_info: + for i in range(size): + offset += 2 + # Only show the fancy argrepr for a CACHE instruction when it's + # the first entry for a particular cache value: + if i == 0: + argrepr = f"{name}: {int.from_bytes(data, sys.byteorder)}" + else: + argrepr = "" + self.print_instruction_line( + Instruction("CACHE", CACHE, 0, None, argrepr, offset, offset, + False, None, None, instr.positions), + False) + + def print_instruction_line(self, instr, mark_as_current): """Format instruction details for inclusion in disassembly output.""" lineno_width = self.lineno_width offset_width = self.offset_width @@ -474,11 +499,14 @@ def print_exception_table(self, exception_entries): class ArgResolver: - def __init__(self, co_consts, names, varname_from_oparg, labels_map): + def __init__(self, co_consts=None, names=None, varname_from_oparg=None, labels_map=None): self.co_consts = co_consts self.names = names self.varname_from_oparg = varname_from_oparg - self.labels_map = labels_map + self.labels_map = labels_map or {} + + def get_label_for_offset(self, offset): + return self.labels_map.get(offset, None) def get_argval_argrepr(self, op, arg, offset): get_name = None if self.names is None else self.names.__getitem__ @@ -547,8 +575,7 @@ def get_argval_argrepr(self, op, arg, offset): argrepr = _intrinsic_2_descs[arg] return argval, argrepr - -def get_instructions(x, *, first_line=None, show_caches=False, adaptive=False): +def get_instructions(x, *, first_line=None, show_caches=None, adaptive=False): """Iterator for the opcodes in methods, functions or code Generates a series of Instruction named tuples giving the details of @@ -567,9 +594,10 @@ def get_instructions(x, *, first_line=None, show_caches=False, adaptive=False): line_offset = 0 original_code = co.co_code - labels_map = _make_labels_map(original_code) - arg_resolver = ArgResolver(co.co_consts, co.co_names, co._varname_from_oparg, - labels_map) + arg_resolver = ArgResolver(co_consts=co.co_consts, + names=co.co_names, + varname_from_oparg=co._varname_from_oparg, + labels_map=_make_labels_map(original_code)) return _get_instructions_bytes(_get_code_array(co, adaptive), linestarts=linestarts, line_offset=line_offset, @@ -648,7 +676,7 @@ def _is_backward_jump(op): 'ENTER_EXECUTOR') def _get_instructions_bytes(code, linestarts=None, line_offset=0, co_positions=None, - original_code=None, labels_map=None, arg_resolver=None): + original_code=None, arg_resolver=None): """Iterate over the instructions in a bytecode string. Generates a sequence of Instruction namedtuples giving the details of each @@ -661,8 +689,6 @@ def _get_instructions_bytes(code, linestarts=None, line_offset=0, co_positions=N original_code = original_code or code co_positions = co_positions or iter(()) - labels_map = labels_map or _make_labels_map(original_code) - starts_line = False local_line_number = None line_number = None @@ -684,10 +710,6 @@ def _get_instructions_bytes(code, linestarts=None, line_offset=0, co_positions=N else: argval, argrepr = arg, repr(arg) - instr = Instruction(_all_opname[op], op, arg, argval, argrepr, - offset, start_offset, starts_line, line_number, - labels_map.get(offset, None), positions) - caches = _get_cache_size(_all_opname[deop]) # Advance the co_positions iterator: for _ in range(caches): @@ -701,10 +723,10 @@ def _get_instructions_bytes(code, linestarts=None, line_offset=0, co_positions=N else: cache_info = None + label = arg_resolver.get_label_for_offset(offset) if arg_resolver else None yield Instruction(_all_opname[op], op, arg, argval, argrepr, offset, start_offset, starts_line, line_number, - labels_map.get(offset, None), positions, cache_info) - + label, positions, cache_info) def disassemble(co, lasti=-1, *, file=None, show_caches=False, adaptive=False, @@ -712,12 +734,20 @@ def disassemble(co, lasti=-1, *, file=None, show_caches=False, adaptive=False, """Disassemble a code object.""" linestarts = dict(findlinestarts(co)) exception_entries = _parse_exception_table(co) - _disassemble_bytes(_get_code_array(co, adaptive), - lasti, co._varname_from_oparg, - co.co_names, co.co_consts, linestarts, file=file, - exception_entries=exception_entries, - co_positions=co.co_positions(), show_caches=show_caches, - original_code=co.co_code, show_offsets=show_offsets) + labels_map = _make_labels_map(co.co_code, exception_entries=exception_entries) + label_width = 4 + len(str(len(labels_map))) + formatter = Formatter(file=file, + lineno_width=_get_lineno_width(linestarts), + offset_width=len(str(max(len(co.co_code) - 2, 9999))) if show_offsets else 0, + label_width=label_width, + show_caches=show_caches) + arg_resolver = ArgResolver(co_consts=co.co_consts, + names=co.co_names, + varname_from_oparg=co._varname_from_oparg, + labels_map=labels_map) + _disassemble_bytes(_get_code_array(co, adaptive), lasti, linestarts, + exception_entries=exception_entries, co_positions=co.co_positions(), + original_code=co.co_code, arg_resolver=arg_resolver, formatter=formatter) def _disassemble_recursive(co, *, file=None, depth=None, show_caches=False, adaptive=False, show_offsets=False): disassemble(co, file=file, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets) @@ -764,60 +794,29 @@ def _get_lineno_width(linestarts): return lineno_width -def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, - names=None, co_consts=None, linestarts=None, - *, file=None, line_offset=0, exception_entries=(), - co_positions=None, show_caches=False, original_code=None, - show_offsets=False): - - offset_width = len(str(max(len(code) - 2, 9999))) if show_offsets else 0 - - labels_map = _make_labels_map(original_code or code, exception_entries) - label_width = 4 + len(str(len(labels_map))) +def _disassemble_bytes(code, lasti=-1, linestarts=None, + *, line_offset=0, exception_entries=(), + co_positions=None, original_code=None, + arg_resolver=None, formatter=None): - formatter = Formatter(file=file, - lineno_width=_get_lineno_width(linestarts), - offset_width=offset_width, - label_width=label_width, - line_offset=line_offset) + assert formatter is not None + assert arg_resolver is not None - arg_resolver = ArgResolver(co_consts, names, varname_from_oparg, labels_map) instrs = _get_instructions_bytes(code, linestarts=linestarts, line_offset=line_offset, co_positions=co_positions, original_code=original_code, - labels_map=labels_map, arg_resolver=arg_resolver) - print_instructions(instrs, exception_entries, formatter, - show_caches=show_caches, lasti=lasti) + print_instructions(instrs, exception_entries, formatter, lasti=lasti) -def print_instructions(instrs, exception_entries, formatter, show_caches=False, lasti=-1): +def print_instructions(instrs, exception_entries, formatter, lasti=-1): for instr in instrs: - if show_caches: - is_current_instr = instr.offset == lasti - else: - # Each CACHE takes 2 bytes - is_current_instr = instr.offset <= lasti \ - <= instr.offset + 2 * _get_cache_size(_all_opname[_deoptop(instr.opcode)]) + # Each CACHE takes 2 bytes + is_current_instr = instr.offset <= lasti \ + <= instr.offset + 2 * _get_cache_size(_all_opname[_deoptop(instr.opcode)]) formatter.print_instruction(instr, is_current_instr) - deop = _deoptop(instr.opcode) - if show_caches and instr.cache_info: - offset = instr.offset - for name, size, data in instr.cache_info: - for i in range(size): - offset += 2 - # Only show the fancy argrepr for a CACHE instruction when it's - # the first entry for a particular cache value: - if i == 0: - argrepr = f"{name}: {int.from_bytes(data, sys.byteorder)}" - else: - argrepr = "" - formatter.print_instruction( - Instruction("CACHE", CACHE, 0, None, argrepr, offset, offset, - False, None, None, instr.positions), - is_current_instr) formatter.print_exception_table(exception_entries) @@ -960,14 +959,15 @@ def __iter__(self): co = self.codeobj original_code = co.co_code labels_map = _make_labels_map(original_code, self.exception_entries) - arg_resolver = ArgResolver(co.co_consts, co.co_names, co._varname_from_oparg, - labels_map) + arg_resolver = ArgResolver(co_consts=co.co_consts, + names=co.co_names, + varname_from_oparg=co._varname_from_oparg, + labels_map=labels_map) return _get_instructions_bytes(_get_code_array(co, self.adaptive), linestarts=self._linestarts, line_offset=self._line_offset, co_positions=co.co_positions(), original_code=original_code, - labels_map=labels_map, arg_resolver=arg_resolver) def __repr__(self): @@ -995,18 +995,32 @@ def dis(self): else: offset = -1 with io.StringIO() as output: - _disassemble_bytes(_get_code_array(co, self.adaptive), - varname_from_oparg=co._varname_from_oparg, - names=co.co_names, co_consts=co.co_consts, + code = _get_code_array(co, self.adaptive) + offset_width = len(str(max(len(code) - 2, 9999))) if self.show_offsets else 0 + + + labels_map = _make_labels_map(co.co_code, self.exception_entries) + label_width = 4 + len(str(len(labels_map))) + formatter = Formatter(file=output, + lineno_width=_get_lineno_width(self._linestarts), + offset_width=offset_width, + label_width=label_width, + line_offset=self._line_offset, + show_caches=self.show_caches) + + arg_resolver = ArgResolver(co_consts=co.co_consts, + names=co.co_names, + varname_from_oparg=co._varname_from_oparg, + labels_map=labels_map) + _disassemble_bytes(code, linestarts=self._linestarts, line_offset=self._line_offset, - file=output, lasti=offset, exception_entries=self.exception_entries, co_positions=co.co_positions(), - show_caches=self.show_caches, original_code=co.co_code, - show_offsets=self.show_offsets) + arg_resolver=arg_resolver, + formatter=formatter) return output.getvalue() diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 12e2c57e50b0ba..0c7fd60f640854 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -2,6 +2,7 @@ import contextlib import dis +import functools import io import re import sys @@ -1982,19 +1983,27 @@ def f(opcode, oparg, offset, *init_args): self.assertEqual(f(opcode.opmap["BINARY_OP"], 3, *args), (3, '<<')) self.assertEqual(f(opcode.opmap["CALL_INTRINSIC_1"], 2, *args), (2, 'INTRINSIC_IMPORT_STAR')) + def get_instructions(self, code): + return dis._get_instructions_bytes(code) + def test_start_offset(self): # When no extended args are present, # start_offset should be equal to offset + instructions = list(dis.Bytecode(_f)) for instruction in instructions: self.assertEqual(instruction.offset, instruction.start_offset) + def last_item(iterable): + return functools.reduce(lambda a, b : b, iterable) + code = bytes([ opcode.opmap["LOAD_FAST"], 0x00, opcode.opmap["EXTENDED_ARG"], 0x01, opcode.opmap["POP_JUMP_IF_TRUE"], 0xFF, ]) - jump = list(dis._get_instructions_bytes(code))[-1] + labels_map = dis._make_labels_map(code) + jump = last_item(self.get_instructions(code)) self.assertEqual(4, jump.offset) self.assertEqual(2, jump.start_offset) @@ -2006,7 +2015,7 @@ def test_start_offset(self): opcode.opmap["POP_JUMP_IF_TRUE"], 0xFF, opcode.opmap["CACHE"], 0x00, ]) - jump = list(dis._get_instructions_bytes(code))[-1] + jump = last_item(self.get_instructions(code)) self.assertEqual(8, jump.offset) self.assertEqual(2, jump.start_offset) @@ -2021,7 +2030,7 @@ def test_start_offset(self): opcode.opmap["POP_JUMP_IF_TRUE"], 0xFF, opcode.opmap["CACHE"], 0x00, ]) - instructions = list(dis._get_instructions_bytes(code)) + instructions = list(self.get_instructions(code)) # 1st jump self.assertEqual(4, instructions[2].offset) self.assertEqual(2, instructions[2].start_offset) @@ -2042,7 +2051,7 @@ def test_cache_offset_and_end_offset(self): opcode.opmap["CACHE"], 0x00, opcode.opmap["CACHE"], 0x00 ]) - instructions = list(dis._get_instructions_bytes(code)) + instructions = list(self.get_instructions(code)) self.assertEqual(2, instructions[0].cache_offset) self.assertEqual(10, instructions[0].end_offset) self.assertEqual(12, instructions[1].cache_offset) From 737d23ffcd16909274f21c932215ec104140fb1c Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Fri, 15 Dec 2023 05:03:17 -0800 Subject: [PATCH 30/62] GH-111485: Mark some instructions as `TIER_ONE_ONLY` (GH-113155) --- Python/bytecodes.c | 7 ++ Python/executor_cases.c.h | 139 ------------------------------------- Python/generated_cases.c.h | 7 ++ 3 files changed, 14 insertions(+), 139 deletions(-) diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 68bb15c2b536eb..19e2268046fcdc 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -506,6 +506,7 @@ dummy_func( // specializations, but there is no output. // At the end we just skip over the STORE_FAST. op(_BINARY_OP_INPLACE_ADD_UNICODE, (unused/1, left, right --)) { + TIER_ONE_ONLY assert(next_instr->op.code == STORE_FAST); PyObject **target_local = &GETLOCAL(next_instr->op.arg); DEOPT_IF(*target_local != left); @@ -786,6 +787,7 @@ dummy_func( } inst(INTERPRETER_EXIT, (retval --)) { + TIER_ONE_ONLY assert(frame == &entry_frame); assert(_PyFrame_IsIncomplete(frame)); /* Restore previous frame and return. */ @@ -1072,6 +1074,7 @@ dummy_func( } inst(YIELD_VALUE, (retval -- unused)) { + TIER_ONE_ONLY // NOTE: It's important that YIELD_VALUE never raises an exception! // The compiler treats any exception raised here as a failed close() // or throw() call. @@ -2297,6 +2300,7 @@ dummy_func( } inst(JUMP_FORWARD, (--)) { + TIER_ONE_ONLY JUMPBY(oparg); } @@ -2402,6 +2406,7 @@ dummy_func( macro(POP_JUMP_IF_NOT_NONE) = _IS_NONE + _POP_JUMP_IF_FALSE; inst(JUMP_BACKWARD_NO_INTERRUPT, (--)) { + TIER_ONE_ONLY /* This bytecode is used in the `yield from` or `await` loop. * If there is an interrupt, we want it handled in the innermost * generator or coroutine, so we deliberately do not check it here. @@ -3454,6 +3459,7 @@ dummy_func( // This is secretly a super-instruction inst(CALL_LIST_APPEND, (unused/1, unused/2, callable, self, args[oparg] -- unused)) { + TIER_ONE_ONLY assert(oparg == 1); PyInterpreterState *interp = tstate->interp; DEOPT_IF(callable != interp->callable_cache.list_append); @@ -3792,6 +3798,7 @@ dummy_func( } inst(RETURN_GENERATOR, (--)) { + TIER_ONE_ONLY assert(PyFunction_Check(frame->f_funcobj)); PyFunctionObject *func = (PyFunctionObject *)frame->f_funcobj; PyGenObject *gen = (PyGenObject *)_Py_MakeCoro(func); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 2519a4ee546a5e..7cb60cbc1dd3ff 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -375,38 +375,6 @@ break; } - case _BINARY_OP_INPLACE_ADD_UNICODE: { - PyObject *right; - PyObject *left; - right = stack_pointer[-1]; - left = stack_pointer[-2]; - assert(next_instr->op.code == STORE_FAST); - PyObject **target_local = &GETLOCAL(next_instr->op.arg); - if (*target_local != left) goto deoptimize; - STAT_INC(BINARY_OP, hit); - /* Handle `left = left + right` or `left += right` for str. - * - * When possible, extend `left` in place rather than - * allocating a new PyUnicodeObject. This attempts to avoid - * quadratic behavior when one neglects to use str.join(). - * - * If `left` has only two references remaining (one from - * the stack, one in the locals), DECREFing `left` leaves - * only the locals reference, so PyUnicode_Append knows - * that the string is safe to mutate. - */ - assert(Py_REFCNT(left) >= 2); - _Py_DECREF_NO_DEALLOC(left); - PyUnicode_Append(target_local, right); - _Py_DECREF_SPECIALIZED(right, _PyUnicode_ExactDealloc); - if (*target_local == NULL) goto pop_2_error_tier_two; - // The STORE_FAST is already done. - assert(next_instr->op.code == STORE_FAST); - SKIP_OVER(1); - stack_pointer += -2; - break; - } - case _BINARY_SUBSCR: { PyObject *sub; PyObject *container; @@ -690,18 +658,6 @@ break; } - case _INTERPRETER_EXIT: { - PyObject *retval; - retval = stack_pointer[-1]; - assert(frame == &entry_frame); - assert(_PyFrame_IsIncomplete(frame)); - /* Restore previous frame and return. */ - tstate->current_frame = frame->previous; - assert(!_PyErr_Occurred(tstate)); - tstate->c_recursion_remaining += PY_EVAL_C_STACK_UNITS; - return retval; - } - case _POP_FRAME: { PyObject *retval; retval = stack_pointer[-1]; @@ -846,33 +802,6 @@ /* _INSTRUMENTED_YIELD_VALUE is not a viable micro-op for tier 2 */ - case _YIELD_VALUE: { - PyObject *retval; - oparg = CURRENT_OPARG(); - retval = stack_pointer[-1]; - // NOTE: It's important that YIELD_VALUE never raises an exception! - // The compiler treats any exception raised here as a failed close() - // or throw() call. - assert(frame != &entry_frame); - frame->instr_ptr = next_instr; - PyGenObject *gen = _PyFrame_GetGenerator(frame); - assert(FRAME_SUSPENDED_YIELD_FROM == FRAME_SUSPENDED + 1); - assert(oparg == 0 || oparg == 1); - gen->gi_frame_state = FRAME_SUSPENDED + oparg; - _PyFrame_SetStackPointer(frame, stack_pointer - 1); - tstate->exc_info = gen->gi_exc_state.previous_item; - gen->gi_exc_state.previous_item = NULL; - _Py_LeaveRecursiveCallPy(tstate); - _PyInterpreterFrame *gen_frame = frame; - frame = tstate->current_frame = frame->previous; - gen_frame->previous = NULL; - _PyFrame_StackPush(frame, retval); - /* We don't know which of these is relevant here, so keep them equal */ - assert(INLINE_CACHE_ENTRIES_SEND == INLINE_CACHE_ENTRIES_FOR_ITER); - LOAD_IP(1 + INLINE_CACHE_ENTRIES_SEND); - goto resume_frame; - } - case _POP_EXCEPT: { PyObject *exc_value; exc_value = stack_pointer[-1]; @@ -2084,12 +2013,6 @@ break; } - case _JUMP_FORWARD: { - oparg = CURRENT_OPARG(); - JUMPBY(oparg); - break; - } - /* _JUMP_BACKWARD is not a viable micro-op for tier 2 */ /* _POP_JUMP_IF_FALSE is not a viable micro-op for tier 2 */ @@ -2111,17 +2034,6 @@ break; } - case _JUMP_BACKWARD_NO_INTERRUPT: { - oparg = CURRENT_OPARG(); - /* This bytecode is used in the `yield from` or `await` loop. - * If there is an interrupt, we want it handled in the innermost - * generator or coroutine, so we deliberately do not check it here. - * (see bpo-30039). - */ - JUMPBY(-oparg); - break; - } - case _GET_LEN: { PyObject *obj; PyObject *len_o; @@ -3060,32 +2972,6 @@ break; } - case _CALL_LIST_APPEND: { - PyObject **args; - PyObject *self; - PyObject *callable; - oparg = CURRENT_OPARG(); - args = &stack_pointer[-oparg]; - self = stack_pointer[-1 - oparg]; - callable = stack_pointer[-2 - oparg]; - assert(oparg == 1); - PyInterpreterState *interp = tstate->interp; - if (callable != interp->callable_cache.list_append) goto deoptimize; - assert(self != NULL); - if (!PyList_Check(self)) goto deoptimize; - STAT_INC(CALL, hit); - if (_PyList_AppendTakeRef((PyListObject *)self, args[0]) < 0) { - goto pop_1_error; // Since arg is DECREF'ed already - } - Py_DECREF(self); - Py_DECREF(callable); - STACK_SHRINK(3); - // Skip POP_TOP - assert(next_instr->op.code == POP_TOP); - SKIP_OVER(1); - DISPATCH(); - } - case _CALL_METHOD_DESCRIPTOR_O: { PyObject **args; PyObject *self_or_null; @@ -3307,31 +3193,6 @@ break; } - case _RETURN_GENERATOR: { - assert(PyFunction_Check(frame->f_funcobj)); - PyFunctionObject *func = (PyFunctionObject *)frame->f_funcobj; - PyGenObject *gen = (PyGenObject *)_Py_MakeCoro(func); - if (gen == NULL) { - GOTO_ERROR(error); - } - assert(EMPTY()); - _PyFrame_SetStackPointer(frame, stack_pointer); - _PyInterpreterFrame *gen_frame = (_PyInterpreterFrame *)gen->gi_iframe; - frame->instr_ptr = next_instr; - _PyFrame_Copy(frame, gen_frame); - assert(frame->frame_obj == NULL); - gen->gi_frame_state = FRAME_CREATED; - gen_frame->owner = FRAME_OWNED_BY_GENERATOR; - _Py_LeaveRecursiveCallPy(tstate); - assert(frame != &entry_frame); - _PyInterpreterFrame *prev = frame->previous; - _PyThreadState_PopFrame(tstate, frame); - frame = tstate->current_frame = prev; - _PyFrame_StackPush(frame, (PyObject *)gen); - LOAD_IP(frame->return_offset); - goto resume_frame; - } - case _BUILD_SLICE: { PyObject *step = NULL; PyObject *stop; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 65e6f11f68b38c..24f26722d7a745 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -238,6 +238,7 @@ } // _BINARY_OP_INPLACE_ADD_UNICODE { + TIER_ONE_ONLY assert(next_instr->op.code == STORE_FAST); PyObject **target_local = &GETLOCAL(next_instr->op.arg); DEOPT_IF(*target_local != left, BINARY_OP); @@ -1446,6 +1447,7 @@ args = &stack_pointer[-oparg]; self = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; + TIER_ONE_ONLY assert(oparg == 1); PyInterpreterState *interp = tstate->interp; DEOPT_IF(callable != interp->callable_cache.list_append, CALL); @@ -3182,6 +3184,7 @@ INSTRUCTION_STATS(INTERPRETER_EXIT); PyObject *retval; retval = stack_pointer[-1]; + TIER_ONE_ONLY assert(frame == &entry_frame); assert(_PyFrame_IsIncomplete(frame)); /* Restore previous frame and return. */ @@ -3253,6 +3256,7 @@ frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(JUMP_BACKWARD_NO_INTERRUPT); + TIER_ONE_ONLY /* This bytecode is used in the `yield from` or `await` loop. * If there is an interrupt, we want it handled in the innermost * generator or coroutine, so we deliberately do not check it here. @@ -3266,6 +3270,7 @@ frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(JUMP_FORWARD); + TIER_ONE_ONLY JUMPBY(oparg); DISPATCH(); } @@ -4793,6 +4798,7 @@ frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(RETURN_GENERATOR); + TIER_ONE_ONLY assert(PyFunction_Check(frame->f_funcobj)); PyFunctionObject *func = (PyFunctionObject *)frame->f_funcobj; PyGenObject *gen = (PyGenObject *)_Py_MakeCoro(func); @@ -5764,6 +5770,7 @@ INSTRUCTION_STATS(YIELD_VALUE); PyObject *retval; retval = stack_pointer[-1]; + TIER_ONE_ONLY // NOTE: It's important that YIELD_VALUE never raises an exception! // The compiler treats any exception raised here as a failed close() // or throw() call. From 8f8f0f97e126db9ca470fd7e7b2944c150db6305 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 15 Dec 2023 15:24:30 +0200 Subject: [PATCH 31/62] gh-61648: Detect line numbers of properties in doctests (GH-113161) --- Lib/doctest.py | 2 ++ Lib/test/doctest_lineno.py | 16 ++++++++++++++++ Lib/test/test_doctest.py | 2 ++ ...2023-12-15-12-35-28.gh-issue-61648.G-4pz0.rst | 1 + 4 files changed, 21 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2023-12-15-12-35-28.gh-issue-61648.G-4pz0.rst diff --git a/Lib/doctest.py b/Lib/doctest.py index d109b6c9e37343..114aac62a34e95 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -1136,6 +1136,8 @@ def _find_lineno(self, obj, source_lines): # Find the line number for functions & methods. if inspect.ismethod(obj): obj = obj.__func__ + if isinstance(obj, property): + obj = obj.fget if inspect.isfunction(obj) and getattr(obj, '__doc__', None): # We don't use `docstring` var here, because `obj` can be changed. obj = obj.__code__ diff --git a/Lib/test/doctest_lineno.py b/Lib/test/doctest_lineno.py index 729a68aceaa990..677c569cf710eb 100644 --- a/Lib/test/doctest_lineno.py +++ b/Lib/test/doctest_lineno.py @@ -49,5 +49,21 @@ def method_with_doctest(self): 'method_with_doctest' """ + @classmethod + def classmethod_with_doctest(cls): + """ + This has a doctest! + >>> MethodWrapper.classmethod_with_doctest.__name__ + 'classmethod_with_doctest' + """ + + @property + def property_with_doctest(self): + """ + This has a doctest! + >>> MethodWrapper.property_with_doctest.__name__ + 'property_with_doctest' + """ + # https://github.com/python/cpython/issues/99433 str_wrapper = object().__str__ diff --git a/Lib/test/test_doctest.py b/Lib/test/test_doctest.py index 36328f8086c7ad..46a51007f9644d 100644 --- a/Lib/test/test_doctest.py +++ b/Lib/test/test_doctest.py @@ -670,9 +670,11 @@ def basics(): r""" 30 test.doctest_lineno.ClassWithDoctest None test.doctest_lineno.ClassWithoutDocstring None test.doctest_lineno.MethodWrapper + 53 test.doctest_lineno.MethodWrapper.classmethod_with_doctest 39 test.doctest_lineno.MethodWrapper.method_with_docstring 45 test.doctest_lineno.MethodWrapper.method_with_doctest None test.doctest_lineno.MethodWrapper.method_without_docstring + 61 test.doctest_lineno.MethodWrapper.property_with_doctest 4 test.doctest_lineno.func_with_docstring 12 test.doctest_lineno.func_with_doctest None test.doctest_lineno.func_without_docstring diff --git a/Misc/NEWS.d/next/Library/2023-12-15-12-35-28.gh-issue-61648.G-4pz0.rst b/Misc/NEWS.d/next/Library/2023-12-15-12-35-28.gh-issue-61648.G-4pz0.rst new file mode 100644 index 00000000000000..c841e5c7f7683a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-15-12-35-28.gh-issue-61648.G-4pz0.rst @@ -0,0 +1 @@ +Detect line numbers of properties in doctests. From d1a2adfb0820ee730fa3e4bbc4bd88a67aa50666 Mon Sep 17 00:00:00 2001 From: AN Long Date: Fri, 15 Dec 2023 21:42:37 +0800 Subject: [PATCH 32/62] gh-112278: Add retry in WMI tests in case of slow initialization (GH-113154) --- Lib/test/test_wmi.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_wmi.py b/Lib/test/test_wmi.py index 3445702846d8a0..bf8c52e646dc18 100644 --- a/Lib/test/test_wmi.py +++ b/Lib/test/test_wmi.py @@ -1,17 +1,29 @@ # Test the internal _wmi module on Windows # This is used by the platform module, and potentially others +import time import unittest -from test.support import import_helper, requires_resource +from test.support import import_helper, requires_resource, LOOPBACK_TIMEOUT # Do this first so test will be skipped if module doesn't exist _wmi = import_helper.import_module('_wmi', required_on=['win']) +def wmi_exec_query(query): + # gh-112278: WMI maybe slow response when first call. + try: + return _wmi.exec_query(query) + except WindowsError as e: + if e.winerror != 258: + raise + time.sleep(LOOPBACK_TIMEOUT) + return _wmi.exec_query(query) + + class WmiTests(unittest.TestCase): def test_wmi_query_os_version(self): - r = _wmi.exec_query("SELECT Version FROM Win32_OperatingSystem").split("\0") + r = wmi_exec_query("SELECT Version FROM Win32_OperatingSystem").split("\0") self.assertEqual(1, len(r)) k, eq, v = r[0].partition("=") self.assertEqual("=", eq, r[0]) @@ -28,7 +40,7 @@ def test_wmi_query_repeated(self): def test_wmi_query_error(self): # Invalid queries fail with OSError try: - _wmi.exec_query("SELECT InvalidColumnName FROM InvalidTableName") + wmi_exec_query("SELECT InvalidColumnName FROM InvalidTableName") except OSError as ex: if ex.winerror & 0xFFFFFFFF == 0x80041010: # This is the expected error code. All others should fail the test @@ -42,7 +54,7 @@ def test_wmi_query_repeated_error(self): def test_wmi_query_not_select(self): # Queries other than SELECT are blocked to avoid potential exploits with self.assertRaises(ValueError): - _wmi.exec_query("not select, just in case someone tries something") + wmi_exec_query("not select, just in case someone tries something") @requires_resource('cpu') def test_wmi_query_overflow(self): @@ -50,11 +62,11 @@ def test_wmi_query_overflow(self): # Test multiple times to ensure consistency for _ in range(2): with self.assertRaises(OSError): - _wmi.exec_query("SELECT * FROM CIM_DataFile") + wmi_exec_query("SELECT * FROM CIM_DataFile") def test_wmi_query_multiple_rows(self): # Multiple instances should have an extra null separator - r = _wmi.exec_query("SELECT ProcessId FROM Win32_Process WHERE ProcessId < 1000") + r = wmi_exec_query("SELECT ProcessId FROM Win32_Process WHERE ProcessId < 1000") self.assertFalse(r.startswith("\0"), r) self.assertFalse(r.endswith("\0"), r) it = iter(r.split("\0")) @@ -69,6 +81,6 @@ def test_wmi_query_threads(self): from concurrent.futures import ThreadPoolExecutor query = "SELECT ProcessId FROM Win32_Process WHERE ProcessId < 1000" with ThreadPoolExecutor(4) as pool: - task = [pool.submit(_wmi.exec_query, query) for _ in range(32)] + task = [pool.submit(wmi_exec_query, query) for _ in range(32)] for t in task: self.assertRegex(t.result(), "ProcessId=") From 4026ad5b2c595b855a3605420cfa0e3d49e63db7 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 15 Dec 2023 15:57:49 +0100 Subject: [PATCH 33/62] gh-113009: Fix multiprocessing Process.terminate() on Windows (#113128) On Windows, Process.terminate() no longer sets the returncode attribute to always call WaitForSingleObject() in Process.wait(). Previously, sometimes the process was still running after TerminateProcess() even if GetExitCodeProcess() is not STILL_ACTIVE. --- Lib/multiprocessing/popen_spawn_win32.py | 54 ++++++++++--------- ...-12-14-19-00-29.gh-issue-113009.6LNdjz.rst | 5 ++ 2 files changed, 35 insertions(+), 24 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2023-12-14-19-00-29.gh-issue-113009.6LNdjz.rst diff --git a/Lib/multiprocessing/popen_spawn_win32.py b/Lib/multiprocessing/popen_spawn_win32.py index af044305709e56..49d4c7eea22411 100644 --- a/Lib/multiprocessing/popen_spawn_win32.py +++ b/Lib/multiprocessing/popen_spawn_win32.py @@ -101,18 +101,20 @@ def duplicate_for_child(self, handle): return reduction.duplicate(handle, self.sentinel) def wait(self, timeout=None): - if self.returncode is None: - if timeout is None: - msecs = _winapi.INFINITE - else: - msecs = max(0, int(timeout * 1000 + 0.5)) - - res = _winapi.WaitForSingleObject(int(self._handle), msecs) - if res == _winapi.WAIT_OBJECT_0: - code = _winapi.GetExitCodeProcess(self._handle) - if code == TERMINATE: - code = -signal.SIGTERM - self.returncode = code + if self.returncode is not None: + return self.returncode + + if timeout is None: + msecs = _winapi.INFINITE + else: + msecs = max(0, int(timeout * 1000 + 0.5)) + + res = _winapi.WaitForSingleObject(int(self._handle), msecs) + if res == _winapi.WAIT_OBJECT_0: + code = _winapi.GetExitCodeProcess(self._handle) + if code == TERMINATE: + code = -signal.SIGTERM + self.returncode = code return self.returncode @@ -120,18 +122,22 @@ def poll(self): return self.wait(timeout=0) def terminate(self): - if self.returncode is None: - try: - _winapi.TerminateProcess(int(self._handle), TERMINATE) - except PermissionError: - # ERROR_ACCESS_DENIED (winerror 5) is received when the - # process already died. - code = _winapi.GetExitCodeProcess(int(self._handle)) - if code == _winapi.STILL_ACTIVE: - raise - self.returncode = code - else: - self.returncode = -signal.SIGTERM + if self.returncode is not None: + return + + try: + _winapi.TerminateProcess(int(self._handle), TERMINATE) + except PermissionError: + # ERROR_ACCESS_DENIED (winerror 5) is received when the + # process already died. + code = _winapi.GetExitCodeProcess(int(self._handle)) + if code == _winapi.STILL_ACTIVE: + raise + + # gh-113009: Don't set self.returncode. Even if GetExitCodeProcess() + # returns an exit code different than STILL_ACTIVE, the process can + # still be running. Only set self.returncode once WaitForSingleObject() + # returns WAIT_OBJECT_0 in wait(). kill = terminate diff --git a/Misc/NEWS.d/next/Windows/2023-12-14-19-00-29.gh-issue-113009.6LNdjz.rst b/Misc/NEWS.d/next/Windows/2023-12-14-19-00-29.gh-issue-113009.6LNdjz.rst new file mode 100644 index 00000000000000..6fd7f7f9afdfa2 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2023-12-14-19-00-29.gh-issue-113009.6LNdjz.rst @@ -0,0 +1,5 @@ +:mod:`multiprocessing`: On Windows, fix a race condition in +``Process.terminate()``: no longer set the ``returncode`` attribute to +always call ``WaitForSingleObject()`` in ``Process.wait()``. Previously, +sometimes the process was still running after ``TerminateProcess()`` even if +``GetExitCodeProcess()`` is not ``STILL_ACTIVE``. Patch by Victor Stinner. From 4a153a1d3b18803a684cd1bcc2cdf3ede3dbae19 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 15 Dec 2023 16:10:40 +0100 Subject: [PATCH 34/62] [CVE-2023-27043] gh-102988: Reject malformed addresses in email.parseaddr() (#111116) Detect email address parsing errors and return empty tuple to indicate the parsing error (old API). Add an optional 'strict' parameter to getaddresses() and parseaddr() functions. Patch by Thomas Dwyer. Co-Authored-By: Thomas Dwyer --- Doc/library/email.utils.rst | 19 +- Doc/whatsnew/3.13.rst | 13 ++ Lib/email/utils.py | 151 +++++++++++++- Lib/test/test_email/test_email.py | 187 +++++++++++++++++- ...-10-20-15-28-08.gh-issue-102988.dStNO7.rst | 8 + 5 files changed, 357 insertions(+), 21 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst diff --git a/Doc/library/email.utils.rst b/Doc/library/email.utils.rst index 345b64001c1ace..d693a9bc3933b5 100644 --- a/Doc/library/email.utils.rst +++ b/Doc/library/email.utils.rst @@ -58,13 +58,18 @@ of the new API. begins with angle brackets, they are stripped off. -.. function:: parseaddr(address) +.. function:: parseaddr(address, *, strict=True) Parse address -- which should be the value of some address-containing field such as :mailheader:`To` or :mailheader:`Cc` -- into its constituent *realname* and *email address* parts. Returns a tuple of that information, unless the parse fails, in which case a 2-tuple of ``('', '')`` is returned. + If *strict* is true, use a strict parser which rejects malformed inputs. + + .. versionchanged:: 3.13 + Add *strict* optional parameter and reject malformed inputs by default. + .. function:: formataddr(pair, charset='utf-8') @@ -82,12 +87,15 @@ of the new API. Added the *charset* option. -.. function:: getaddresses(fieldvalues) +.. function:: getaddresses(fieldvalues, *, strict=True) This method returns a list of 2-tuples of the form returned by ``parseaddr()``. *fieldvalues* is a sequence of header field values as might be returned by - :meth:`Message.get_all `. Here's a simple - example that gets all the recipients of a message:: + :meth:`Message.get_all `. + + If *strict* is true, use a strict parser which rejects malformed inputs. + + Here's a simple example that gets all the recipients of a message:: from email.utils import getaddresses @@ -97,6 +105,9 @@ of the new API. resent_ccs = msg.get_all('resent-cc', []) all_recipients = getaddresses(tos + ccs + resent_tos + resent_ccs) + .. versionchanged:: 3.13 + Add *strict* optional parameter and reject malformed inputs by default. + .. function:: parsedate(date) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index e22257853d8333..4f9643967d20cf 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -199,6 +199,19 @@ doctest :attr:`doctest.TestResults.skipped` attributes. (Contributed by Victor Stinner in :gh:`108794`.) +email +----- + +* :func:`email.utils.getaddresses` and :func:`email.utils.parseaddr` now return + ``('', '')`` 2-tuples in more situations where invalid email addresses are + encountered instead of potentially inaccurate values. Add optional *strict* + 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. + (Contributed by Thomas Dwyer and Victor Stinner for :gh:`102988` to improve + the CVE-2023-27043 fix.) + glob ---- diff --git a/Lib/email/utils.py b/Lib/email/utils.py index 9175f2fdb6e69e..103cef61a83538 100644 --- a/Lib/email/utils.py +++ b/Lib/email/utils.py @@ -43,6 +43,7 @@ specialsre = re.compile(r'[][\\()<>@,:;".]') escapesre = re.compile(r'[\\"]') + def _has_surrogates(s): """Return True if s may contain surrogate-escaped binary data.""" # This check is based on the fact that unless there are surrogates, utf8 @@ -103,12 +104,127 @@ def formataddr(pair, charset='utf-8'): return address +def _iter_escaped_chars(addr): + pos = 0 + escape = False + for pos, ch in enumerate(addr): + if escape: + yield (pos, '\\' + ch) + escape = False + elif ch == '\\': + escape = True + else: + yield (pos, ch) + if escape: + yield (pos, '\\') + + +def _strip_quoted_realnames(addr): + """Strip real names between quotes.""" + if '"' not in addr: + # Fast path + return addr + + start = 0 + open_pos = None + result = [] + for pos, ch in _iter_escaped_chars(addr): + if ch == '"': + if open_pos is None: + open_pos = pos + else: + if start != open_pos: + result.append(addr[start:open_pos]) + start = pos + 1 + open_pos = None + + if start < len(addr): + result.append(addr[start:]) + + return ''.join(result) -def getaddresses(fieldvalues): - """Return a list of (REALNAME, EMAIL) for each fieldvalue.""" - all = COMMASPACE.join(str(v) for v in fieldvalues) - a = _AddressList(all) - return a.addresslist + +supports_strict_parsing = True + +def getaddresses(fieldvalues, *, strict=True): + """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue. + + When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in + its place. + + If strict is true, use a strict parser which rejects malformed inputs. + """ + + # If strict is true, if the resulting list of parsed addresses is greater + # than the number of fieldvalues in the input list, a parsing error has + # occurred and consequently a list containing a single empty 2-tuple [('', + # '')] is returned in its place. This is done to avoid invalid output. + # + # Malformed input: getaddresses(['alice@example.com ']) + # Invalid output: [('', 'alice@example.com'), ('', 'bob@example.com')] + # Safe output: [('', '')] + + if not strict: + all = COMMASPACE.join(str(v) for v in fieldvalues) + a = _AddressList(all) + return a.addresslist + + fieldvalues = [str(v) for v in fieldvalues] + fieldvalues = _pre_parse_validation(fieldvalues) + addr = COMMASPACE.join(fieldvalues) + a = _AddressList(addr) + result = _post_parse_validation(a.addresslist) + + # Treat output as invalid if the number of addresses is not equal to the + # expected number of addresses. + n = 0 + for v in fieldvalues: + # When a comma is used in the Real Name part it is not a deliminator. + # So strip those out before counting the commas. + v = _strip_quoted_realnames(v) + # Expected number of addresses: 1 + number of commas + n += 1 + v.count(',') + if len(result) != n: + return [('', '')] + + return result + + +def _check_parenthesis(addr): + # Ignore parenthesis in quoted real names. + addr = _strip_quoted_realnames(addr) + + opens = 0 + for pos, ch in _iter_escaped_chars(addr): + if ch == '(': + opens += 1 + elif ch == ')': + opens -= 1 + if opens < 0: + return False + return (opens == 0) + + +def _pre_parse_validation(email_header_fields): + accepted_values = [] + for v in email_header_fields: + if not _check_parenthesis(v): + v = "('', '')" + accepted_values.append(v) + + return accepted_values + + +def _post_parse_validation(parsed_email_header_tuples): + accepted_values = [] + # The parser would have parsed a correctly formatted domain-literal + # The existence of an [ after parsing indicates a parsing failure + for v in parsed_email_header_tuples: + if '[' in v[1]: + v = ('', '') + accepted_values.append(v) + + return accepted_values def _format_timetuple_and_zone(timetuple, zone): @@ -207,16 +323,33 @@ def parsedate_to_datetime(data): tzinfo=datetime.timezone(datetime.timedelta(seconds=tz))) -def parseaddr(addr): +def parseaddr(addr, *, strict=True): """ Parse addr into its constituent realname and email address parts. Return a tuple of realname and email address, unless the parse fails, in which case return a 2-tuple of ('', ''). + + If strict is True, use a strict parser which rejects malformed inputs. """ - addrs = _AddressList(addr).addresslist - if not addrs: - return '', '' + if not strict: + addrs = _AddressList(addr).addresslist + if not addrs: + return ('', '') + return addrs[0] + + if isinstance(addr, list): + addr = addr[0] + + if not isinstance(addr, str): + return ('', '') + + addr = _pre_parse_validation([addr])[0] + addrs = _post_parse_validation(_AddressList(addr).addresslist) + + if not addrs or len(addrs) > 1: + return ('', '') + return addrs[0] diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py index 512464f87162cd..39d4ace8d4a1d8 100644 --- a/Lib/test/test_email/test_email.py +++ b/Lib/test/test_email/test_email.py @@ -16,6 +16,7 @@ import email import email.policy +import email.utils from email.charset import Charset from email.generator import Generator, DecodedGenerator, BytesGenerator @@ -3337,15 +3338,137 @@ def test_getaddresses_comma_in_name(self): ], ) + def test_parsing_errors(self): + """Test for parsing errors from CVE-2023-27043 and CVE-2019-16056""" + alice = 'alice@example.org' + bob = 'bob@example.com' + empty = ('', '') + + # Test utils.getaddresses() and utils.parseaddr() on malformed email + # addresses: default behavior (strict=True) rejects malformed address, + # and strict=False which tolerates malformed address. + for invalid_separator, expected_non_strict in ( + ('(', [(f'<{bob}>', alice)]), + (')', [('', alice), empty, ('', bob)]), + ('<', [('', alice), empty, ('', bob), empty]), + ('>', [('', alice), empty, ('', bob)]), + ('[', [('', f'{alice}[<{bob}>]')]), + (']', [('', alice), empty, ('', bob)]), + ('@', [empty, empty, ('', bob)]), + (';', [('', alice), empty, ('', bob)]), + (':', [('', alice), ('', bob)]), + ('.', [('', alice + '.'), ('', bob)]), + ('"', [('', alice), ('', f'<{bob}>')]), + ): + address = f'{alice}{invalid_separator}<{bob}>' + with self.subTest(address=address): + self.assertEqual(utils.getaddresses([address]), + [empty]) + self.assertEqual(utils.getaddresses([address], strict=False), + expected_non_strict) + + self.assertEqual(utils.parseaddr([address]), + empty) + self.assertEqual(utils.parseaddr([address], strict=False), + ('', address)) + + # Comma (',') is treated differently depending on strict parameter. + # Comma without quotes. + address = f'{alice},<{bob}>' + self.assertEqual(utils.getaddresses([address]), + [('', alice), ('', bob)]) + self.assertEqual(utils.getaddresses([address], strict=False), + [('', alice), ('', bob)]) + self.assertEqual(utils.parseaddr([address]), + empty) + self.assertEqual(utils.parseaddr([address], strict=False), + ('', address)) + + # Real name between quotes containing comma. + address = '"Alice, alice@example.org" ' + expected_strict = ('Alice, alice@example.org', 'bob@example.com') + self.assertEqual(utils.getaddresses([address]), [expected_strict]) + self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict]) + self.assertEqual(utils.parseaddr([address]), expected_strict) + self.assertEqual(utils.parseaddr([address], strict=False), + ('', address)) + + # Valid parenthesis in comments. + address = 'alice@example.org (Alice)' + expected_strict = ('Alice', 'alice@example.org') + self.assertEqual(utils.getaddresses([address]), [expected_strict]) + self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict]) + self.assertEqual(utils.parseaddr([address]), expected_strict) + self.assertEqual(utils.parseaddr([address], strict=False), + ('', address)) + + # Invalid parenthesis in comments. + address = 'alice@example.org )Alice(' + self.assertEqual(utils.getaddresses([address]), [empty]) + self.assertEqual(utils.getaddresses([address], strict=False), + [('', 'alice@example.org'), ('', ''), ('', 'Alice')]) + self.assertEqual(utils.parseaddr([address]), empty) + self.assertEqual(utils.parseaddr([address], strict=False), + ('', address)) + + # Two addresses with quotes separated by comma. + address = '"Jane Doe" , "John Doe" ' + self.assertEqual(utils.getaddresses([address]), + [('Jane Doe', 'jane@example.net'), + ('John Doe', 'john@example.net')]) + self.assertEqual(utils.getaddresses([address], strict=False), + [('Jane Doe', 'jane@example.net'), + ('John Doe', 'john@example.net')]) + self.assertEqual(utils.parseaddr([address]), empty) + self.assertEqual(utils.parseaddr([address], strict=False), + ('', address)) + + # Test email.utils.supports_strict_parsing attribute + self.assertEqual(email.utils.supports_strict_parsing, True) + def test_getaddresses_nasty(self): - eq = self.assertEqual - eq(utils.getaddresses(['foo: ;']), [('', '')]) - eq(utils.getaddresses( - ['[]*-- =~$']), - [('', ''), ('', ''), ('', '*--')]) - eq(utils.getaddresses( - ['foo: ;', '"Jason R. Mastaler" ']), - [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]) + for addresses, expected in ( + (['"Sürname, Firstname" '], + [('Sürname, Firstname', 'to@example.com')]), + + (['foo: ;'], + [('', '')]), + + (['foo: ;', '"Jason R. Mastaler" '], + [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]), + + ([r'Pete(A nice \) chap) '], + [('Pete (A nice ) chap his account his host)', 'pete@silly.test')]), + + (['(Empty list)(start)Undisclosed recipients :(nobody(I know))'], + [('', '')]), + + (['Mary <@machine.tld:mary@example.net>, , jdoe@test . example'], + [('Mary', 'mary@example.net'), ('', ''), ('', 'jdoe@test.example')]), + + (['John Doe '], + [('John Doe (comment)', 'jdoe@machine.example')]), + + (['"Mary Smith: Personal Account" '], + [('Mary Smith: Personal Account', 'smith@home.example')]), + + (['Undisclosed recipients:;'], + [('', '')]), + + ([r', "Giant; \"Big\" Box" '], + [('', 'boss@nil.test'), ('Giant; "Big" Box', 'bob@example.net')]), + ): + with self.subTest(addresses=addresses): + self.assertEqual(utils.getaddresses(addresses), + expected) + self.assertEqual(utils.getaddresses(addresses, strict=False), + expected) + + addresses = ['[]*-- =~$'] + self.assertEqual(utils.getaddresses(addresses), + [('', '')]) + self.assertEqual(utils.getaddresses(addresses, strict=False), + [('', ''), ('', ''), ('', '*--')]) def test_getaddresses_embedded_comment(self): """Test proper handling of a nested comment""" @@ -3536,6 +3659,54 @@ def test_mime_classes_policy_argument(self): m = cls(*constructor, policy=email.policy.default) self.assertIs(m.policy, email.policy.default) + def test_iter_escaped_chars(self): + self.assertEqual(list(utils._iter_escaped_chars(r'a\\b\"c\\"d')), + [(0, 'a'), + (2, '\\\\'), + (3, 'b'), + (5, '\\"'), + (6, 'c'), + (8, '\\\\'), + (9, '"'), + (10, 'd')]) + self.assertEqual(list(utils._iter_escaped_chars('a\\')), + [(0, 'a'), (1, '\\')]) + + def test_strip_quoted_realnames(self): + def check(addr, expected): + self.assertEqual(utils._strip_quoted_realnames(addr), expected) + + check('"Jane Doe" , "John Doe" ', + ' , ') + check(r'"Jane \"Doe\"." ', + ' ') + + # special cases + check(r'before"name"after', 'beforeafter') + check(r'before"name"', 'before') + check(r'b"name"', 'b') # single char + check(r'"name"after', 'after') + check(r'"name"a', 'a') # single char + check(r'"name"', '') + + # no change + for addr in ( + 'Jane Doe , John Doe ', + 'lone " quote', + ): + self.assertEqual(utils._strip_quoted_realnames(addr), addr) + + + def test_check_parenthesis(self): + addr = 'alice@example.net' + self.assertTrue(utils._check_parenthesis(f'{addr} (Alice)')) + self.assertFalse(utils._check_parenthesis(f'{addr} )Alice(')) + self.assertFalse(utils._check_parenthesis(f'{addr} (Alice))')) + self.assertFalse(utils._check_parenthesis(f'{addr} ((Alice)')) + + # Ignore real name between quotes + self.assertTrue(utils._check_parenthesis(f'")Alice((" {addr}')) + # Test the iterator/generators class TestIterators(TestEmailBase): diff --git a/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst b/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst new file mode 100644 index 00000000000000..3d0e9e4078c934 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst @@ -0,0 +1,8 @@ +:func:`email.utils.getaddresses` and :func:`email.utils.parseaddr` now +return ``('', '')`` 2-tuples in more situations where invalid email +addresses are encountered instead of potentially inaccurate values. Add +optional *strict* 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. Patch by Thomas Dwyer and Victor +Stinner to improve the CVE-2023-27043 fix. From e365c943f24ab577cf7a4bf12bc0d2619ab9ae47 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 15 Dec 2023 17:36:25 +0200 Subject: [PATCH 35/62] gh-113172: Fix compiler warnings in Modules/_xxinterpqueuesmodule.c (GH-113173) Fix compiler waarnings in Modules/_xxinterpqueuesmodule.c --- Modules/_xxinterpqueuesmodule.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Modules/_xxinterpqueuesmodule.c b/Modules/_xxinterpqueuesmodule.c index 2cc3a2ac5dc85f..537ba9188055dd 100644 --- a/Modules/_xxinterpqueuesmodule.c +++ b/Modules/_xxinterpqueuesmodule.c @@ -234,11 +234,13 @@ static int add_exctype(PyObject *mod, PyObject **p_state_field, const char *qualname, const char *doc, PyObject *base) { +#ifndef NDEBUG const char *dot = strrchr(qualname, '.'); assert(dot != NULL); const char *name = dot+1; assert(*p_state_field == NULL); assert(!PyObject_HasAttrStringWithError(mod, name)); +#endif PyObject *exctype = PyErr_NewExceptionWithDoc(qualname, doc, base, NULL); if (exctype == NULL) { return -1; @@ -1505,7 +1507,7 @@ queuesmod_is_full(PyObject *self, PyObject *args, PyObject *kwds) } int64_t qid = qidarg.id; - int is_full; + int is_full = 0; int err = queue_is_full(&_globals.queues, qid, &is_full); if (handle_queue_error(err, self, qid)) { return NULL; From c2c4879b0a535b2a43005ba4983408082ef94bd2 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 15 Dec 2023 17:05:12 +0000 Subject: [PATCH 36/62] gh-101100: Fix Sphinx nitpicks in `library/numbers.rst` (#113162) --- Doc/library/numbers.rst | 19 ++++++++++--------- Doc/tools/.nitignore | 1 - 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Doc/library/numbers.rst b/Doc/library/numbers.rst index 2a05b56db051f9..17d1a275f04c9b 100644 --- a/Doc/library/numbers.rst +++ b/Doc/library/numbers.rst @@ -8,7 +8,7 @@ -------------- -The :mod:`numbers` module (:pep:`3141`) defines a hierarchy of numeric +The :mod:`!numbers` module (:pep:`3141`) defines a hierarchy of numeric :term:`abstract base classes ` which progressively define more operations. None of the types defined in this module are intended to be instantiated. @@ -45,7 +45,7 @@ The numeric tower .. class:: Real - To :class:`Complex`, :class:`Real` adds the operations that work on real + To :class:`Complex`, :class:`!Real` adds the operations that work on real numbers. In short, those are: a conversion to :class:`float`, :func:`math.trunc`, @@ -126,7 +126,8 @@ We want to implement the arithmetic operations so that mixed-mode operations either call an implementation whose author knew about the types of both arguments, or convert both to the nearest built in type and do the operation there. For subtypes of :class:`Integral`, this -means that :meth:`__add__` and :meth:`__radd__` should be defined as:: +means that :meth:`~object.__add__` and :meth:`~object.__radd__` should be +defined as:: class MyIntegral(Integral): @@ -160,15 +161,15 @@ refer to ``MyIntegral`` and ``OtherTypeIKnowAbout`` as of :class:`Complex` (``a : A <: Complex``), and ``b : B <: Complex``. I'll consider ``a + b``: -1. If ``A`` defines an :meth:`__add__` which accepts ``b``, all is +1. If ``A`` defines an :meth:`~object.__add__` which accepts ``b``, all is well. 2. If ``A`` falls back to the boilerplate code, and it were to - return a value from :meth:`__add__`, we'd miss the possibility - that ``B`` defines a more intelligent :meth:`__radd__`, so the + return a value from :meth:`~object.__add__`, we'd miss the possibility + that ``B`` defines a more intelligent :meth:`~object.__radd__`, so the boilerplate should return :const:`NotImplemented` from - :meth:`__add__`. (Or ``A`` may not implement :meth:`__add__` at + :meth:`!__add__`. (Or ``A`` may not implement :meth:`!__add__` at all.) -3. Then ``B``'s :meth:`__radd__` gets a chance. If it accepts +3. Then ``B``'s :meth:`~object.__radd__` gets a chance. If it accepts ``a``, all is well. 4. If it falls back to the boilerplate, there are no more possible methods to try, so this is where the default implementation @@ -180,7 +181,7 @@ Complex``. I'll consider ``a + b``: If ``A <: Complex`` and ``B <: Real`` without sharing any other knowledge, then the appropriate shared operation is the one involving the built -in :class:`complex`, and both :meth:`__radd__` s land there, so ``a+b +in :class:`complex`, and both :meth:`~object.__radd__` s land there, so ``a+b == b+a``. Because most of the operations on any given type will be very similar, diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index c91e698ff0753a..23d4d3e522b455 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -65,7 +65,6 @@ Doc/library/lzma.rst Doc/library/mmap.rst Doc/library/multiprocessing.rst Doc/library/multiprocessing.shared_memory.rst -Doc/library/numbers.rst Doc/library/optparse.rst Doc/library/os.rst Doc/library/pickle.rst From 1addde0c698f7f8eb716bcecf63d119e19e1ecda Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 15 Dec 2023 17:15:34 +0000 Subject: [PATCH 37/62] gh-101100: Fix various Sphinx warnings for dunder references in the `library/` directory (#113163) --- Doc/conf.py | 1 + Doc/library/datetime.rst | 3 ++- Doc/library/exceptions.rst | 4 ++-- Doc/library/test.rst | 3 ++- Doc/library/unittest.mock.rst | 23 +++++++++++++---------- Doc/library/unittest.rst | 4 ++-- Doc/library/wsgiref.rst | 11 +++++++---- Doc/library/xmlrpc.client.rst | 9 +++++---- 8 files changed, 34 insertions(+), 24 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index 4077eaf5a139b0..dc09b0b51ca84c 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -248,6 +248,7 @@ # Attributes/methods/etc. that definitely should be documented better, # but are deferred for now: ('py:attr', '__annotations__'), + ('py:meth', '__missing__'), ('py:attr', '__wrapped__'), ('py:meth', 'index'), # list.index, tuple.index, etc. ] diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 1bab1fb9bd9464..686b37754368c1 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -1985,7 +1985,8 @@ Examples of working with a :class:`.time` object:: American EST and EDT. Special requirement for pickling: A :class:`tzinfo` subclass must have an - :meth:`__init__` method that can be called with no arguments, otherwise it can be + :meth:`~object.__init__` method that can be called with no arguments, + otherwise it can be pickled but possibly not unpickled again. This is a technical requirement that may be relaxed in the future. diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index f7891f2732bdb1..f821776c286133 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -1009,9 +1009,9 @@ their subgroups based on the types of the contained exceptions. True - Note that :exc:`BaseExceptionGroup` defines :meth:`__new__`, so + Note that :exc:`BaseExceptionGroup` defines :meth:`~object.__new__`, so subclasses that need a different constructor signature need to - override that rather than :meth:`__init__`. For example, the following + override that rather than :meth:`~object.__init__`. For example, the following defines an exception group subclass which accepts an exit_code and and constructs the group's message from it. :: diff --git a/Doc/library/test.rst b/Doc/library/test.rst index 6cbdc39b3c024d..9173db07fd0071 100644 --- a/Doc/library/test.rst +++ b/Doc/library/test.rst @@ -1408,7 +1408,8 @@ The :mod:`test.support.os_helper` module provides support for os tests. .. class:: FakePath(path) - Simple :term:`path-like object`. It implements the :meth:`__fspath__` + Simple :term:`path-like object`. It implements the + :meth:`~os.PathLike.__fspath__` method which just returns the *path* argument. If *path* is an exception, it will be raised in :meth:`!__fspath__`. diff --git a/Doc/library/unittest.mock.rst b/Doc/library/unittest.mock.rst index d6ac4d09300d9f..f1cc482c5cfe2a 100644 --- a/Doc/library/unittest.mock.rst +++ b/Doc/library/unittest.mock.rst @@ -824,8 +824,9 @@ apply to method calls on the mock object. .. class:: PropertyMock(*args, **kwargs) - A mock intended to be used as a property, or other descriptor, on a class. - :class:`PropertyMock` provides :meth:`__get__` and :meth:`__set__` methods + A mock intended to be used as a :class:`property`, or other + :term:`descriptor`, on a class. :class:`PropertyMock` provides + :meth:`~object.__get__` and :meth:`~object.__set__` methods so you can specify a return value when it is fetched. Fetching a :class:`PropertyMock` instance from an object calls the mock, with @@ -1707,8 +1708,9 @@ Keywords can be used in the :func:`patch.dict` call to set values in the diction :func:`patch.dict` can be used with dictionary like objects that aren't actually dictionaries. At the very minimum they must support item getting, setting, deleting and either iteration or membership test. This corresponds to the -magic methods :meth:`~object.__getitem__`, :meth:`__setitem__`, :meth:`__delitem__` and either -:meth:`__iter__` or :meth:`__contains__`. +magic methods :meth:`~object.__getitem__`, :meth:`~object.__setitem__`, +:meth:`~object.__delitem__` and either :meth:`~container.__iter__` or +:meth:`~object.__contains__`. >>> class Container: ... def __init__(self): @@ -2171,7 +2173,7 @@ For example: >>> object() in mock False -The two equality methods, :meth:`__eq__` and :meth:`__ne__`, are special. +The two equality methods, :meth:`!__eq__` and :meth:`!__ne__`, are special. They do the default equality comparison on identity, using the :attr:`~Mock.side_effect` attribute, unless you change their return value to return something else:: @@ -2521,8 +2523,8 @@ mock_open *read_data* is now reset on each call to the *mock*. .. versionchanged:: 3.8 - Added :meth:`__iter__` to implementation so that iteration (such as in for - loops) correctly consumes *read_data*. + Added :meth:`~container.__iter__` to implementation so that iteration + (such as in for loops) correctly consumes *read_data*. Using :func:`open` as a context manager is a great way to ensure your file handles are closed properly and is becoming common:: @@ -2704,7 +2706,7 @@ able to use autospec. On the other hand it is much better to design your objects so that introspection is safe [#]_. A more serious problem is that it is common for instance attributes to be -created in the :meth:`__init__` method and not to exist on the class at all. +created in the :meth:`~object.__init__` method and not to exist on the class at all. *autospec* can't know about any dynamically created attributes and restricts the api to visible attributes. :: @@ -2745,8 +2747,9 @@ this particular scenario: AttributeError: Mock object has no attribute 'a' Probably the best way of solving the problem is to add class attributes as -default values for instance members initialised in :meth:`__init__`. Note that if -you are only setting default attributes in :meth:`__init__` then providing them via +default values for instance members initialised in :meth:`~object.__init__`. +Note that if +you are only setting default attributes in :meth:`!__init__` then providing them via class attributes (shared between instances of course) is faster too. e.g. .. code-block:: python diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst index c04e6c378cc3b1..9aad96bdf11bf8 100644 --- a/Doc/library/unittest.rst +++ b/Doc/library/unittest.rst @@ -1733,7 +1733,7 @@ Grouping tests .. method:: __iter__() Tests grouped by a :class:`TestSuite` are always accessed by iteration. - Subclasses can lazily provide tests by overriding :meth:`__iter__`. Note + Subclasses can lazily provide tests by overriding :meth:`!__iter__`. Note that this method may be called several times on a single suite (for example when counting tests or comparing for equality) so the tests returned by repeated iterations before :meth:`TestSuite.run` must be the @@ -1744,7 +1744,7 @@ Grouping tests .. versionchanged:: 3.2 In earlier versions the :class:`TestSuite` accessed tests directly rather - than through iteration, so overriding :meth:`__iter__` wasn't sufficient + than through iteration, so overriding :meth:`!__iter__` wasn't sufficient for providing tests. .. versionchanged:: 3.4 diff --git a/Doc/library/wsgiref.rst b/Doc/library/wsgiref.rst index be9e56b04c1fbf..c2b0ba7046967e 100644 --- a/Doc/library/wsgiref.rst +++ b/Doc/library/wsgiref.rst @@ -201,8 +201,9 @@ manipulation of WSGI response headers using a mapping-like interface. an empty list. :class:`Headers` objects support typical mapping operations including - :meth:`~object.__getitem__`, :meth:`get`, :meth:`__setitem__`, :meth:`setdefault`, - :meth:`__delitem__` and :meth:`__contains__`. For each of + :meth:`~object.__getitem__`, :meth:`~dict.get`, :meth:`~object.__setitem__`, + :meth:`~dict.setdefault`, + :meth:`~object.__delitem__` and :meth:`~object.__contains__`. For each of these methods, the key is the header name (treated case-insensitively), and the value is the first value associated with that header name. Setting a header deletes any existing values for that header, then adds a new value at the end of @@ -520,8 +521,10 @@ input, output, and error streams. want to subclass this instead of :class:`BaseCGIHandler`. This class is a subclass of :class:`BaseHandler`. It overrides the - :meth:`__init__`, :meth:`get_stdin`, :meth:`get_stderr`, :meth:`add_cgi_vars`, - :meth:`_write`, and :meth:`_flush` methods to support explicitly setting the + :meth:`!__init__`, :meth:`~BaseHandler.get_stdin`, + :meth:`~BaseHandler.get_stderr`, :meth:`~BaseHandler.add_cgi_vars`, + :meth:`~BaseHandler._write`, and :meth:`~BaseHandler._flush` methods to + support explicitly setting the environment and streams via the constructor. The supplied environment and streams are stored in the :attr:`stdin`, :attr:`stdout`, :attr:`stderr`, and :attr:`environ` attributes. diff --git a/Doc/library/xmlrpc.client.rst b/Doc/library/xmlrpc.client.rst index 146c4fd768233b..f7f23007fb0522 100644 --- a/Doc/library/xmlrpc.client.rst +++ b/Doc/library/xmlrpc.client.rst @@ -269,8 +269,9 @@ DateTime Objects Write the XML-RPC encoding of this :class:`DateTime` item to the *out* stream object. - It also supports certain of Python's built-in operators through rich comparison - and :meth:`__repr__` methods. + It also supports certain of Python's built-in operators through + :meth:`rich comparison ` and :meth:`~object.__repr__` + methods. A working example follows. The server code:: @@ -334,8 +335,8 @@ Binary Objects which was the de facto standard base64 specification when the XML-RPC spec was written. - It also supports certain of Python's built-in operators through :meth:`__eq__` - and :meth:`__ne__` methods. + It also supports certain of Python's built-in operators through + :meth:`~object.__eq__` and :meth:`~object.__ne__` methods. Example usage of the binary objects. We're going to transfer an image over XMLRPC:: From d07483292b115a5a0e9b9b09f3ec1000ce879986 Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Fri, 15 Dec 2023 09:27:57 -0800 Subject: [PATCH 38/62] GH-112383: Fix test_loop_quicken when an executor is installed (GH-113153) --- Lib/test/test_dis.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 0c7fd60f640854..e4c7e9ba4649f7 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -1210,8 +1210,13 @@ def test_loop_quicken(self): got = self.get_disassembly(loop_test, adaptive=True) expected = dis_loop_test_quickened_code if _testinternalcapi.get_optimizer(): - # We *may* see ENTER_EXECUTOR in the disassembly - got = got.replace("ENTER_EXECUTOR", "JUMP_BACKWARD ") + # We *may* see ENTER_EXECUTOR in the disassembly. This is a + # temporary hack to keep the test working until dis is able to + # handle the instruction correctly (GH-112383): + got = got.replace( + "ENTER_EXECUTOR 16", + "JUMP_BACKWARD 16 (to L1)", + ) self.do_disassembly_compare(got, expected) @cpython_only From 00d2b6d1fca91e1a83f7f99a370685b095ed4928 Mon Sep 17 00:00:00 2001 From: Akshat Khandelwal <35228810+akshatgokul@users.noreply.github.com> Date: Sat, 16 Dec 2023 02:23:16 +0530 Subject: [PATCH 39/62] gh-110746: Improve markup in ``tkinter.ttk.rst`` (#111236) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * gh-110746: Improve markup in tkinter.ttk.rst * gh-110746: Improve markup in tkinter.ttk.rst * 📜🤖 Added by blurb_it. --------- Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: Ezio Melotti --- Doc/library/tkinter.ttk.rst | 31 ++++++++++--------- ...-10-23-23-43-43.gh-issue-110746.yg77IE.rst | 1 + 2 files changed, 17 insertions(+), 15 deletions(-) create mode 100644 Misc/NEWS.d/next/Documentation/2023-10-23-23-43-43.gh-issue-110746.yg77IE.rst diff --git a/Doc/library/tkinter.ttk.rst b/Doc/library/tkinter.ttk.rst index 6e01ec7b291255..1609dc2ce9218e 100644 --- a/Doc/library/tkinter.ttk.rst +++ b/Doc/library/tkinter.ttk.rst @@ -986,19 +986,19 @@ ttk.Treeview The valid options/values are: - id + *id* Returns the column name. This is a read-only option. - anchor: One of the standard Tk anchor values. + *anchor*: One of the standard Tk anchor values. Specifies how the text in this column should be aligned with respect to the cell. - minwidth: width + *minwidth*: width The minimum width of the column in pixels. The treeview widget will not make the column any smaller than specified by this option when the widget is resized or the user drags a column. - stretch: ``True``/``False`` + *stretch*: ``True``/``False`` Specifies whether the column's width should be adjusted when the widget is resized. - width: width + *width*: width The width of the column in pixels. To configure the tree column, call this with column = "#0" @@ -1041,14 +1041,14 @@ ttk.Treeview The valid options/values are: - text: text + *text*: text The text to display in the column heading. - image: imageName + *image*: imageName Specifies an image to display to the right of the column heading. - anchor: anchor + *anchor*: anchor Specifies how the heading text should be aligned. One of the standard Tk anchor values. - command: callback + *command*: callback A callback to be invoked when the heading label is pressed. To configure the tree column heading, call this with column = "#0". @@ -1573,23 +1573,24 @@ Layouts A layout can be just ``None``, if it takes no options, or a dict of options specifying how to arrange the element. The layout mechanism uses a simplified version of the pack geometry manager: given an -initial cavity, each element is allocated a parcel. Valid -options/values are: +initial cavity, each element is allocated a parcel. -side: whichside +The valid options/values are: + +*side*: whichside Specifies which side of the cavity to place the element; one of top, right, bottom or left. If omitted, the element occupies the entire cavity. -sticky: nswe +*sticky*: nswe Specifies where the element is placed inside its allocated parcel. -unit: 0 or 1 +*unit*: 0 or 1 If set to 1, causes the element and all of its descendants to be treated as a single element for the purposes of :meth:`Widget.identify` et al. It's used for things like scrollbar thumbs with grips. -children: [sublayout... ] +*children*: [sublayout... ] Specifies a list of elements to place inside the element. Each element is a tuple (or other sequence type) where the first item is the layout name, and the other is a `Layout`_. diff --git a/Misc/NEWS.d/next/Documentation/2023-10-23-23-43-43.gh-issue-110746.yg77IE.rst b/Misc/NEWS.d/next/Documentation/2023-10-23-23-43-43.gh-issue-110746.yg77IE.rst new file mode 100644 index 00000000000000..215db7beb75dcf --- /dev/null +++ b/Misc/NEWS.d/next/Documentation/2023-10-23-23-43-43.gh-issue-110746.yg77IE.rst @@ -0,0 +1 @@ +Improved markup for valid options/values for methods ttk.treeview.column and ttk.treeview.heading, and for Layouts. From 40574da0196c7e51e1886f6ff9ed26447622ae58 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Fri, 15 Dec 2023 18:03:44 -0600 Subject: [PATCH 40/62] Add reshape() recipe to demonstrate a use case for batched() and chained.from_iterable() (gh-113198) --- Doc/library/itertools.rst | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 03127afe1b4460..6bcda307f256f2 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -1036,10 +1036,15 @@ The following recipes have a more mathematical flavor: # sum_of_squares([10, 20, 30]) -> 1400 return math.sumprod(*tee(it)) - def transpose(it): - "Swap the rows and columns of the input." + def reshape(matrix, cols): + "Reshape a 2-D matrix to have a given number of columns." + # reshape([(0, 1), (2, 3), (4, 5)], 3) --> (0, 1, 2), (3, 4, 5) + return batched(chain.from_iterable(matrix), cols) + + def transpose(matrix): + "Swap the rows and columns of a 2-D matrix." # transpose([(1, 2, 3), (11, 22, 33)]) --> (1, 11) (2, 22) (3, 33) - return zip(*it, strict=True) + return zip(*matrix, strict=True) def matmul(m1, m2): "Multiply two matrices." @@ -1254,6 +1259,22 @@ The following recipes have a more mathematical flavor: >>> sum_of_squares([10, 20, 30]) 1400 + >>> list(reshape([(0, 1), (2, 3), (4, 5)], 3)) + [(0, 1, 2), (3, 4, 5)] + >>> M = [(0, 1, 2, 3), (4, 5, 6, 7), (8, 9, 10, 11)] + >>> list(reshape(M, 1)) + [(0,), (1,), (2,), (3,), (4,), (5,), (6,), (7,), (8,), (9,), (10,), (11,)] + >>> list(reshape(M, 2)) + [(0, 1), (2, 3), (4, 5), (6, 7), (8, 9), (10, 11)] + >>> list(reshape(M, 3)) + [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11)] + >>> list(reshape(M, 4)) + [(0, 1, 2, 3), (4, 5, 6, 7), (8, 9, 10, 11)] + >>> list(reshape(M, 6)) + [(0, 1, 2, 3, 4, 5), (6, 7, 8, 9, 10, 11)] + >>> list(reshape(M, 12)) + [(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)] + >>> list(transpose([(1, 2, 3), (11, 22, 33)])) [(1, 11), (2, 22), (3, 33)] From 5ae75e1be24bd6b031a68040cfddb71732461f67 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Fri, 15 Dec 2023 20:56:55 -0500 Subject: [PATCH 41/62] gh-111964: Add _PyRWMutex a "readers-writer" lock (gh-112859) This adds `_PyRWMutex`, a "readers-writer" lock, which wil be used to serialize global stop-the-world pauses with per-interpreter pauses. --- Include/internal/pycore_lock.h | 39 ++++++++++ Modules/_testinternalcapi/test_lock.c | 99 ++++++++++++++++++++++++ Python/lock.c | 106 ++++++++++++++++++++++++++ 3 files changed, 244 insertions(+) diff --git a/Include/internal/pycore_lock.h b/Include/internal/pycore_lock.h index 03ad1c9fd584b5..18a8896d97a548 100644 --- a/Include/internal/pycore_lock.h +++ b/Include/internal/pycore_lock.h @@ -213,6 +213,45 @@ _PyOnceFlag_CallOnce(_PyOnceFlag *flag, _Py_once_fn_t *fn, void *arg) return _PyOnceFlag_CallOnceSlow(flag, fn, arg); } +// A readers-writer (RW) lock. The lock supports multiple concurrent readers or +// a single writer. The lock is write-preferring: if a writer is waiting while +// the lock is read-locked then, new readers will be blocked. This avoids +// starvation of writers. +// +// In C++, the equivalent synchronization primitive is std::shared_mutex +// with shared ("read") and exclusive ("write") locking. +// +// The two least significant bits are used to indicate if the lock is +// write-locked and if there are parked threads (either readers or writers) +// waiting to acquire the lock. The remaining bits are used to indicate the +// number of readers holding the lock. +// +// 0b000..00000: unlocked +// 0bnnn..nnn00: nnn..nnn readers holding the lock +// 0bnnn..nnn10: nnn..nnn readers holding the lock and a writer is waiting +// 0b00000..010: unlocked with awoken writer about to acquire lock +// 0b00000..001: write-locked +// 0b00000..011: write-locked and readers or other writers are waiting +// +// Note that reader_count must be zero if the lock is held by a writer, and +// vice versa. The lock can only be held by readers or a writer, but not both. +// +// The design is optimized for simplicity of the implementation. The lock is +// not fair: if fairness is desired, use an additional PyMutex to serialize +// writers. The lock is also not reentrant. +typedef struct { + uintptr_t bits; +} _PyRWMutex; + +// Read lock (i.e., shared lock) +PyAPI_FUNC(void) _PyRWMutex_RLock(_PyRWMutex *rwmutex); +PyAPI_FUNC(void) _PyRWMutex_RUnlock(_PyRWMutex *rwmutex); + +// Write lock (i.e., exclusive lock) +PyAPI_FUNC(void) _PyRWMutex_Lock(_PyRWMutex *rwmutex); +PyAPI_FUNC(void) _PyRWMutex_Unlock(_PyRWMutex *rwmutex); + + #ifdef __cplusplus } #endif diff --git a/Modules/_testinternalcapi/test_lock.c b/Modules/_testinternalcapi/test_lock.c index 418f71c1441995..83081f73a72f64 100644 --- a/Modules/_testinternalcapi/test_lock.c +++ b/Modules/_testinternalcapi/test_lock.c @@ -372,6 +372,104 @@ test_lock_once(PyObject *self, PyObject *obj) Py_RETURN_NONE; } +struct test_rwlock_data { + Py_ssize_t nthreads; + _PyRWMutex rw; + PyEvent step1; + PyEvent step2; + PyEvent step3; + PyEvent done; +}; + +static void +rdlock_thread(void *arg) +{ + struct test_rwlock_data *test_data = arg; + + // Acquire the lock in read mode + _PyRWMutex_RLock(&test_data->rw); + PyEvent_Wait(&test_data->step1); + _PyRWMutex_RUnlock(&test_data->rw); + + _PyRWMutex_RLock(&test_data->rw); + PyEvent_Wait(&test_data->step3); + _PyRWMutex_RUnlock(&test_data->rw); + + if (_Py_atomic_add_ssize(&test_data->nthreads, -1) == 1) { + _PyEvent_Notify(&test_data->done); + } +} +static void +wrlock_thread(void *arg) +{ + struct test_rwlock_data *test_data = arg; + + // First acquire the lock in write mode + _PyRWMutex_Lock(&test_data->rw); + PyEvent_Wait(&test_data->step2); + _PyRWMutex_Unlock(&test_data->rw); + + if (_Py_atomic_add_ssize(&test_data->nthreads, -1) == 1) { + _PyEvent_Notify(&test_data->done); + } +} + +static void +wait_until(uintptr_t *ptr, uintptr_t value) +{ + // wait up to two seconds for *ptr == value + int iters = 0; + uintptr_t bits; + do { + pysleep(10); + bits = _Py_atomic_load_uintptr(ptr); + iters++; + } while (bits != value && iters < 200); +} + +static PyObject * +test_lock_rwlock(PyObject *self, PyObject *obj) +{ + struct test_rwlock_data test_data = {.nthreads = 3}; + + _PyRWMutex_Lock(&test_data.rw); + assert(test_data.rw.bits == 1); + + _PyRWMutex_Unlock(&test_data.rw); + assert(test_data.rw.bits == 0); + + // Start two readers + PyThread_start_new_thread(rdlock_thread, &test_data); + PyThread_start_new_thread(rdlock_thread, &test_data); + + // wait up to two seconds for the threads to attempt to read-lock "rw" + wait_until(&test_data.rw.bits, 8); + assert(test_data.rw.bits == 8); + + // start writer (while readers hold lock) + PyThread_start_new_thread(wrlock_thread, &test_data); + wait_until(&test_data.rw.bits, 10); + assert(test_data.rw.bits == 10); + + // readers release lock, writer should acquire it + _PyEvent_Notify(&test_data.step1); + wait_until(&test_data.rw.bits, 3); + assert(test_data.rw.bits == 3); + + // writer releases lock, readers acquire it + _PyEvent_Notify(&test_data.step2); + wait_until(&test_data.rw.bits, 8); + assert(test_data.rw.bits == 8); + + // readers release lock again + _PyEvent_Notify(&test_data.step3); + wait_until(&test_data.rw.bits, 0); + assert(test_data.rw.bits == 0); + + PyEvent_Wait(&test_data.done); + Py_RETURN_NONE; +} + static PyMethodDef test_methods[] = { {"test_lock_basic", test_lock_basic, METH_NOARGS}, {"test_lock_two_threads", test_lock_two_threads, METH_NOARGS}, @@ -380,6 +478,7 @@ static PyMethodDef test_methods[] = { _TESTINTERNALCAPI_BENCHMARK_LOCKS_METHODDEF {"test_lock_benchmark", test_lock_benchmark, METH_NOARGS}, {"test_lock_once", test_lock_once, METH_NOARGS}, + {"test_lock_rwlock", test_lock_rwlock, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/Python/lock.c b/Python/lock.c index e9279f0b92a5e7..f0ff1176941da8 100644 --- a/Python/lock.c +++ b/Python/lock.c @@ -353,3 +353,109 @@ _PyOnceFlag_CallOnceSlow(_PyOnceFlag *flag, _Py_once_fn_t *fn, void *arg) v = _Py_atomic_load_uint8(&flag->v); } } + +#define _Py_WRITE_LOCKED 1 +#define _PyRWMutex_READER_SHIFT 2 +#define _Py_RWMUTEX_MAX_READERS (UINTPTR_MAX >> _PyRWMutex_READER_SHIFT) + +static uintptr_t +rwmutex_set_parked_and_wait(_PyRWMutex *rwmutex, uintptr_t bits) +{ + // Set _Py_HAS_PARKED and wait until we are woken up. + if ((bits & _Py_HAS_PARKED) == 0) { + uintptr_t newval = bits | _Py_HAS_PARKED; + if (!_Py_atomic_compare_exchange_uintptr(&rwmutex->bits, + &bits, newval)) { + return bits; + } + bits = newval; + } + + _PyParkingLot_Park(&rwmutex->bits, &bits, sizeof(bits), -1, NULL, 1); + return _Py_atomic_load_uintptr_relaxed(&rwmutex->bits); +} + +// The number of readers holding the lock +static uintptr_t +rwmutex_reader_count(uintptr_t bits) +{ + return bits >> _PyRWMutex_READER_SHIFT; +} + +void +_PyRWMutex_RLock(_PyRWMutex *rwmutex) +{ + uintptr_t bits = _Py_atomic_load_uintptr_relaxed(&rwmutex->bits); + for (;;) { + if ((bits & _Py_WRITE_LOCKED)) { + // A writer already holds the lock. + bits = rwmutex_set_parked_and_wait(rwmutex, bits); + continue; + } + else if ((bits & _Py_HAS_PARKED)) { + // Reader(s) hold the lock (or just gave up the lock), but there is + // at least one waiting writer. We can't grab the lock because we + // don't want to starve the writer. Instead, we park ourselves and + // wait for the writer to eventually wake us up. + bits = rwmutex_set_parked_and_wait(rwmutex, bits); + continue; + } + else { + // The lock is unlocked or read-locked. Try to grab it. + assert(rwmutex_reader_count(bits) < _Py_RWMUTEX_MAX_READERS); + uintptr_t newval = bits + (1 << _PyRWMutex_READER_SHIFT); + if (!_Py_atomic_compare_exchange_uintptr(&rwmutex->bits, + &bits, newval)) { + continue; + } + return; + } + } +} + +void +_PyRWMutex_RUnlock(_PyRWMutex *rwmutex) +{ + uintptr_t bits = _Py_atomic_add_uintptr(&rwmutex->bits, -(1 << _PyRWMutex_READER_SHIFT)); + assert(rwmutex_reader_count(bits) > 0 && "lock was not read-locked"); + bits -= (1 << _PyRWMutex_READER_SHIFT); + + if (rwmutex_reader_count(bits) == 0 && (bits & _Py_HAS_PARKED)) { + _PyParkingLot_UnparkAll(&rwmutex->bits); + return; + } +} + +void +_PyRWMutex_Lock(_PyRWMutex *rwmutex) +{ + uintptr_t bits = _Py_atomic_load_uintptr_relaxed(&rwmutex->bits); + for (;;) { + // If there are no active readers and it's not already write-locked, + // then we can grab the lock. + if ((bits & ~_Py_HAS_PARKED) == 0) { + if (!_Py_atomic_compare_exchange_uintptr(&rwmutex->bits, + &bits, + bits | _Py_WRITE_LOCKED)) { + continue; + } + return; + } + + // Otherwise, we have to wait. + bits = rwmutex_set_parked_and_wait(rwmutex, bits); + } +} + +void +_PyRWMutex_Unlock(_PyRWMutex *rwmutex) +{ + uintptr_t old_bits = _Py_atomic_exchange_uintptr(&rwmutex->bits, 0); + + assert((old_bits & _Py_WRITE_LOCKED) && "lock was not write-locked"); + assert(rwmutex_reader_count(old_bits) == 0 && "lock was read-locked"); + + if ((old_bits & _Py_HAS_PARKED) != 0) { + _PyParkingLot_UnparkAll(&rwmutex->bits); + } +} From 84df3172efe8767ddf5c28bdb6696b3f216bcaa6 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Sat, 16 Dec 2023 03:12:39 -0500 Subject: [PATCH 42/62] gh-113046: Revise csv.reader doc (#113207) Clarify nature of csvfile. --- Doc/library/csv.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Doc/library/csv.rst b/Doc/library/csv.rst index 4d52254e6d6db5..7a5589e68b3052 100644 --- a/Doc/library/csv.rst +++ b/Doc/library/csv.rst @@ -55,10 +55,11 @@ The :mod:`csv` module defines the following functions: .. function:: reader(csvfile, dialect='excel', **fmtparams) - Return a reader object which will iterate over lines in the given *csvfile*. - *csvfile* can be any object which supports the :term:`iterator` protocol and returns a - string each time its :meth:`!__next__` method is called --- :term:`file objects - ` and list objects are both suitable. If *csvfile* is a file object, + Return a :ref:`reader object ` that will process + lines from the given *csvfile*. A csvfile must be an iterable of + strings, each in the reader's defined csv format. + A csvfile is most commonly a file-like object or list. + If *csvfile* is a file object, it should be opened with ``newline=''``. [1]_ An optional *dialect* parameter can be given which is used to define a set of parameters specific to a particular CSV dialect. It may be an instance of a subclass of @@ -449,6 +450,8 @@ Dialects support the following attributes: When ``True``, raise exception :exc:`Error` on bad CSV input. The default is ``False``. +.. _reader-objects: + Reader Objects -------------- From fe479fb8a979894224a4d279d1e46a5cdb108fa4 Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Sat, 16 Dec 2023 10:58:31 +0000 Subject: [PATCH 43/62] gh-67790: Support basic formatting for Fraction (#111320) PR #100161 added fancy float-style formatting for the Fraction type, but left us in a state where basic formatting for fractions (alignment, fill, minimum width, thousands separators) still wasn't supported. This PR adds that support. --------- Co-authored-by: Serhiy Storchaka --- Doc/library/fractions.rst | 37 ++++++-- Doc/whatsnew/3.13.rst | 8 ++ Lib/fractions.py | 87 +++++++++++++++---- Lib/test/test_fractions.py | 52 +++++++++-- ...3-10-25-13-07-53.gh-issue-67790.jMn9Ad.rst | 2 + 5 files changed, 155 insertions(+), 31 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-10-25-13-07-53.gh-issue-67790.jMn9Ad.rst diff --git a/Doc/library/fractions.rst b/Doc/library/fractions.rst index 509c63686f5a7f..887c3844d20faa 100644 --- a/Doc/library/fractions.rst +++ b/Doc/library/fractions.rst @@ -106,6 +106,10 @@ another rational number, or from a string. presentation types ``"e"``, ``"E"``, ``"f"``, ``"F"``, ``"g"``, ``"G"`` and ``"%""``. + .. versionchanged:: 3.13 + Formatting of :class:`Fraction` instances without a presentation type + now supports fill, alignment, sign handling, minimum width and grouping. + .. attribute:: numerator Numerator of the Fraction in lowest term. @@ -201,17 +205,36 @@ another rational number, or from a string. .. method:: __format__(format_spec, /) - Provides support for float-style formatting of :class:`Fraction` - instances via the :meth:`str.format` method, the :func:`format` built-in - function, or :ref:`Formatted string literals `. The - presentation types ``"e"``, ``"E"``, ``"f"``, ``"F"``, ``"g"``, ``"G"`` - and ``"%"`` are supported. For these presentation types, formatting for a - :class:`Fraction` object ``x`` follows the rules outlined for - the :class:`float` type in the :ref:`formatspec` section. + Provides support for formatting of :class:`Fraction` instances via the + :meth:`str.format` method, the :func:`format` built-in function, or + :ref:`Formatted string literals `. + + If the ``format_spec`` format specification string does not end with one + of the presentation types ``'e'``, ``'E'``, ``'f'``, ``'F'``, ``'g'``, + ``'G'`` or ``'%'`` then formatting follows the general rules for fill, + alignment, sign handling, minimum width, and grouping as described in the + :ref:`format specification mini-language `. The "alternate + form" flag ``'#'`` is supported: if present, it forces the output string + to always include an explicit denominator, even when the value being + formatted is an exact integer. The zero-fill flag ``'0'`` is not + supported. + + If the ``format_spec`` format specification string ends with one of + the presentation types ``'e'``, ``'E'``, ``'f'``, ``'F'``, ``'g'``, + ``'G'`` or ``'%'`` then formatting follows the rules outlined for the + :class:`float` type in the :ref:`formatspec` section. Here are some examples:: >>> from fractions import Fraction + >>> format(Fraction(103993, 33102), '_') + '103_993/33_102' + >>> format(Fraction(1, 7), '.^+10') + '...+1/7...' + >>> format(Fraction(3, 1), '') + '3' + >>> format(Fraction(3, 1), '#') + '3/1' >>> format(Fraction(1, 7), '.40g') '0.1428571428571428571428571428571428571429' >>> format(Fraction('1234567.855'), '_.2f') diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 4f9643967d20cf..ce4f66b97a0be4 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -212,6 +212,14 @@ email (Contributed by Thomas Dwyer and Victor Stinner for :gh:`102988` to improve the CVE-2023-27043 fix.) +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`) + glob ---- diff --git a/Lib/fractions.py b/Lib/fractions.py index c95db0730e5b6d..6532d5d54e3c35 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -139,6 +139,23 @@ def _round_to_figures(n, d, figures): return sign, significand, exponent +# Pattern for matching non-float-style format specifications. +_GENERAL_FORMAT_SPECIFICATION_MATCHER = re.compile(r""" + (?: + (?P.)? + (?P[<>=^]) + )? + (?P[-+ ]?) + # Alt flag forces a slash and denominator in the output, even for + # integer-valued Fraction objects. + (?P\#)? + # We don't implement the zeropad flag since there's no single obvious way + # to interpret it. + (?P0|[1-9][0-9]*)? + (?P[,_])? +""", re.DOTALL | re.VERBOSE).fullmatch + + # Pattern for matching float-style format specifications; # supports 'e', 'E', 'f', 'F', 'g', 'G' and '%' presentation types. _FLOAT_FORMAT_SPECIFICATION_MATCHER = re.compile(r""" @@ -414,27 +431,42 @@ def __str__(self): else: return '%s/%s' % (self._numerator, self._denominator) - def __format__(self, format_spec, /): - """Format this fraction according to the given format specification.""" - - # Backwards compatiblility with existing formatting. - if not format_spec: - return str(self) + def _format_general(self, match): + """Helper method for __format__. + Handles fill, alignment, signs, and thousands separators in the + case of no presentation type. + """ # Validate and parse the format specifier. - match = _FLOAT_FORMAT_SPECIFICATION_MATCHER(format_spec) - if match is None: - raise ValueError( - f"Invalid format specifier {format_spec!r} " - f"for object of type {type(self).__name__!r}" - ) - elif match["align"] is not None and match["zeropad"] is not None: - # Avoid the temptation to guess. - raise ValueError( - f"Invalid format specifier {format_spec!r} " - f"for object of type {type(self).__name__!r}; " - "can't use explicit alignment when zero-padding" - ) + fill = match["fill"] or " " + align = match["align"] or ">" + pos_sign = "" if match["sign"] == "-" else match["sign"] + alternate_form = bool(match["alt"]) + minimumwidth = int(match["minimumwidth"] or "0") + thousands_sep = match["thousands_sep"] or '' + + # Determine the body and sign representation. + n, d = self._numerator, self._denominator + if d > 1 or alternate_form: + body = f"{abs(n):{thousands_sep}}/{d:{thousands_sep}}" + else: + body = f"{abs(n):{thousands_sep}}" + sign = '-' if n < 0 else pos_sign + + # Pad with fill character if necessary and return. + padding = fill * (minimumwidth - len(sign) - len(body)) + if align == ">": + return padding + sign + body + elif align == "<": + return sign + body + padding + elif align == "^": + half = len(padding) // 2 + return padding[:half] + sign + body + padding[half:] + else: # align == "=" + return sign + padding + body + + def _format_float_style(self, match): + """Helper method for __format__; handles float presentation types.""" fill = match["fill"] or " " align = match["align"] or ">" pos_sign = "" if match["sign"] == "-" else match["sign"] @@ -530,6 +562,23 @@ def __format__(self, format_spec, /): else: # align == "=" return sign + padding + body + def __format__(self, format_spec, /): + """Format this fraction according to the given format specification.""" + + if match := _GENERAL_FORMAT_SPECIFICATION_MATCHER(format_spec): + return self._format_general(match) + + if match := _FLOAT_FORMAT_SPECIFICATION_MATCHER(format_spec): + # Refuse the temptation to guess if both alignment _and_ + # zero padding are specified. + if match["align"] is None or match["zeropad"] is None: + return self._format_float_style(match) + + raise ValueError( + f"Invalid format specifier {format_spec!r} " + f"for object of type {type(self).__name__!r}" + ) + def _operator_fallbacks(monomorphic_operator, fallback_operator): """Generates forward and reverse operators given a purely-rational operator and a function from the operator module. diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py index 499e3b6e656faa..84779526ce0eb0 100644 --- a/Lib/test/test_fractions.py +++ b/Lib/test/test_fractions.py @@ -849,12 +849,50 @@ def denominator(self): self.assertEqual(type(f.denominator), myint) def test_format_no_presentation_type(self): - # Triples (fraction, specification, expected_result) + # Triples (fraction, specification, expected_result). testcases = [ - (F(1, 3), '', '1/3'), - (F(-1, 3), '', '-1/3'), - (F(3), '', '3'), - (F(-3), '', '-3'), + # Explicit sign handling + (F(2, 3), '+', '+2/3'), + (F(-2, 3), '+', '-2/3'), + (F(3), '+', '+3'), + (F(-3), '+', '-3'), + (F(2, 3), ' ', ' 2/3'), + (F(-2, 3), ' ', '-2/3'), + (F(3), ' ', ' 3'), + (F(-3), ' ', '-3'), + (F(2, 3), '-', '2/3'), + (F(-2, 3), '-', '-2/3'), + (F(3), '-', '3'), + (F(-3), '-', '-3'), + # Padding + (F(0), '5', ' 0'), + (F(2, 3), '5', ' 2/3'), + (F(-2, 3), '5', ' -2/3'), + (F(2, 3), '0', '2/3'), + (F(2, 3), '1', '2/3'), + (F(2, 3), '2', '2/3'), + # Alignment + (F(2, 3), '<5', '2/3 '), + (F(2, 3), '>5', ' 2/3'), + (F(2, 3), '^5', ' 2/3 '), + (F(2, 3), '=5', ' 2/3'), + (F(-2, 3), '<5', '-2/3 '), + (F(-2, 3), '>5', ' -2/3'), + (F(-2, 3), '^5', '-2/3 '), + (F(-2, 3), '=5', '- 2/3'), + # Fill + (F(2, 3), 'X>5', 'XX2/3'), + (F(-2, 3), '.<5', '-2/3.'), + (F(-2, 3), '\n^6', '\n-2/3\n'), + # Thousands separators + (F(1234, 5679), ',', '1,234/5,679'), + (F(-1234, 5679), '_', '-1_234/5_679'), + (F(1234567), '_', '1_234_567'), + (F(-1234567), ',', '-1,234,567'), + # Alternate form forces a slash in the output + (F(123), '#', '123/1'), + (F(-123), '#', '-123/1'), + (F(0), '#', '0/1'), ] for fraction, spec, expected in testcases: with self.subTest(fraction=fraction, spec=spec): @@ -1218,6 +1256,10 @@ def test_invalid_formats(self): '.%', # Z instead of z for negative zero suppression 'Z.2f' + # z flag not supported for general formatting + 'z', + # zero padding not supported for general formatting + '05', ] for spec in invalid_specs: with self.subTest(spec=spec): diff --git a/Misc/NEWS.d/next/Library/2023-10-25-13-07-53.gh-issue-67790.jMn9Ad.rst b/Misc/NEWS.d/next/Library/2023-10-25-13-07-53.gh-issue-67790.jMn9Ad.rst new file mode 100644 index 00000000000000..44c5702a6551b0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-10-25-13-07-53.gh-issue-67790.jMn9Ad.rst @@ -0,0 +1,2 @@ +Implement basic formatting support (minimum width, alignment, fill) for +:class:`fractions.Fraction`. From 1583c40be938d2caf363c126976bc8757df90b13 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sat, 16 Dec 2023 09:13:50 -0600 Subject: [PATCH 44/62] gh-113202: Add a strict option to itertools.batched() (gh-113203) --- Doc/library/itertools.rst | 18 +++++++++-- Lib/test/test_itertools.py | 4 +++ ...-12-15-18-10-26.gh-issue-113202.xv_Ww8.rst | 1 + Modules/clinic/itertoolsmodule.c.h | 32 +++++++++++++------ Modules/itertoolsmodule.c | 29 ++++++++++------- 5 files changed, 60 insertions(+), 24 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-15-18-10-26.gh-issue-113202.xv_Ww8.rst diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 6bcda307f256f2..c016fb76bfd0a0 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -164,11 +164,14 @@ loops that truncate the stream. Added the optional *initial* parameter. -.. function:: batched(iterable, n) +.. function:: batched(iterable, n, *, strict=False) Batch data from the *iterable* into tuples of length *n*. The last batch may be shorter than *n*. + If *strict* is true, will raise a :exc:`ValueError` if the final + batch is shorter than *n*. + Loops over the input iterable and accumulates data into tuples up to size *n*. The input is consumed lazily, just enough to fill a batch. The result is yielded as soon as the batch is full or when the input @@ -190,16 +193,21 @@ loops that truncate the stream. Roughly equivalent to:: - def batched(iterable, n): + def batched(iterable, n, *, strict=False): # batched('ABCDEFG', 3) --> ABC DEF G if n < 1: raise ValueError('n must be at least one') it = iter(iterable) while batch := tuple(islice(it, n)): + if strict and len(batch) != n: + raise ValueError('batched(): incomplete batch') yield batch .. versionadded:: 3.12 + .. versionchanged:: 3.13 + Added the *strict* option. + .. function:: chain(*iterables) @@ -1039,7 +1047,7 @@ The following recipes have a more mathematical flavor: def reshape(matrix, cols): "Reshape a 2-D matrix to have a given number of columns." # reshape([(0, 1), (2, 3), (4, 5)], 3) --> (0, 1, 2), (3, 4, 5) - return batched(chain.from_iterable(matrix), cols) + return batched(chain.from_iterable(matrix), cols, strict=True) def transpose(matrix): "Swap the rows and columns of a 2-D matrix." @@ -1270,6 +1278,10 @@ The following recipes have a more mathematical flavor: [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11)] >>> list(reshape(M, 4)) [(0, 1, 2, 3), (4, 5, 6, 7), (8, 9, 10, 11)] + >>> list(reshape(M, 5)) + Traceback (most recent call last): + ... + ValueError: batched(): incomplete batch >>> list(reshape(M, 6)) [(0, 1, 2, 3, 4, 5), (6, 7, 8, 9, 10, 11)] >>> list(reshape(M, 12)) diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py index 705e880d98685e..9af0730ea98004 100644 --- a/Lib/test/test_itertools.py +++ b/Lib/test/test_itertools.py @@ -187,7 +187,11 @@ def test_batched(self): [('A', 'B'), ('C', 'D'), ('E', 'F'), ('G',)]) self.assertEqual(list(batched('ABCDEFG', 1)), [('A',), ('B',), ('C',), ('D',), ('E',), ('F',), ('G',)]) + self.assertEqual(list(batched('ABCDEF', 2, strict=True)), + [('A', 'B'), ('C', 'D'), ('E', 'F')]) + with self.assertRaises(ValueError): # Incomplete batch when strict + list(batched('ABCDEFG', 3, strict=True)) with self.assertRaises(TypeError): # Too few arguments list(batched('ABCDEFG')) with self.assertRaises(TypeError): diff --git a/Misc/NEWS.d/next/Library/2023-12-15-18-10-26.gh-issue-113202.xv_Ww8.rst b/Misc/NEWS.d/next/Library/2023-12-15-18-10-26.gh-issue-113202.xv_Ww8.rst new file mode 100644 index 00000000000000..44f26aef60a33a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-15-18-10-26.gh-issue-113202.xv_Ww8.rst @@ -0,0 +1 @@ +Add a ``strict`` option to ``batched()`` in the ``itertools`` module. diff --git a/Modules/clinic/itertoolsmodule.c.h b/Modules/clinic/itertoolsmodule.c.h index fa2c5e0e922387..3ec479943a83d4 100644 --- a/Modules/clinic/itertoolsmodule.c.h +++ b/Modules/clinic/itertoolsmodule.c.h @@ -10,7 +10,7 @@ preserve #include "pycore_modsupport.h" // _PyArg_UnpackKeywords() PyDoc_STRVAR(batched_new__doc__, -"batched(iterable, n)\n" +"batched(iterable, n, *, strict=False)\n" "--\n" "\n" "Batch data into tuples of length n. The last batch may be shorter than n.\n" @@ -25,10 +25,14 @@ PyDoc_STRVAR(batched_new__doc__, " ...\n" " (\'A\', \'B\', \'C\')\n" " (\'D\', \'E\', \'F\')\n" -" (\'G\',)"); +" (\'G\',)\n" +"\n" +"If \"strict\" is True, raises a ValueError if the final batch is shorter\n" +"than n."); static PyObject * -batched_new_impl(PyTypeObject *type, PyObject *iterable, Py_ssize_t n); +batched_new_impl(PyTypeObject *type, PyObject *iterable, Py_ssize_t n, + int strict); static PyObject * batched_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) @@ -36,14 +40,14 @@ batched_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - #define NUM_KEYWORDS 2 + #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(iterable), &_Py_ID(n), }, + .ob_item = { &_Py_ID(iterable), &_Py_ID(n), &_Py_ID(strict), }, }; #undef NUM_KEYWORDS #define KWTUPLE (&_kwtuple.ob_base.ob_base) @@ -52,18 +56,20 @@ batched_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) # define KWTUPLE NULL #endif // !Py_BUILD_CORE - static const char * const _keywords[] = {"iterable", "n", NULL}; + static const char * const _keywords[] = {"iterable", "n", "strict", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, .fname = "batched", .kwtuple = KWTUPLE, }; #undef KWTUPLE - PyObject *argsbuf[2]; + PyObject *argsbuf[3]; PyObject * const *fastargs; Py_ssize_t nargs = PyTuple_GET_SIZE(args); + Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 2; PyObject *iterable; Py_ssize_t n; + int strict = 0; fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 2, 2, 0, argsbuf); if (!fastargs) { @@ -82,7 +88,15 @@ batched_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) } n = ival; } - return_value = batched_new_impl(type, iterable, n); + if (!noptargs) { + goto skip_optional_kwonly; + } + strict = PyObject_IsTrue(fastargs[2]); + if (strict < 0) { + goto exit; + } +skip_optional_kwonly: + return_value = batched_new_impl(type, iterable, n, strict); exit: return return_value; @@ -914,4 +928,4 @@ itertools_count(PyTypeObject *type, PyObject *args, PyObject *kwargs) exit: return return_value; } -/*[clinic end generated code: output=782fe7e30733779b input=a9049054013a1b77]*/ +/*[clinic end generated code: output=c6a515f765da86b5 input=a9049054013a1b77]*/ diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c index ab99fa4d873bf5..164741495c7baf 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -105,20 +105,11 @@ class itertools.pairwise "pairwiseobject *" "clinic_state()->pairwise_type" /* batched object ************************************************************/ -/* Note: The built-in zip() function includes a "strict" argument - that was needed because that function would silently truncate data, - and there was no easy way for a user to detect the data loss. - The same reasoning does not apply to batched() which never drops data. - Instead, batched() produces a shorter tuple which can be handled - as the user sees fit. If requested, it would be reasonable to add - "fillvalue" support which had demonstrated value in zip_longest(). - For now, the API is kept simple and clean. - */ - typedef struct { PyObject_HEAD PyObject *it; Py_ssize_t batch_size; + bool strict; } batchedobject; /*[clinic input] @@ -126,6 +117,9 @@ typedef struct { itertools.batched.__new__ as batched_new iterable: object n: Py_ssize_t + * + strict: bool = False + Batch data into tuples of length n. The last batch may be shorter than n. Loops over the input iterable and accumulates data into tuples @@ -140,11 +134,15 @@ or when the input iterable is exhausted. ('D', 'E', 'F') ('G',) +If "strict" is True, raises a ValueError if the final batch is shorter +than n. + [clinic start generated code]*/ static PyObject * -batched_new_impl(PyTypeObject *type, PyObject *iterable, Py_ssize_t n) -/*[clinic end generated code: output=7ebc954d655371b6 input=ffd70726927c5129]*/ +batched_new_impl(PyTypeObject *type, PyObject *iterable, Py_ssize_t n, + int strict) +/*[clinic end generated code: output=c6de11b061529d3e input=7814b47e222f5467]*/ { PyObject *it; batchedobject *bo; @@ -170,6 +168,7 @@ batched_new_impl(PyTypeObject *type, PyObject *iterable, Py_ssize_t n) } bo->batch_size = n; bo->it = it; + bo->strict = (bool) strict; return (PyObject *)bo; } @@ -233,6 +232,12 @@ batched_next(batchedobject *bo) Py_DECREF(result); return NULL; } + if (bo->strict) { + Py_CLEAR(bo->it); + Py_DECREF(result); + PyErr_SetString(PyExc_ValueError, "batched(): incomplete batch"); + return NULL; + } _PyTuple_Resize(&result, i); return result; } From d91e43ed7839b601cbeadd137d89e69e2cc3efda Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Sat, 16 Dec 2023 19:04:33 +0000 Subject: [PATCH 45/62] GH-110109: Move tests for pathlib ABCs to new module. (#112904) --- Lib/test/test_pathlib/__init__.py | 5 + Lib/test/{ => test_pathlib}/test_pathlib.py | 1896 +----------------- Lib/test/test_pathlib/test_pathlib_abc.py | 1918 +++++++++++++++++++ Makefile.pre.in | 1 + 4 files changed, 1927 insertions(+), 1893 deletions(-) create mode 100644 Lib/test/test_pathlib/__init__.py rename Lib/test/{ => test_pathlib}/test_pathlib.py (50%) create mode 100644 Lib/test/test_pathlib/test_pathlib_abc.py diff --git a/Lib/test/test_pathlib/__init__.py b/Lib/test/test_pathlib/__init__.py new file mode 100644 index 00000000000000..4b16ecc31156a5 --- /dev/null +++ b/Lib/test/test_pathlib/__init__.py @@ -0,0 +1,5 @@ +import os +from test.support import load_package_tests + +def load_tests(*args): + return load_package_tests(os.path.dirname(__file__), *args) diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py similarity index 50% rename from Lib/test/test_pathlib.py rename to Lib/test/test_pathlib/test_pathlib.py index a1acba406ea37c..304786fc6f7fd9 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -1,11 +1,9 @@ -import collections.abc import io import os import sys import errno import pathlib import pickle -import posixpath import socket import stat import tempfile @@ -14,10 +12,10 @@ from urllib.request import pathname2url from test.support import import_helper -from test.support import set_recursion_limit from test.support import is_emscripten, is_wasi from test.support import os_helper from test.support.os_helper import TESTFN, FakePath +from test.test_pathlib import test_pathlib_abc try: import grp, pwd @@ -25,12 +23,6 @@ grp = pwd = None -class UnsupportedOperationTest(unittest.TestCase): - def test_is_notimplemented(self): - self.assertTrue(issubclass(pathlib.UnsupportedOperation, NotImplementedError)) - self.assertTrue(isinstance(pathlib.UnsupportedOperation(), NotImplementedError)) - - # Make sure any symbolic links in the base test path are resolved. BASE = os.path.realpath(TESTFN) join = lambda *x: os.path.join(BASE, *x) @@ -49,654 +41,7 @@ def test_is_notimplemented(self): # Tests for the pure classes. # - -class PurePathBaseTest(unittest.TestCase): - cls = pathlib._abc.PurePathBase - - def test_magic_methods(self): - P = self.cls - self.assertFalse(hasattr(P, '__fspath__')) - self.assertFalse(hasattr(P, '__bytes__')) - self.assertIs(P.__reduce__, object.__reduce__) - self.assertIs(P.__hash__, object.__hash__) - self.assertIs(P.__eq__, object.__eq__) - self.assertIs(P.__lt__, object.__lt__) - self.assertIs(P.__le__, object.__le__) - self.assertIs(P.__gt__, object.__gt__) - self.assertIs(P.__ge__, object.__ge__) - - -class DummyPurePath(pathlib._abc.PurePathBase): - def __eq__(self, other): - if not isinstance(other, DummyPurePath): - return NotImplemented - return str(self) == str(other) - - def __hash__(self): - return hash(str(self)) - - -class DummyPurePathTest(unittest.TestCase): - cls = DummyPurePath - - # Keys are canonical paths, values are list of tuples of arguments - # supposed to produce equal paths. - equivalences = { - 'a/b': [ - ('a', 'b'), ('a/', 'b'), ('a', 'b/'), ('a/', 'b/'), - ('a/b/',), ('a//b',), ('a//b//',), - # Empty components get removed. - ('', 'a', 'b'), ('a', '', 'b'), ('a', 'b', ''), - ], - '/b/c/d': [ - ('a', '/b/c', 'd'), ('/a', '/b/c', 'd'), - # Empty components get removed. - ('/', 'b', '', 'c/d'), ('/', '', 'b/c/d'), ('', '/b/c/d'), - ], - } - - def setUp(self): - p = self.cls('a') - self.pathmod = p.pathmod - self.sep = self.pathmod.sep - self.altsep = self.pathmod.altsep - - def test_constructor_common(self): - P = self.cls - p = P('a') - self.assertIsInstance(p, P) - P('a', 'b', 'c') - P('/a', 'b', 'c') - P('a/b/c') - P('/a/b/c') - - def test_concrete_class(self): - if self.cls is pathlib.PurePath: - expected = pathlib.PureWindowsPath if os.name == 'nt' else pathlib.PurePosixPath - else: - expected = self.cls - p = self.cls('a') - self.assertIs(type(p), expected) - - def test_different_pathmods_unequal(self): - p = self.cls('a') - if p.pathmod is posixpath: - q = pathlib.PureWindowsPath('a') - else: - q = pathlib.PurePosixPath('a') - self.assertNotEqual(p, q) - - def test_different_pathmods_unordered(self): - p = self.cls('a') - if p.pathmod is posixpath: - q = pathlib.PureWindowsPath('a') - else: - q = pathlib.PurePosixPath('a') - with self.assertRaises(TypeError): - p < q - with self.assertRaises(TypeError): - p <= q - with self.assertRaises(TypeError): - p > q - with self.assertRaises(TypeError): - p >= q - - def _check_str_subclass(self, *args): - # Issue #21127: it should be possible to construct a PurePath object - # from a str subclass instance, and it then gets converted to - # a pure str object. - class StrSubclass(str): - pass - P = self.cls - p = P(*(StrSubclass(x) for x in args)) - self.assertEqual(p, P(*args)) - for part in p.parts: - self.assertIs(type(part), str) - - def test_str_subclass_common(self): - self._check_str_subclass('') - self._check_str_subclass('.') - self._check_str_subclass('a') - self._check_str_subclass('a/b.txt') - self._check_str_subclass('/a/b.txt') - - def test_with_segments_common(self): - class P(self.cls): - def __init__(self, *pathsegments, session_id): - super().__init__(*pathsegments) - self.session_id = session_id - - def with_segments(self, *pathsegments): - return type(self)(*pathsegments, session_id=self.session_id) - p = P('foo', 'bar', session_id=42) - self.assertEqual(42, (p / 'foo').session_id) - self.assertEqual(42, ('foo' / p).session_id) - self.assertEqual(42, p.joinpath('foo').session_id) - self.assertEqual(42, p.with_name('foo').session_id) - self.assertEqual(42, p.with_stem('foo').session_id) - self.assertEqual(42, p.with_suffix('.foo').session_id) - self.assertEqual(42, p.with_segments('foo').session_id) - self.assertEqual(42, p.relative_to('foo').session_id) - self.assertEqual(42, p.parent.session_id) - for parent in p.parents: - self.assertEqual(42, parent.session_id) - - def _check_parse_path(self, raw_path, *expected): - sep = self.pathmod.sep - actual = self.cls._parse_path(raw_path.replace('/', sep)) - self.assertEqual(actual, expected) - if altsep := self.pathmod.altsep: - actual = self.cls._parse_path(raw_path.replace('/', altsep)) - self.assertEqual(actual, expected) - - def test_parse_path_common(self): - check = self._check_parse_path - sep = self.pathmod.sep - check('', '', '', []) - check('a', '', '', ['a']) - check('a/', '', '', ['a']) - check('a/b', '', '', ['a', 'b']) - check('a/b/', '', '', ['a', 'b']) - check('a/b/c/d', '', '', ['a', 'b', 'c', 'd']) - check('a/b//c/d', '', '', ['a', 'b', 'c', 'd']) - check('a/b/c/d', '', '', ['a', 'b', 'c', 'd']) - check('.', '', '', []) - check('././b', '', '', ['b']) - check('a/./b', '', '', ['a', 'b']) - check('a/./.', '', '', ['a']) - check('/a/b', '', sep, ['a', 'b']) - - def test_join_common(self): - P = self.cls - p = P('a/b') - pp = p.joinpath('c') - self.assertEqual(pp, P('a/b/c')) - self.assertIs(type(pp), type(p)) - pp = p.joinpath('c', 'd') - self.assertEqual(pp, P('a/b/c/d')) - pp = p.joinpath('/c') - self.assertEqual(pp, P('/c')) - - def test_div_common(self): - # Basically the same as joinpath(). - P = self.cls - p = P('a/b') - pp = p / 'c' - self.assertEqual(pp, P('a/b/c')) - self.assertIs(type(pp), type(p)) - pp = p / 'c/d' - self.assertEqual(pp, P('a/b/c/d')) - pp = p / 'c' / 'd' - self.assertEqual(pp, P('a/b/c/d')) - pp = 'c' / p / 'd' - self.assertEqual(pp, P('c/a/b/d')) - pp = p/ '/c' - self.assertEqual(pp, P('/c')) - - def _check_str(self, expected, args): - p = self.cls(*args) - self.assertEqual(str(p), expected.replace('/', self.sep)) - - def test_str_common(self): - # Canonicalized paths roundtrip. - for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'): - self._check_str(pathstr, (pathstr,)) - # Special case for the empty path. - self._check_str('.', ('',)) - # Other tests for str() are in test_equivalences(). - - def test_as_posix_common(self): - P = self.cls - for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'): - self.assertEqual(P(pathstr).as_posix(), pathstr) - # Other tests for as_posix() are in test_equivalences(). - - def test_repr_common(self): - for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'): - with self.subTest(pathstr=pathstr): - p = self.cls(pathstr) - clsname = p.__class__.__name__ - r = repr(p) - # The repr() is in the form ClassName("forward-slashes path"). - self.assertTrue(r.startswith(clsname + '('), r) - self.assertTrue(r.endswith(')'), r) - inner = r[len(clsname) + 1 : -1] - self.assertEqual(eval(inner), p.as_posix()) - - def test_eq_common(self): - P = self.cls - self.assertEqual(P('a/b'), P('a/b')) - self.assertEqual(P('a/b'), P('a', 'b')) - self.assertNotEqual(P('a/b'), P('a')) - self.assertNotEqual(P('a/b'), P('/a/b')) - self.assertNotEqual(P('a/b'), P()) - self.assertNotEqual(P('/a/b'), P('/')) - self.assertNotEqual(P(), P('/')) - self.assertNotEqual(P(), "") - self.assertNotEqual(P(), {}) - self.assertNotEqual(P(), int) - - def test_match_common(self): - P = self.cls - self.assertRaises(ValueError, P('a').match, '') - self.assertRaises(ValueError, P('a').match, '.') - # Simple relative pattern. - self.assertTrue(P('b.py').match('b.py')) - self.assertTrue(P('a/b.py').match('b.py')) - self.assertTrue(P('/a/b.py').match('b.py')) - self.assertFalse(P('a.py').match('b.py')) - self.assertFalse(P('b/py').match('b.py')) - self.assertFalse(P('/a.py').match('b.py')) - self.assertFalse(P('b.py/c').match('b.py')) - # Wildcard relative pattern. - self.assertTrue(P('b.py').match('*.py')) - self.assertTrue(P('a/b.py').match('*.py')) - self.assertTrue(P('/a/b.py').match('*.py')) - self.assertFalse(P('b.pyc').match('*.py')) - self.assertFalse(P('b./py').match('*.py')) - self.assertFalse(P('b.py/c').match('*.py')) - # Multi-part relative pattern. - self.assertTrue(P('ab/c.py').match('a*/*.py')) - self.assertTrue(P('/d/ab/c.py').match('a*/*.py')) - self.assertFalse(P('a.py').match('a*/*.py')) - self.assertFalse(P('/dab/c.py').match('a*/*.py')) - self.assertFalse(P('ab/c.py/d').match('a*/*.py')) - # Absolute pattern. - self.assertTrue(P('/b.py').match('/*.py')) - self.assertFalse(P('b.py').match('/*.py')) - self.assertFalse(P('a/b.py').match('/*.py')) - self.assertFalse(P('/a/b.py').match('/*.py')) - # Multi-part absolute pattern. - self.assertTrue(P('/a/b.py').match('/a/*.py')) - self.assertFalse(P('/ab.py').match('/a/*.py')) - self.assertFalse(P('/a/b/c.py').match('/a/*.py')) - # Multi-part glob-style pattern. - self.assertTrue(P('a').match('**')) - self.assertTrue(P('c.py').match('**')) - self.assertTrue(P('a/b/c.py').match('**')) - self.assertTrue(P('/a/b/c.py').match('**')) - self.assertTrue(P('/a/b/c.py').match('/**')) - self.assertTrue(P('/a/b/c.py').match('**/')) - self.assertTrue(P('/a/b/c.py').match('/a/**')) - self.assertTrue(P('/a/b/c.py').match('**/*.py')) - self.assertTrue(P('/a/b/c.py').match('/**/*.py')) - self.assertTrue(P('/a/b/c.py').match('/a/**/*.py')) - self.assertTrue(P('/a/b/c.py').match('/a/b/**/*.py')) - self.assertTrue(P('/a/b/c.py').match('/**/**/**/**/*.py')) - self.assertFalse(P('c.py').match('**/a.py')) - self.assertFalse(P('c.py').match('c/**')) - self.assertFalse(P('a/b/c.py').match('**/a')) - self.assertFalse(P('a/b/c.py').match('**/a/b')) - self.assertFalse(P('a/b/c.py').match('**/a/b/c')) - self.assertFalse(P('a/b/c.py').match('**/a/b/c.')) - self.assertFalse(P('a/b/c.py').match('**/a/b/c./**')) - self.assertFalse(P('a/b/c.py').match('**/a/b/c./**')) - self.assertFalse(P('a/b/c.py').match('/a/b/c.py/**')) - self.assertFalse(P('a/b/c.py').match('/**/a/b/c.py')) - self.assertRaises(ValueError, P('a').match, '**a/b/c') - self.assertRaises(ValueError, P('a').match, 'a/b/c**') - # Case-sensitive flag - self.assertFalse(P('A.py').match('a.PY', case_sensitive=True)) - self.assertTrue(P('A.py').match('a.PY', case_sensitive=False)) - self.assertFalse(P('c:/a/B.Py').match('C:/A/*.pY', case_sensitive=True)) - self.assertTrue(P('/a/b/c.py').match('/A/*/*.Py', case_sensitive=False)) - # Matching against empty path - self.assertFalse(P().match('*')) - self.assertTrue(P().match('**')) - self.assertFalse(P().match('**/*')) - - def test_parts_common(self): - # `parts` returns a tuple. - sep = self.sep - P = self.cls - p = P('a/b') - parts = p.parts - self.assertEqual(parts, ('a', 'b')) - # When the path is absolute, the anchor is a separate part. - p = P('/a/b') - parts = p.parts - self.assertEqual(parts, (sep, 'a', 'b')) - - def test_equivalences(self): - for k, tuples in self.equivalences.items(): - canon = k.replace('/', self.sep) - posix = k.replace(self.sep, '/') - if canon != posix: - tuples = tuples + [ - tuple(part.replace('/', self.sep) for part in t) - for t in tuples - ] - tuples.append((posix, )) - pcanon = self.cls(canon) - for t in tuples: - p = self.cls(*t) - self.assertEqual(p, pcanon, "failed with args {}".format(t)) - self.assertEqual(hash(p), hash(pcanon)) - self.assertEqual(str(p), canon) - self.assertEqual(p.as_posix(), posix) - - def test_parent_common(self): - # Relative - P = self.cls - p = P('a/b/c') - self.assertEqual(p.parent, P('a/b')) - self.assertEqual(p.parent.parent, P('a')) - self.assertEqual(p.parent.parent.parent, P()) - self.assertEqual(p.parent.parent.parent.parent, P()) - # Anchored - p = P('/a/b/c') - self.assertEqual(p.parent, P('/a/b')) - self.assertEqual(p.parent.parent, P('/a')) - self.assertEqual(p.parent.parent.parent, P('/')) - self.assertEqual(p.parent.parent.parent.parent, P('/')) - - def test_parents_common(self): - # Relative - P = self.cls - p = P('a/b/c') - par = p.parents - self.assertEqual(len(par), 3) - self.assertEqual(par[0], P('a/b')) - self.assertEqual(par[1], P('a')) - self.assertEqual(par[2], P('.')) - self.assertEqual(par[-1], P('.')) - self.assertEqual(par[-2], P('a')) - self.assertEqual(par[-3], P('a/b')) - self.assertEqual(par[0:1], (P('a/b'),)) - self.assertEqual(par[:2], (P('a/b'), P('a'))) - self.assertEqual(par[:-1], (P('a/b'), P('a'))) - self.assertEqual(par[1:], (P('a'), P('.'))) - self.assertEqual(par[::2], (P('a/b'), P('.'))) - self.assertEqual(par[::-1], (P('.'), P('a'), P('a/b'))) - self.assertEqual(list(par), [P('a/b'), P('a'), P('.')]) - with self.assertRaises(IndexError): - par[-4] - with self.assertRaises(IndexError): - par[3] - with self.assertRaises(TypeError): - par[0] = p - # Anchored - p = P('/a/b/c') - par = p.parents - self.assertEqual(len(par), 3) - self.assertEqual(par[0], P('/a/b')) - self.assertEqual(par[1], P('/a')) - self.assertEqual(par[2], P('/')) - self.assertEqual(par[-1], P('/')) - self.assertEqual(par[-2], P('/a')) - self.assertEqual(par[-3], P('/a/b')) - self.assertEqual(par[0:1], (P('/a/b'),)) - self.assertEqual(par[:2], (P('/a/b'), P('/a'))) - self.assertEqual(par[:-1], (P('/a/b'), P('/a'))) - self.assertEqual(par[1:], (P('/a'), P('/'))) - self.assertEqual(par[::2], (P('/a/b'), P('/'))) - self.assertEqual(par[::-1], (P('/'), P('/a'), P('/a/b'))) - self.assertEqual(list(par), [P('/a/b'), P('/a'), P('/')]) - with self.assertRaises(IndexError): - par[-4] - with self.assertRaises(IndexError): - par[3] - - def test_drive_common(self): - P = self.cls - self.assertEqual(P('a/b').drive, '') - self.assertEqual(P('/a/b').drive, '') - self.assertEqual(P('').drive, '') - - def test_root_common(self): - P = self.cls - sep = self.sep - self.assertEqual(P('').root, '') - self.assertEqual(P('a/b').root, '') - self.assertEqual(P('/').root, sep) - self.assertEqual(P('/a/b').root, sep) - - def test_anchor_common(self): - P = self.cls - sep = self.sep - self.assertEqual(P('').anchor, '') - self.assertEqual(P('a/b').anchor, '') - self.assertEqual(P('/').anchor, sep) - self.assertEqual(P('/a/b').anchor, sep) - - def test_name_common(self): - P = self.cls - self.assertEqual(P('').name, '') - self.assertEqual(P('.').name, '') - self.assertEqual(P('/').name, '') - self.assertEqual(P('a/b').name, 'b') - self.assertEqual(P('/a/b').name, 'b') - self.assertEqual(P('/a/b/.').name, 'b') - self.assertEqual(P('a/b.py').name, 'b.py') - self.assertEqual(P('/a/b.py').name, 'b.py') - - def test_suffix_common(self): - P = self.cls - self.assertEqual(P('').suffix, '') - self.assertEqual(P('.').suffix, '') - self.assertEqual(P('..').suffix, '') - self.assertEqual(P('/').suffix, '') - self.assertEqual(P('a/b').suffix, '') - self.assertEqual(P('/a/b').suffix, '') - self.assertEqual(P('/a/b/.').suffix, '') - self.assertEqual(P('a/b.py').suffix, '.py') - self.assertEqual(P('/a/b.py').suffix, '.py') - self.assertEqual(P('a/.hgrc').suffix, '') - self.assertEqual(P('/a/.hgrc').suffix, '') - self.assertEqual(P('a/.hg.rc').suffix, '.rc') - self.assertEqual(P('/a/.hg.rc').suffix, '.rc') - self.assertEqual(P('a/b.tar.gz').suffix, '.gz') - self.assertEqual(P('/a/b.tar.gz').suffix, '.gz') - self.assertEqual(P('a/Some name. Ending with a dot.').suffix, '') - self.assertEqual(P('/a/Some name. Ending with a dot.').suffix, '') - - def test_suffixes_common(self): - P = self.cls - self.assertEqual(P('').suffixes, []) - self.assertEqual(P('.').suffixes, []) - self.assertEqual(P('/').suffixes, []) - self.assertEqual(P('a/b').suffixes, []) - self.assertEqual(P('/a/b').suffixes, []) - self.assertEqual(P('/a/b/.').suffixes, []) - self.assertEqual(P('a/b.py').suffixes, ['.py']) - self.assertEqual(P('/a/b.py').suffixes, ['.py']) - self.assertEqual(P('a/.hgrc').suffixes, []) - self.assertEqual(P('/a/.hgrc').suffixes, []) - self.assertEqual(P('a/.hg.rc').suffixes, ['.rc']) - self.assertEqual(P('/a/.hg.rc').suffixes, ['.rc']) - self.assertEqual(P('a/b.tar.gz').suffixes, ['.tar', '.gz']) - self.assertEqual(P('/a/b.tar.gz').suffixes, ['.tar', '.gz']) - self.assertEqual(P('a/Some name. Ending with a dot.').suffixes, []) - self.assertEqual(P('/a/Some name. Ending with a dot.').suffixes, []) - - def test_stem_common(self): - P = self.cls - self.assertEqual(P('').stem, '') - self.assertEqual(P('.').stem, '') - self.assertEqual(P('..').stem, '..') - self.assertEqual(P('/').stem, '') - self.assertEqual(P('a/b').stem, 'b') - self.assertEqual(P('a/b.py').stem, 'b') - self.assertEqual(P('a/.hgrc').stem, '.hgrc') - self.assertEqual(P('a/.hg.rc').stem, '.hg') - self.assertEqual(P('a/b.tar.gz').stem, 'b.tar') - self.assertEqual(P('a/Some name. Ending with a dot.').stem, - 'Some name. Ending with a dot.') - - def test_with_name_common(self): - P = self.cls - self.assertEqual(P('a/b').with_name('d.xml'), P('a/d.xml')) - self.assertEqual(P('/a/b').with_name('d.xml'), P('/a/d.xml')) - self.assertEqual(P('a/b.py').with_name('d.xml'), P('a/d.xml')) - self.assertEqual(P('/a/b.py').with_name('d.xml'), P('/a/d.xml')) - self.assertEqual(P('a/Dot ending.').with_name('d.xml'), P('a/d.xml')) - self.assertEqual(P('/a/Dot ending.').with_name('d.xml'), P('/a/d.xml')) - self.assertRaises(ValueError, P('').with_name, 'd.xml') - self.assertRaises(ValueError, P('.').with_name, 'd.xml') - self.assertRaises(ValueError, P('/').with_name, 'd.xml') - self.assertRaises(ValueError, P('a/b').with_name, '') - self.assertRaises(ValueError, P('a/b').with_name, '.') - self.assertRaises(ValueError, P('a/b').with_name, '/c') - self.assertRaises(ValueError, P('a/b').with_name, 'c/') - self.assertRaises(ValueError, P('a/b').with_name, 'c/d') - - def test_with_stem_common(self): - P = self.cls - self.assertEqual(P('a/b').with_stem('d'), P('a/d')) - self.assertEqual(P('/a/b').with_stem('d'), P('/a/d')) - self.assertEqual(P('a/b.py').with_stem('d'), P('a/d.py')) - self.assertEqual(P('/a/b.py').with_stem('d'), P('/a/d.py')) - self.assertEqual(P('/a/b.tar.gz').with_stem('d'), P('/a/d.gz')) - self.assertEqual(P('a/Dot ending.').with_stem('d'), P('a/d')) - self.assertEqual(P('/a/Dot ending.').with_stem('d'), P('/a/d')) - self.assertRaises(ValueError, P('').with_stem, 'd') - self.assertRaises(ValueError, P('.').with_stem, 'd') - self.assertRaises(ValueError, P('/').with_stem, 'd') - self.assertRaises(ValueError, P('a/b').with_stem, '') - self.assertRaises(ValueError, P('a/b').with_stem, '.') - self.assertRaises(ValueError, P('a/b').with_stem, '/c') - self.assertRaises(ValueError, P('a/b').with_stem, 'c/') - self.assertRaises(ValueError, P('a/b').with_stem, 'c/d') - - def test_with_suffix_common(self): - P = self.cls - self.assertEqual(P('a/b').with_suffix('.gz'), P('a/b.gz')) - self.assertEqual(P('/a/b').with_suffix('.gz'), P('/a/b.gz')) - self.assertEqual(P('a/b.py').with_suffix('.gz'), P('a/b.gz')) - self.assertEqual(P('/a/b.py').with_suffix('.gz'), P('/a/b.gz')) - # Stripping suffix. - self.assertEqual(P('a/b.py').with_suffix(''), P('a/b')) - self.assertEqual(P('/a/b').with_suffix(''), P('/a/b')) - # Path doesn't have a "filename" component. - self.assertRaises(ValueError, P('').with_suffix, '.gz') - self.assertRaises(ValueError, P('.').with_suffix, '.gz') - self.assertRaises(ValueError, P('/').with_suffix, '.gz') - # Invalid suffix. - self.assertRaises(ValueError, P('a/b').with_suffix, 'gz') - self.assertRaises(ValueError, P('a/b').with_suffix, '/') - self.assertRaises(ValueError, P('a/b').with_suffix, '.') - self.assertRaises(ValueError, P('a/b').with_suffix, '/.gz') - self.assertRaises(ValueError, P('a/b').with_suffix, 'c/d') - self.assertRaises(ValueError, P('a/b').with_suffix, '.c/.d') - self.assertRaises(ValueError, P('a/b').with_suffix, './.d') - self.assertRaises(ValueError, P('a/b').with_suffix, '.d/.') - - def test_relative_to_common(self): - P = self.cls - p = P('a/b') - self.assertRaises(TypeError, p.relative_to) - self.assertRaises(TypeError, p.relative_to, b'a') - self.assertEqual(p.relative_to(P()), P('a/b')) - self.assertEqual(p.relative_to(''), P('a/b')) - self.assertEqual(p.relative_to(P('a')), P('b')) - self.assertEqual(p.relative_to('a'), P('b')) - self.assertEqual(p.relative_to('a/'), P('b')) - self.assertEqual(p.relative_to(P('a/b')), P()) - self.assertEqual(p.relative_to('a/b'), P()) - self.assertEqual(p.relative_to(P(), walk_up=True), P('a/b')) - self.assertEqual(p.relative_to('', walk_up=True), P('a/b')) - self.assertEqual(p.relative_to(P('a'), walk_up=True), P('b')) - self.assertEqual(p.relative_to('a', walk_up=True), P('b')) - self.assertEqual(p.relative_to('a/', walk_up=True), P('b')) - self.assertEqual(p.relative_to(P('a/b'), walk_up=True), P()) - self.assertEqual(p.relative_to('a/b', walk_up=True), P()) - self.assertEqual(p.relative_to(P('a/c'), walk_up=True), P('../b')) - self.assertEqual(p.relative_to('a/c', walk_up=True), P('../b')) - self.assertEqual(p.relative_to(P('a/b/c'), walk_up=True), P('..')) - self.assertEqual(p.relative_to('a/b/c', walk_up=True), P('..')) - self.assertEqual(p.relative_to(P('c'), walk_up=True), P('../a/b')) - self.assertEqual(p.relative_to('c', walk_up=True), P('../a/b')) - # With several args. - with self.assertWarns(DeprecationWarning): - p.relative_to('a', 'b') - p.relative_to('a', 'b', walk_up=True) - # Unrelated paths. - self.assertRaises(ValueError, p.relative_to, P('c')) - self.assertRaises(ValueError, p.relative_to, P('a/b/c')) - self.assertRaises(ValueError, p.relative_to, P('a/c')) - self.assertRaises(ValueError, p.relative_to, P('/a')) - self.assertRaises(ValueError, p.relative_to, P("../a")) - self.assertRaises(ValueError, p.relative_to, P("a/..")) - self.assertRaises(ValueError, p.relative_to, P("/a/..")) - self.assertRaises(ValueError, p.relative_to, P('/'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('/a'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P("../a"), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P("a/.."), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P("/a/.."), walk_up=True) - p = P('/a/b') - self.assertEqual(p.relative_to(P('/')), P('a/b')) - self.assertEqual(p.relative_to('/'), P('a/b')) - self.assertEqual(p.relative_to(P('/a')), P('b')) - self.assertEqual(p.relative_to('/a'), P('b')) - self.assertEqual(p.relative_to('/a/'), P('b')) - self.assertEqual(p.relative_to(P('/a/b')), P()) - self.assertEqual(p.relative_to('/a/b'), P()) - self.assertEqual(p.relative_to(P('/'), walk_up=True), P('a/b')) - self.assertEqual(p.relative_to('/', walk_up=True), P('a/b')) - self.assertEqual(p.relative_to(P('/a'), walk_up=True), P('b')) - self.assertEqual(p.relative_to('/a', walk_up=True), P('b')) - self.assertEqual(p.relative_to('/a/', walk_up=True), P('b')) - self.assertEqual(p.relative_to(P('/a/b'), walk_up=True), P()) - self.assertEqual(p.relative_to('/a/b', walk_up=True), P()) - self.assertEqual(p.relative_to(P('/a/c'), walk_up=True), P('../b')) - self.assertEqual(p.relative_to('/a/c', walk_up=True), P('../b')) - self.assertEqual(p.relative_to(P('/a/b/c'), walk_up=True), P('..')) - self.assertEqual(p.relative_to('/a/b/c', walk_up=True), P('..')) - self.assertEqual(p.relative_to(P('/c'), walk_up=True), P('../a/b')) - self.assertEqual(p.relative_to('/c', walk_up=True), P('../a/b')) - # Unrelated paths. - self.assertRaises(ValueError, p.relative_to, P('/c')) - self.assertRaises(ValueError, p.relative_to, P('/a/b/c')) - self.assertRaises(ValueError, p.relative_to, P('/a/c')) - self.assertRaises(ValueError, p.relative_to, P()) - self.assertRaises(ValueError, p.relative_to, '') - self.assertRaises(ValueError, p.relative_to, P('a')) - self.assertRaises(ValueError, p.relative_to, P("../a")) - self.assertRaises(ValueError, p.relative_to, P("a/..")) - self.assertRaises(ValueError, p.relative_to, P("/a/..")) - self.assertRaises(ValueError, p.relative_to, P(''), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('a'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P("../a"), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P("a/.."), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P("/a/.."), walk_up=True) - - def test_is_relative_to_common(self): - P = self.cls - p = P('a/b') - self.assertRaises(TypeError, p.is_relative_to) - self.assertRaises(TypeError, p.is_relative_to, b'a') - self.assertTrue(p.is_relative_to(P())) - self.assertTrue(p.is_relative_to('')) - self.assertTrue(p.is_relative_to(P('a'))) - self.assertTrue(p.is_relative_to('a/')) - self.assertTrue(p.is_relative_to(P('a/b'))) - self.assertTrue(p.is_relative_to('a/b')) - # With several args. - with self.assertWarns(DeprecationWarning): - p.is_relative_to('a', 'b') - # Unrelated paths. - self.assertFalse(p.is_relative_to(P('c'))) - self.assertFalse(p.is_relative_to(P('a/b/c'))) - self.assertFalse(p.is_relative_to(P('a/c'))) - self.assertFalse(p.is_relative_to(P('/a'))) - p = P('/a/b') - self.assertTrue(p.is_relative_to(P('/'))) - self.assertTrue(p.is_relative_to('/')) - self.assertTrue(p.is_relative_to(P('/a'))) - self.assertTrue(p.is_relative_to('/a')) - self.assertTrue(p.is_relative_to('/a/')) - self.assertTrue(p.is_relative_to(P('/a/b'))) - self.assertTrue(p.is_relative_to('/a/b')) - # Unrelated paths. - self.assertFalse(p.is_relative_to(P('/c'))) - self.assertFalse(p.is_relative_to(P('/a/b/c'))) - self.assertFalse(p.is_relative_to(P('/a/c'))) - self.assertFalse(p.is_relative_to(P())) - self.assertFalse(p.is_relative_to('')) - self.assertFalse(p.is_relative_to(P('a'))) - - -class PurePathTest(DummyPurePathTest): +class PurePathTest(test_pathlib_abc.DummyPurePathTest): cls = pathlib.PurePath def test_constructor_nested(self): @@ -1581,1246 +926,11 @@ class cls(pathlib.PurePath): test_repr_roundtrips = None -# -# Tests for the virtual classes. -# - -class PathBaseTest(PurePathBaseTest): - cls = pathlib._abc.PathBase - - def test_unsupported_operation(self): - P = self.cls - p = self.cls() - e = pathlib.UnsupportedOperation - self.assertRaises(e, p.stat) - self.assertRaises(e, p.lstat) - self.assertRaises(e, p.exists) - self.assertRaises(e, p.samefile, 'foo') - self.assertRaises(e, p.is_dir) - self.assertRaises(e, p.is_file) - self.assertRaises(e, p.is_mount) - self.assertRaises(e, p.is_symlink) - self.assertRaises(e, p.is_block_device) - self.assertRaises(e, p.is_char_device) - self.assertRaises(e, p.is_fifo) - self.assertRaises(e, p.is_socket) - self.assertRaises(e, p.open) - self.assertRaises(e, p.read_bytes) - self.assertRaises(e, p.read_text) - self.assertRaises(e, p.write_bytes, b'foo') - self.assertRaises(e, p.write_text, 'foo') - self.assertRaises(e, p.iterdir) - self.assertRaises(e, p.glob, '*') - self.assertRaises(e, p.rglob, '*') - self.assertRaises(e, lambda: list(p.walk())) - self.assertRaises(e, p.absolute) - self.assertRaises(e, P.cwd) - self.assertRaises(e, p.expanduser) - self.assertRaises(e, p.home) - self.assertRaises(e, p.readlink) - self.assertRaises(e, p.symlink_to, 'foo') - self.assertRaises(e, p.hardlink_to, 'foo') - self.assertRaises(e, p.mkdir) - self.assertRaises(e, p.touch) - self.assertRaises(e, p.rename, 'foo') - self.assertRaises(e, p.replace, 'foo') - self.assertRaises(e, p.chmod, 0o755) - self.assertRaises(e, p.lchmod, 0o755) - self.assertRaises(e, p.unlink) - self.assertRaises(e, p.rmdir) - self.assertRaises(e, p.owner) - self.assertRaises(e, p.group) - self.assertRaises(e, p.as_uri) - - def test_as_uri_common(self): - e = pathlib.UnsupportedOperation - self.assertRaises(e, self.cls().as_uri) - - def test_fspath_common(self): - self.assertRaises(TypeError, os.fspath, self.cls()) - - def test_as_bytes_common(self): - self.assertRaises(TypeError, bytes, self.cls()) - - def test_matches_path_api(self): - our_names = {name for name in dir(self.cls) if name[0] != '_'} - path_names = {name for name in dir(pathlib.Path) if name[0] != '_'} - self.assertEqual(our_names, path_names) - for attr_name in our_names: - our_attr = getattr(self.cls, attr_name) - path_attr = getattr(pathlib.Path, attr_name) - self.assertEqual(our_attr.__doc__, path_attr.__doc__) - - -class DummyPathIO(io.BytesIO): - """ - Used by DummyPath to implement `open('w')` - """ - - def __init__(self, files, path): - super().__init__() - self.files = files - self.path = path - - def close(self): - self.files[self.path] = self.getvalue() - super().close() - - -class DummyPath(pathlib._abc.PathBase): - """ - Simple implementation of PathBase that keeps files and directories in - memory. - """ - _files = {} - _directories = {} - _symlinks = {} - - def __eq__(self, other): - if not isinstance(other, DummyPath): - return NotImplemented - return str(self) == str(other) - - def __hash__(self): - return hash(str(self)) - - def stat(self, *, follow_symlinks=True): - if follow_symlinks: - path = str(self.resolve()) - else: - path = str(self.parent.resolve() / self.name) - if path in self._files: - st_mode = stat.S_IFREG - elif path in self._directories: - st_mode = stat.S_IFDIR - elif path in self._symlinks: - st_mode = stat.S_IFLNK - else: - raise FileNotFoundError(errno.ENOENT, "Not found", str(self)) - return os.stat_result((st_mode, hash(str(self)), 0, 0, 0, 0, 0, 0, 0, 0)) - - def open(self, mode='r', buffering=-1, encoding=None, - errors=None, newline=None): - if buffering != -1: - raise NotImplementedError - path_obj = self.resolve() - path = str(path_obj) - name = path_obj.name - parent = str(path_obj.parent) - if path in self._directories: - raise IsADirectoryError(errno.EISDIR, "Is a directory", path) - - text = 'b' not in mode - mode = ''.join(c for c in mode if c not in 'btU') - if mode == 'r': - if path not in self._files: - raise FileNotFoundError(errno.ENOENT, "File not found", path) - stream = io.BytesIO(self._files[path]) - elif mode == 'w': - if parent not in self._directories: - raise FileNotFoundError(errno.ENOENT, "File not found", parent) - stream = DummyPathIO(self._files, path) - self._files[path] = b'' - self._directories[parent].add(name) - else: - raise NotImplementedError - if text: - stream = io.TextIOWrapper(stream, encoding=encoding, errors=errors, newline=newline) - return stream - - def iterdir(self): - path = str(self.resolve()) - if path in self._files: - raise NotADirectoryError(errno.ENOTDIR, "Not a directory", path) - elif path in self._directories: - return (self / name for name in self._directories[path]) - else: - raise FileNotFoundError(errno.ENOENT, "File not found", path) - - def mkdir(self, mode=0o777, parents=False, exist_ok=False): - path = str(self.resolve()) - if path in self._directories: - if exist_ok: - return - else: - raise FileExistsError(errno.EEXIST, "File exists", path) - try: - if self.name: - self._directories[str(self.parent)].add(self.name) - self._directories[path] = set() - except KeyError: - if not parents: - raise FileNotFoundError(errno.ENOENT, "File not found", str(self.parent)) from None - self.parent.mkdir(parents=True, exist_ok=True) - self.mkdir(mode, parents=False, exist_ok=exist_ok) - - -class DummyPathTest(DummyPurePathTest): - """Tests for PathBase methods that use stat(), open() and iterdir().""" - - cls = DummyPath - can_symlink = False - - # (BASE) - # | - # |-- brokenLink -> non-existing - # |-- dirA - # | `-- linkC -> ../dirB - # |-- dirB - # | |-- fileB - # | `-- linkD -> ../dirB - # |-- dirC - # | |-- dirD - # | | `-- fileD - # | `-- fileC - # | `-- novel.txt - # |-- dirE # No permissions - # |-- fileA - # |-- linkA -> fileA - # |-- linkB -> dirB - # `-- brokenLinkLoop -> brokenLinkLoop - # - - def setUp(self): - super().setUp() - pathmod = self.cls.pathmod - p = self.cls(BASE) - p.mkdir(parents=True) - p.joinpath('dirA').mkdir() - p.joinpath('dirB').mkdir() - p.joinpath('dirC').mkdir() - p.joinpath('dirC', 'dirD').mkdir() - p.joinpath('dirE').mkdir() - with p.joinpath('fileA').open('wb') as f: - f.write(b"this is file A\n") - with p.joinpath('dirB', 'fileB').open('wb') as f: - f.write(b"this is file B\n") - with p.joinpath('dirC', 'fileC').open('wb') as f: - f.write(b"this is file C\n") - with p.joinpath('dirC', 'novel.txt').open('wb') as f: - f.write(b"this is a novel\n") - with p.joinpath('dirC', 'dirD', 'fileD').open('wb') as f: - f.write(b"this is file D\n") - if self.can_symlink: - p.joinpath('linkA').symlink_to('fileA') - p.joinpath('brokenLink').symlink_to('non-existing') - p.joinpath('linkB').symlink_to('dirB') - p.joinpath('dirA', 'linkC').symlink_to(pathmod.join('..', 'dirB')) - p.joinpath('dirB', 'linkD').symlink_to(pathmod.join('..', 'dirB')) - p.joinpath('brokenLinkLoop').symlink_to('brokenLinkLoop') - - def tearDown(self): - cls = self.cls - cls._files.clear() - cls._directories.clear() - cls._symlinks.clear() - - def tempdir(self): - path = self.cls(BASE).with_name('tmp-dirD') - path.mkdir() - return path - - def assertFileNotFound(self, func, *args, **kwargs): - with self.assertRaises(FileNotFoundError) as cm: - func(*args, **kwargs) - self.assertEqual(cm.exception.errno, errno.ENOENT) - - def assertEqualNormCase(self, path_a, path_b): - self.assertEqual(os.path.normcase(path_a), os.path.normcase(path_b)) - - def test_samefile(self): - fileA_path = os.path.join(BASE, 'fileA') - fileB_path = os.path.join(BASE, 'dirB', 'fileB') - p = self.cls(fileA_path) - pp = self.cls(fileA_path) - q = self.cls(fileB_path) - self.assertTrue(p.samefile(fileA_path)) - self.assertTrue(p.samefile(pp)) - self.assertFalse(p.samefile(fileB_path)) - self.assertFalse(p.samefile(q)) - # Test the non-existent file case - non_existent = os.path.join(BASE, 'foo') - r = self.cls(non_existent) - self.assertRaises(FileNotFoundError, p.samefile, r) - self.assertRaises(FileNotFoundError, p.samefile, non_existent) - self.assertRaises(FileNotFoundError, r.samefile, p) - self.assertRaises(FileNotFoundError, r.samefile, non_existent) - self.assertRaises(FileNotFoundError, r.samefile, r) - self.assertRaises(FileNotFoundError, r.samefile, non_existent) - - def test_empty_path(self): - # The empty path points to '.' - p = self.cls('') - self.assertEqual(str(p), '.') - - def test_exists(self): - P = self.cls - p = P(BASE) - self.assertIs(True, p.exists()) - self.assertIs(True, (p / 'dirA').exists()) - self.assertIs(True, (p / 'fileA').exists()) - self.assertIs(False, (p / 'fileA' / 'bah').exists()) - if self.can_symlink: - self.assertIs(True, (p / 'linkA').exists()) - self.assertIs(True, (p / 'linkB').exists()) - self.assertIs(True, (p / 'linkB' / 'fileB').exists()) - self.assertIs(False, (p / 'linkA' / 'bah').exists()) - self.assertIs(False, (p / 'brokenLink').exists()) - self.assertIs(True, (p / 'brokenLink').exists(follow_symlinks=False)) - self.assertIs(False, (p / 'foo').exists()) - self.assertIs(False, P('/xyzzy').exists()) - self.assertIs(False, P(BASE + '\udfff').exists()) - self.assertIs(False, P(BASE + '\x00').exists()) - - def test_open_common(self): - p = self.cls(BASE) - with (p / 'fileA').open('r') as f: - self.assertIsInstance(f, io.TextIOBase) - self.assertEqual(f.read(), "this is file A\n") - with (p / 'fileA').open('rb') as f: - self.assertIsInstance(f, io.BufferedIOBase) - self.assertEqual(f.read().strip(), b"this is file A") - - def test_read_write_bytes(self): - p = self.cls(BASE) - (p / 'fileA').write_bytes(b'abcdefg') - self.assertEqual((p / 'fileA').read_bytes(), b'abcdefg') - # Check that trying to write str does not truncate the file. - self.assertRaises(TypeError, (p / 'fileA').write_bytes, 'somestr') - self.assertEqual((p / 'fileA').read_bytes(), b'abcdefg') - - def test_read_write_text(self): - p = self.cls(BASE) - (p / 'fileA').write_text('äbcdefg', encoding='latin-1') - self.assertEqual((p / 'fileA').read_text( - encoding='utf-8', errors='ignore'), 'bcdefg') - # Check that trying to write bytes does not truncate the file. - self.assertRaises(TypeError, (p / 'fileA').write_text, b'somebytes') - self.assertEqual((p / 'fileA').read_text(encoding='latin-1'), 'äbcdefg') - - def test_read_text_with_newlines(self): - p = self.cls(BASE) - # Check that `\n` character change nothing - (p / 'fileA').write_bytes(b'abcde\r\nfghlk\n\rmnopq') - self.assertEqual((p / 'fileA').read_text(newline='\n'), - 'abcde\r\nfghlk\n\rmnopq') - # Check that `\r` character replaces `\n` - (p / 'fileA').write_bytes(b'abcde\r\nfghlk\n\rmnopq') - self.assertEqual((p / 'fileA').read_text(newline='\r'), - 'abcde\r\nfghlk\n\rmnopq') - # Check that `\r\n` character replaces `\n` - (p / 'fileA').write_bytes(b'abcde\r\nfghlk\n\rmnopq') - self.assertEqual((p / 'fileA').read_text(newline='\r\n'), - 'abcde\r\nfghlk\n\rmnopq') - - def test_write_text_with_newlines(self): - p = self.cls(BASE) - # Check that `\n` character change nothing - (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\n') - self.assertEqual((p / 'fileA').read_bytes(), - b'abcde\r\nfghlk\n\rmnopq') - # Check that `\r` character replaces `\n` - (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\r') - self.assertEqual((p / 'fileA').read_bytes(), - b'abcde\r\rfghlk\r\rmnopq') - # Check that `\r\n` character replaces `\n` - (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\r\n') - self.assertEqual((p / 'fileA').read_bytes(), - b'abcde\r\r\nfghlk\r\n\rmnopq') - # Check that no argument passed will change `\n` to `os.linesep` - os_linesep_byte = bytes(os.linesep, encoding='ascii') - (p / 'fileA').write_text('abcde\nfghlk\n\rmnopq') - self.assertEqual((p / 'fileA').read_bytes(), - b'abcde' + os_linesep_byte + b'fghlk' + os_linesep_byte + b'\rmnopq') - - def test_iterdir(self): - P = self.cls - p = P(BASE) - it = p.iterdir() - paths = set(it) - expected = ['dirA', 'dirB', 'dirC', 'dirE', 'fileA'] - if self.can_symlink: - expected += ['linkA', 'linkB', 'brokenLink', 'brokenLinkLoop'] - self.assertEqual(paths, { P(BASE, q) for q in expected }) - - def test_iterdir_symlink(self): - if not self.can_symlink: - self.skipTest("symlinks required") - # __iter__ on a symlink to a directory. - P = self.cls - p = P(BASE, 'linkB') - paths = set(p.iterdir()) - expected = { P(BASE, 'linkB', q) for q in ['fileB', 'linkD'] } - self.assertEqual(paths, expected) - - def test_iterdir_nodir(self): - # __iter__ on something that is not a directory. - p = self.cls(BASE, 'fileA') - with self.assertRaises(OSError) as cm: - p.iterdir() - # ENOENT or EINVAL under Windows, ENOTDIR otherwise - # (see issue #12802). - self.assertIn(cm.exception.errno, (errno.ENOTDIR, - errno.ENOENT, errno.EINVAL)) - - def test_glob_common(self): - def _check(glob, expected): - self.assertEqual(set(glob), { P(BASE, q) for q in expected }) - P = self.cls - p = P(BASE) - it = p.glob("fileA") - self.assertIsInstance(it, collections.abc.Iterator) - _check(it, ["fileA"]) - _check(p.glob("fileB"), []) - _check(p.glob("dir*/file*"), ["dirB/fileB", "dirC/fileC"]) - if not self.can_symlink: - _check(p.glob("*A"), ['dirA', 'fileA']) - else: - _check(p.glob("*A"), ['dirA', 'fileA', 'linkA']) - if not self.can_symlink: - _check(p.glob("*B/*"), ['dirB/fileB']) - else: - _check(p.glob("*B/*"), ['dirB/fileB', 'dirB/linkD', - 'linkB/fileB', 'linkB/linkD']) - if not self.can_symlink: - _check(p.glob("*/fileB"), ['dirB/fileB']) - else: - _check(p.glob("*/fileB"), ['dirB/fileB', 'linkB/fileB']) - if self.can_symlink: - _check(p.glob("brokenLink"), ['brokenLink']) - - if not self.can_symlink: - _check(p.glob("*/"), ["dirA/", "dirB/", "dirC/", "dirE/"]) - else: - _check(p.glob("*/"), ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"]) - - def test_glob_empty_pattern(self): - p = self.cls() - with self.assertRaisesRegex(ValueError, 'Unacceptable pattern'): - list(p.glob('')) - - def test_glob_case_sensitive(self): - P = self.cls - def _check(path, pattern, case_sensitive, expected): - actual = {str(q) for q in path.glob(pattern, case_sensitive=case_sensitive)} - expected = {str(P(BASE, q)) for q in expected} - self.assertEqual(actual, expected) - path = P(BASE) - _check(path, "DIRB/FILE*", True, []) - _check(path, "DIRB/FILE*", False, ["dirB/fileB"]) - _check(path, "dirb/file*", True, []) - _check(path, "dirb/file*", False, ["dirB/fileB"]) - - def test_glob_follow_symlinks_common(self): - if not self.can_symlink: - self.skipTest("symlinks required") - def _check(path, glob, expected): - actual = {path for path in path.glob(glob, follow_symlinks=True) - if "linkD" not in path.parent.parts} # exclude symlink loop. - self.assertEqual(actual, { P(BASE, q) for q in expected }) - P = self.cls - p = P(BASE) - _check(p, "fileB", []) - _check(p, "dir*/file*", ["dirB/fileB", "dirC/fileC"]) - _check(p, "*A", ["dirA", "fileA", "linkA"]) - _check(p, "*B/*", ["dirB/fileB", "dirB/linkD", "linkB/fileB", "linkB/linkD"]) - _check(p, "*/fileB", ["dirB/fileB", "linkB/fileB"]) - _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"]) - _check(p, "dir*/*/..", ["dirC/dirD/..", "dirA/linkC/.."]) - _check(p, "dir*/**/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/", - "dirC/", "dirC/dirD/", "dirE/"]) - _check(p, "dir*/**/..", ["dirA/..", "dirA/linkC/..", "dirB/..", - "dirC/..", "dirC/dirD/..", "dirE/.."]) - _check(p, "dir*/*/**/", ["dirA/linkC/", "dirA/linkC/linkD/", "dirB/linkD/", "dirC/dirD/"]) - _check(p, "dir*/*/**/..", ["dirA/linkC/..", "dirC/dirD/.."]) - _check(p, "dir*/**/fileC", ["dirC/fileC"]) - _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"]) - _check(p, "*/dirD/**/", ["dirC/dirD/"]) - - def test_glob_no_follow_symlinks_common(self): - if not self.can_symlink: - self.skipTest("symlinks required") - def _check(path, glob, expected): - actual = {path for path in path.glob(glob, follow_symlinks=False)} - self.assertEqual(actual, { P(BASE, q) for q in expected }) - P = self.cls - p = P(BASE) - _check(p, "fileB", []) - _check(p, "dir*/file*", ["dirB/fileB", "dirC/fileC"]) - _check(p, "*A", ["dirA", "fileA", "linkA"]) - _check(p, "*B/*", ["dirB/fileB", "dirB/linkD"]) - _check(p, "*/fileB", ["dirB/fileB"]) - _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/"]) - _check(p, "dir*/*/..", ["dirC/dirD/.."]) - _check(p, "dir*/**/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"]) - _check(p, "dir*/**/..", ["dirA/..", "dirB/..", "dirC/..", "dirC/dirD/..", "dirE/.."]) - _check(p, "dir*/*/**/", ["dirC/dirD/"]) - _check(p, "dir*/*/**/..", ["dirC/dirD/.."]) - _check(p, "dir*/**/fileC", ["dirC/fileC"]) - _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"]) - _check(p, "*/dirD/**/", ["dirC/dirD/"]) - - def test_rglob_common(self): - def _check(glob, expected): - self.assertEqual(set(glob), {P(BASE, q) for q in expected}) - P = self.cls - p = P(BASE) - it = p.rglob("fileA") - self.assertIsInstance(it, collections.abc.Iterator) - _check(it, ["fileA"]) - _check(p.rglob("fileB"), ["dirB/fileB"]) - _check(p.rglob("**/fileB"), ["dirB/fileB"]) - _check(p.rglob("*/fileA"), []) - if not self.can_symlink: - _check(p.rglob("*/fileB"), ["dirB/fileB"]) - else: - _check(p.rglob("*/fileB"), ["dirB/fileB", "dirB/linkD/fileB", - "linkB/fileB", "dirA/linkC/fileB"]) - _check(p.rglob("file*"), ["fileA", "dirB/fileB", - "dirC/fileC", "dirC/dirD/fileD"]) - if not self.can_symlink: - _check(p.rglob("*/"), [ - "dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/", - ]) - else: - _check(p.rglob("*/"), [ - "dirA/", "dirA/linkC/", "dirB/", "dirB/linkD/", "dirC/", - "dirC/dirD/", "dirE/", "linkB/", - ]) - _check(p.rglob(""), ["./", "dirA/", "dirB/", "dirC/", "dirE/", "dirC/dirD/"]) - - p = P(BASE, "dirC") - _check(p.rglob("*"), ["dirC/fileC", "dirC/novel.txt", - "dirC/dirD", "dirC/dirD/fileD"]) - _check(p.rglob("file*"), ["dirC/fileC", "dirC/dirD/fileD"]) - _check(p.rglob("**/file*"), ["dirC/fileC", "dirC/dirD/fileD"]) - _check(p.rglob("dir*/**/"), ["dirC/dirD/"]) - _check(p.rglob("*/*"), ["dirC/dirD/fileD"]) - _check(p.rglob("*/"), ["dirC/dirD/"]) - _check(p.rglob(""), ["dirC/", "dirC/dirD/"]) - _check(p.rglob("**/"), ["dirC/", "dirC/dirD/"]) - # gh-91616, a re module regression - _check(p.rglob("*.txt"), ["dirC/novel.txt"]) - _check(p.rglob("*.*"), ["dirC/novel.txt"]) - - def test_rglob_follow_symlinks_common(self): - if not self.can_symlink: - self.skipTest("symlinks required") - def _check(path, glob, expected): - actual = {path for path in path.rglob(glob, follow_symlinks=True) - if 'linkD' not in path.parent.parts} # exclude symlink loop. - self.assertEqual(actual, { P(BASE, q) for q in expected }) - P = self.cls - p = P(BASE) - _check(p, "fileB", ["dirB/fileB", "dirA/linkC/fileB", "linkB/fileB"]) - _check(p, "*/fileA", []) - _check(p, "*/fileB", ["dirB/fileB", "dirA/linkC/fileB", "linkB/fileB"]) - _check(p, "file*", ["fileA", "dirA/linkC/fileB", "dirB/fileB", - "dirC/fileC", "dirC/dirD/fileD", "linkB/fileB"]) - _check(p, "*/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/", - "dirC/", "dirC/dirD/", "dirE/", "linkB/", "linkB/linkD/"]) - _check(p, "", ["./", "dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/", - "dirC/", "dirE/", "dirC/dirD/", "linkB/", "linkB/linkD/"]) - - p = P(BASE, "dirC") - _check(p, "*", ["dirC/fileC", "dirC/novel.txt", - "dirC/dirD", "dirC/dirD/fileD"]) - _check(p, "file*", ["dirC/fileC", "dirC/dirD/fileD"]) - _check(p, "*/*", ["dirC/dirD/fileD"]) - _check(p, "*/", ["dirC/dirD/"]) - _check(p, "", ["dirC/", "dirC/dirD/"]) - # gh-91616, a re module regression - _check(p, "*.txt", ["dirC/novel.txt"]) - _check(p, "*.*", ["dirC/novel.txt"]) - - def test_rglob_no_follow_symlinks_common(self): - if not self.can_symlink: - self.skipTest("symlinks required") - def _check(path, glob, expected): - actual = {path for path in path.rglob(glob, follow_symlinks=False)} - self.assertEqual(actual, { P(BASE, q) for q in expected }) - P = self.cls - p = P(BASE) - _check(p, "fileB", ["dirB/fileB"]) - _check(p, "*/fileA", []) - _check(p, "*/fileB", ["dirB/fileB"]) - _check(p, "file*", ["fileA", "dirB/fileB", "dirC/fileC", "dirC/dirD/fileD", ]) - _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"]) - _check(p, "", ["./", "dirA/", "dirB/", "dirC/", "dirE/", "dirC/dirD/"]) - - p = P(BASE, "dirC") - _check(p, "*", ["dirC/fileC", "dirC/novel.txt", - "dirC/dirD", "dirC/dirD/fileD"]) - _check(p, "file*", ["dirC/fileC", "dirC/dirD/fileD"]) - _check(p, "*/*", ["dirC/dirD/fileD"]) - _check(p, "*/", ["dirC/dirD/"]) - _check(p, "", ["dirC/", "dirC/dirD/"]) - # gh-91616, a re module regression - _check(p, "*.txt", ["dirC/novel.txt"]) - _check(p, "*.*", ["dirC/novel.txt"]) - - def test_rglob_symlink_loop(self): - # Don't get fooled by symlink loops (Issue #26012). - if not self.can_symlink: - self.skipTest("symlinks required") - P = self.cls - p = P(BASE) - given = set(p.rglob('*')) - expect = {'brokenLink', - 'dirA', 'dirA/linkC', - 'dirB', 'dirB/fileB', 'dirB/linkD', - 'dirC', 'dirC/dirD', 'dirC/dirD/fileD', - 'dirC/fileC', 'dirC/novel.txt', - 'dirE', - 'fileA', - 'linkA', - 'linkB', - 'brokenLinkLoop', - } - self.assertEqual(given, {p / x for x in expect}) - - def test_glob_many_open_files(self): - depth = 30 - P = self.cls - p = base = P(BASE) / 'deep' - p.mkdir() - for _ in range(depth): - p /= 'd' - p.mkdir() - pattern = '/'.join(['*'] * depth) - iters = [base.glob(pattern) for j in range(100)] - for it in iters: - self.assertEqual(next(it), p) - iters = [base.rglob('d') for j in range(100)] - p = base - for i in range(depth): - p = p / 'd' - for it in iters: - self.assertEqual(next(it), p) - - def test_glob_dotdot(self): - # ".." is not special in globs. - P = self.cls - p = P(BASE) - self.assertEqual(set(p.glob("..")), { P(BASE, "..") }) - self.assertEqual(set(p.glob("../..")), { P(BASE, "..", "..") }) - self.assertEqual(set(p.glob("dirA/..")), { P(BASE, "dirA", "..") }) - self.assertEqual(set(p.glob("dirA/../file*")), { P(BASE, "dirA/../fileA") }) - self.assertEqual(set(p.glob("dirA/../file*/..")), set()) - self.assertEqual(set(p.glob("../xyzzy")), set()) - self.assertEqual(set(p.glob("xyzzy/..")), set()) - self.assertEqual(set(p.glob("/".join([".."] * 50))), { P(BASE, *[".."] * 50)}) - - def test_glob_permissions(self): - # See bpo-38894 - if not self.can_symlink: - self.skipTest("symlinks required") - P = self.cls - base = P(BASE) / 'permissions' - base.mkdir() - - for i in range(100): - link = base / f"link{i}" - if i % 2: - link.symlink_to(P(BASE, "dirE", "nonexistent")) - else: - link.symlink_to(P(BASE, "dirC")) - - self.assertEqual(len(set(base.glob("*"))), 100) - self.assertEqual(len(set(base.glob("*/"))), 50) - self.assertEqual(len(set(base.glob("*/fileC"))), 50) - self.assertEqual(len(set(base.glob("*/file*"))), 50) - - def test_glob_long_symlink(self): - # See gh-87695 - if not self.can_symlink: - self.skipTest("symlinks required") - base = self.cls(BASE) / 'long_symlink' - base.mkdir() - bad_link = base / 'bad_link' - bad_link.symlink_to("bad" * 200) - self.assertEqual(sorted(base.glob('**/*')), [bad_link]) - - def test_glob_above_recursion_limit(self): - recursion_limit = 50 - # directory_depth > recursion_limit - directory_depth = recursion_limit + 10 - base = self.cls(BASE, 'deep') - path = base.joinpath(*(['d'] * directory_depth)) - path.mkdir(parents=True) - - with set_recursion_limit(recursion_limit): - list(base.glob('**/')) - - def test_glob_recursive_no_trailing_slash(self): - P = self.cls - p = P(BASE) - with self.assertWarns(FutureWarning): - p.glob('**') - with self.assertWarns(FutureWarning): - p.glob('*/**') - with self.assertWarns(FutureWarning): - p.rglob('**') - with self.assertWarns(FutureWarning): - p.rglob('*/**') - - - def test_readlink(self): - if not self.can_symlink: - self.skipTest("symlinks required") - P = self.cls(BASE) - self.assertEqual((P / 'linkA').readlink(), self.cls('fileA')) - self.assertEqual((P / 'brokenLink').readlink(), - self.cls('non-existing')) - self.assertEqual((P / 'linkB').readlink(), self.cls('dirB')) - self.assertEqual((P / 'linkB' / 'linkD').readlink(), self.cls('../dirB')) - with self.assertRaises(OSError): - (P / 'fileA').readlink() - - @unittest.skipIf(hasattr(os, "readlink"), "os.readlink() is present") - def test_readlink_unsupported(self): - P = self.cls(BASE) - p = P / 'fileA' - with self.assertRaises(pathlib.UnsupportedOperation): - q.readlink(p) - - def _check_resolve(self, p, expected, strict=True): - q = p.resolve(strict) - self.assertEqual(q, expected) - - # This can be used to check both relative and absolute resolutions. - _check_resolve_relative = _check_resolve_absolute = _check_resolve - - def test_resolve_common(self): - if not self.can_symlink: - self.skipTest("symlinks required") - P = self.cls - p = P(BASE, 'foo') - with self.assertRaises(OSError) as cm: - p.resolve(strict=True) - self.assertEqual(cm.exception.errno, errno.ENOENT) - # Non-strict - self.assertEqualNormCase(str(p.resolve(strict=False)), - os.path.join(BASE, 'foo')) - p = P(BASE, 'foo', 'in', 'spam') - self.assertEqualNormCase(str(p.resolve(strict=False)), - os.path.join(BASE, 'foo', 'in', 'spam')) - p = P(BASE, '..', 'foo', 'in', 'spam') - self.assertEqualNormCase(str(p.resolve(strict=False)), - os.path.abspath(os.path.join('foo', 'in', 'spam'))) - # These are all relative symlinks. - p = P(BASE, 'dirB', 'fileB') - self._check_resolve_relative(p, p) - p = P(BASE, 'linkA') - self._check_resolve_relative(p, P(BASE, 'fileA')) - p = P(BASE, 'dirA', 'linkC', 'fileB') - self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB')) - p = P(BASE, 'dirB', 'linkD', 'fileB') - self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB')) - # Non-strict - p = P(BASE, 'dirA', 'linkC', 'fileB', 'foo', 'in', 'spam') - self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB', 'foo', 'in', - 'spam'), False) - p = P(BASE, 'dirA', 'linkC', '..', 'foo', 'in', 'spam') - if os.name == 'nt' and isinstance(p, pathlib.Path): - # In Windows, if linkY points to dirB, 'dirA\linkY\..' - # resolves to 'dirA' without resolving linkY first. - self._check_resolve_relative(p, P(BASE, 'dirA', 'foo', 'in', - 'spam'), False) - else: - # In Posix, if linkY points to dirB, 'dirA/linkY/..' - # resolves to 'dirB/..' first before resolving to parent of dirB. - self._check_resolve_relative(p, P(BASE, 'foo', 'in', 'spam'), False) - # Now create absolute symlinks. - d = self.tempdir() - P(BASE, 'dirA', 'linkX').symlink_to(d) - P(BASE, str(d), 'linkY').symlink_to(join('dirB')) - p = P(BASE, 'dirA', 'linkX', 'linkY', 'fileB') - self._check_resolve_absolute(p, P(BASE, 'dirB', 'fileB')) - # Non-strict - p = P(BASE, 'dirA', 'linkX', 'linkY', 'foo', 'in', 'spam') - self._check_resolve_relative(p, P(BASE, 'dirB', 'foo', 'in', 'spam'), - False) - p = P(BASE, 'dirA', 'linkX', 'linkY', '..', 'foo', 'in', 'spam') - if os.name == 'nt' and isinstance(p, pathlib.Path): - # In Windows, if linkY points to dirB, 'dirA\linkY\..' - # resolves to 'dirA' without resolving linkY first. - self._check_resolve_relative(p, P(d, 'foo', 'in', 'spam'), False) - else: - # In Posix, if linkY points to dirB, 'dirA/linkY/..' - # resolves to 'dirB/..' first before resolving to parent of dirB. - self._check_resolve_relative(p, P(BASE, 'foo', 'in', 'spam'), False) - - def test_resolve_dot(self): - # See http://web.archive.org/web/20200623062557/https://bitbucket.org/pitrou/pathlib/issues/9/ - if not self.can_symlink: - self.skipTest("symlinks required") - p = self.cls(BASE) - p.joinpath('0').symlink_to('.', target_is_directory=True) - p.joinpath('1').symlink_to(os.path.join('0', '0'), target_is_directory=True) - p.joinpath('2').symlink_to(os.path.join('1', '1'), target_is_directory=True) - q = p / '2' - self.assertEqual(q.resolve(strict=True), p) - r = q / '3' / '4' - self.assertRaises(FileNotFoundError, r.resolve, strict=True) - # Non-strict - self.assertEqual(r.resolve(strict=False), p / '3' / '4') - - def _check_symlink_loop(self, *args): - path = self.cls(*args) - with self.assertRaises(OSError) as cm: - path.resolve(strict=True) - self.assertEqual(cm.exception.errno, errno.ELOOP) - - def test_resolve_loop(self): - if not self.can_symlink: - self.skipTest("symlinks required") - if os.name == 'nt' and issubclass(self.cls, pathlib.Path): - self.skipTest("symlink loops work differently with concrete Windows paths") - # Loops with relative symlinks. - self.cls(BASE, 'linkX').symlink_to('linkX/inside') - self._check_symlink_loop(BASE, 'linkX') - self.cls(BASE, 'linkY').symlink_to('linkY') - self._check_symlink_loop(BASE, 'linkY') - self.cls(BASE, 'linkZ').symlink_to('linkZ/../linkZ') - self._check_symlink_loop(BASE, 'linkZ') - # Non-strict - p = self.cls(BASE, 'linkZ', 'foo') - self.assertEqual(p.resolve(strict=False), p) - # Loops with absolute symlinks. - self.cls(BASE, 'linkU').symlink_to(join('linkU/inside')) - self._check_symlink_loop(BASE, 'linkU') - self.cls(BASE, 'linkV').symlink_to(join('linkV')) - self._check_symlink_loop(BASE, 'linkV') - self.cls(BASE, 'linkW').symlink_to(join('linkW/../linkW')) - self._check_symlink_loop(BASE, 'linkW') - # Non-strict - q = self.cls(BASE, 'linkW', 'foo') - self.assertEqual(q.resolve(strict=False), q) - - def test_stat(self): - statA = self.cls(BASE).joinpath('fileA').stat() - statB = self.cls(BASE).joinpath('dirB', 'fileB').stat() - statC = self.cls(BASE).joinpath('dirC').stat() - # st_mode: files are the same, directory differs. - self.assertIsInstance(statA.st_mode, int) - self.assertEqual(statA.st_mode, statB.st_mode) - self.assertNotEqual(statA.st_mode, statC.st_mode) - self.assertNotEqual(statB.st_mode, statC.st_mode) - # st_ino: all different, - self.assertIsInstance(statA.st_ino, int) - self.assertNotEqual(statA.st_ino, statB.st_ino) - self.assertNotEqual(statA.st_ino, statC.st_ino) - self.assertNotEqual(statB.st_ino, statC.st_ino) - # st_dev: all the same. - self.assertIsInstance(statA.st_dev, int) - self.assertEqual(statA.st_dev, statB.st_dev) - self.assertEqual(statA.st_dev, statC.st_dev) - # other attributes not used by pathlib. - - def test_stat_no_follow_symlinks(self): - if not self.can_symlink: - self.skipTest("symlinks required") - p = self.cls(BASE) / 'linkA' - st = p.stat() - self.assertNotEqual(st, p.stat(follow_symlinks=False)) - - def test_stat_no_follow_symlinks_nosymlink(self): - p = self.cls(BASE) / 'fileA' - st = p.stat() - self.assertEqual(st, p.stat(follow_symlinks=False)) - - def test_lstat(self): - if not self.can_symlink: - self.skipTest("symlinks required") - p = self.cls(BASE)/ 'linkA' - st = p.stat() - self.assertNotEqual(st, p.lstat()) - - def test_lstat_nosymlink(self): - p = self.cls(BASE) / 'fileA' - st = p.stat() - self.assertEqual(st, p.lstat()) - - def test_is_dir(self): - P = self.cls(BASE) - self.assertTrue((P / 'dirA').is_dir()) - self.assertFalse((P / 'fileA').is_dir()) - self.assertFalse((P / 'non-existing').is_dir()) - self.assertFalse((P / 'fileA' / 'bah').is_dir()) - if self.can_symlink: - self.assertFalse((P / 'linkA').is_dir()) - self.assertTrue((P / 'linkB').is_dir()) - self.assertFalse((P/ 'brokenLink').is_dir()) - self.assertFalse((P / 'dirA\udfff').is_dir()) - self.assertFalse((P / 'dirA\x00').is_dir()) - - def test_is_dir_no_follow_symlinks(self): - P = self.cls(BASE) - self.assertTrue((P / 'dirA').is_dir(follow_symlinks=False)) - self.assertFalse((P / 'fileA').is_dir(follow_symlinks=False)) - self.assertFalse((P / 'non-existing').is_dir(follow_symlinks=False)) - self.assertFalse((P / 'fileA' / 'bah').is_dir(follow_symlinks=False)) - if self.can_symlink: - self.assertFalse((P / 'linkA').is_dir(follow_symlinks=False)) - self.assertFalse((P / 'linkB').is_dir(follow_symlinks=False)) - self.assertFalse((P/ 'brokenLink').is_dir(follow_symlinks=False)) - self.assertFalse((P / 'dirA\udfff').is_dir(follow_symlinks=False)) - self.assertFalse((P / 'dirA\x00').is_dir(follow_symlinks=False)) - - def test_is_file(self): - P = self.cls(BASE) - self.assertTrue((P / 'fileA').is_file()) - self.assertFalse((P / 'dirA').is_file()) - self.assertFalse((P / 'non-existing').is_file()) - self.assertFalse((P / 'fileA' / 'bah').is_file()) - if self.can_symlink: - self.assertTrue((P / 'linkA').is_file()) - self.assertFalse((P / 'linkB').is_file()) - self.assertFalse((P/ 'brokenLink').is_file()) - self.assertFalse((P / 'fileA\udfff').is_file()) - self.assertFalse((P / 'fileA\x00').is_file()) - - def test_is_file_no_follow_symlinks(self): - P = self.cls(BASE) - self.assertTrue((P / 'fileA').is_file(follow_symlinks=False)) - self.assertFalse((P / 'dirA').is_file(follow_symlinks=False)) - self.assertFalse((P / 'non-existing').is_file(follow_symlinks=False)) - self.assertFalse((P / 'fileA' / 'bah').is_file(follow_symlinks=False)) - if self.can_symlink: - self.assertFalse((P / 'linkA').is_file(follow_symlinks=False)) - self.assertFalse((P / 'linkB').is_file(follow_symlinks=False)) - self.assertFalse((P/ 'brokenLink').is_file(follow_symlinks=False)) - self.assertFalse((P / 'fileA\udfff').is_file(follow_symlinks=False)) - self.assertFalse((P / 'fileA\x00').is_file(follow_symlinks=False)) - - def test_is_mount(self): - P = self.cls(BASE) - self.assertFalse((P / 'fileA').is_mount()) - self.assertFalse((P / 'dirA').is_mount()) - self.assertFalse((P / 'non-existing').is_mount()) - self.assertFalse((P / 'fileA' / 'bah').is_mount()) - if self.can_symlink: - self.assertFalse((P / 'linkA').is_mount()) - - def test_is_symlink(self): - P = self.cls(BASE) - self.assertFalse((P / 'fileA').is_symlink()) - self.assertFalse((P / 'dirA').is_symlink()) - self.assertFalse((P / 'non-existing').is_symlink()) - self.assertFalse((P / 'fileA' / 'bah').is_symlink()) - if self.can_symlink: - self.assertTrue((P / 'linkA').is_symlink()) - self.assertTrue((P / 'linkB').is_symlink()) - self.assertTrue((P/ 'brokenLink').is_symlink()) - self.assertIs((P / 'fileA\udfff').is_file(), False) - self.assertIs((P / 'fileA\x00').is_file(), False) - if self.can_symlink: - self.assertIs((P / 'linkA\udfff').is_file(), False) - self.assertIs((P / 'linkA\x00').is_file(), False) - - def test_is_junction_false(self): - P = self.cls(BASE) - self.assertFalse((P / 'fileA').is_junction()) - self.assertFalse((P / 'dirA').is_junction()) - self.assertFalse((P / 'non-existing').is_junction()) - self.assertFalse((P / 'fileA' / 'bah').is_junction()) - self.assertFalse((P / 'fileA\udfff').is_junction()) - self.assertFalse((P / 'fileA\x00').is_junction()) - - def test_is_fifo_false(self): - P = self.cls(BASE) - self.assertFalse((P / 'fileA').is_fifo()) - self.assertFalse((P / 'dirA').is_fifo()) - self.assertFalse((P / 'non-existing').is_fifo()) - self.assertFalse((P / 'fileA' / 'bah').is_fifo()) - self.assertIs((P / 'fileA\udfff').is_fifo(), False) - self.assertIs((P / 'fileA\x00').is_fifo(), False) - - def test_is_socket_false(self): - P = self.cls(BASE) - self.assertFalse((P / 'fileA').is_socket()) - self.assertFalse((P / 'dirA').is_socket()) - self.assertFalse((P / 'non-existing').is_socket()) - self.assertFalse((P / 'fileA' / 'bah').is_socket()) - self.assertIs((P / 'fileA\udfff').is_socket(), False) - self.assertIs((P / 'fileA\x00').is_socket(), False) - - def test_is_block_device_false(self): - P = self.cls(BASE) - self.assertFalse((P / 'fileA').is_block_device()) - self.assertFalse((P / 'dirA').is_block_device()) - self.assertFalse((P / 'non-existing').is_block_device()) - self.assertFalse((P / 'fileA' / 'bah').is_block_device()) - self.assertIs((P / 'fileA\udfff').is_block_device(), False) - self.assertIs((P / 'fileA\x00').is_block_device(), False) - - def test_is_char_device_false(self): - P = self.cls(BASE) - self.assertFalse((P / 'fileA').is_char_device()) - self.assertFalse((P / 'dirA').is_char_device()) - self.assertFalse((P / 'non-existing').is_char_device()) - self.assertFalse((P / 'fileA' / 'bah').is_char_device()) - self.assertIs((P / 'fileA\udfff').is_char_device(), False) - self.assertIs((P / 'fileA\x00').is_char_device(), False) - - def test_pickling_common(self): - p = self.cls(BASE, 'fileA') - for proto in range(0, pickle.HIGHEST_PROTOCOL + 1): - dumped = pickle.dumps(p, proto) - pp = pickle.loads(dumped) - self.assertEqual(pp.stat(), p.stat()) - - def test_parts_interning(self): - P = self.cls - p = P('/usr/bin/foo') - q = P('/usr/local/bin') - # 'usr' - self.assertIs(p.parts[1], q.parts[1]) - # 'bin' - self.assertIs(p.parts[2], q.parts[3]) - - def _check_complex_symlinks(self, link0_target): - if not self.can_symlink: - self.skipTest("symlinks required") - - # Test solving a non-looping chain of symlinks (issue #19887). - P = self.cls(BASE) - P.joinpath('link1').symlink_to(os.path.join('link0', 'link0'), target_is_directory=True) - P.joinpath('link2').symlink_to(os.path.join('link1', 'link1'), target_is_directory=True) - P.joinpath('link3').symlink_to(os.path.join('link2', 'link2'), target_is_directory=True) - P.joinpath('link0').symlink_to(link0_target, target_is_directory=True) - - # Resolve absolute paths. - p = (P / 'link0').resolve() - self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) - p = (P / 'link1').resolve() - self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) - p = (P / 'link2').resolve() - self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) - p = (P / 'link3').resolve() - self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) - - # Resolve relative paths. - try: - self.cls().absolute() - except pathlib.UnsupportedOperation: - return - old_path = os.getcwd() - os.chdir(BASE) - try: - p = self.cls('link0').resolve() - self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) - p = self.cls('link1').resolve() - self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) - p = self.cls('link2').resolve() - self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) - p = self.cls('link3').resolve() - self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) - finally: - os.chdir(old_path) - - def test_complex_symlinks_absolute(self): - self._check_complex_symlinks(BASE) - - def test_complex_symlinks_relative(self): - self._check_complex_symlinks('.') - - def test_complex_symlinks_relative_dot_dot(self): - self._check_complex_symlinks(os.path.join('dirA', '..')) - - def setUpWalk(self): - # Build: - # TESTFN/ - # TEST1/ a file kid and two directory kids - # tmp1 - # SUB1/ a file kid and a directory kid - # tmp2 - # SUB11/ no kids - # SUB2/ a file kid and a dirsymlink kid - # tmp3 - # link/ a symlink to TEST2 - # broken_link - # broken_link2 - # TEST2/ - # tmp4 a lone file - self.walk_path = self.cls(BASE, "TEST1") - self.sub1_path = self.walk_path / "SUB1" - self.sub11_path = self.sub1_path / "SUB11" - self.sub2_path = self.walk_path / "SUB2" - tmp1_path = self.walk_path / "tmp1" - tmp2_path = self.sub1_path / "tmp2" - tmp3_path = self.sub2_path / "tmp3" - self.link_path = self.sub2_path / "link" - t2_path = self.cls(BASE, "TEST2") - tmp4_path = self.cls(BASE, "TEST2", "tmp4") - broken_link_path = self.sub2_path / "broken_link" - broken_link2_path = self.sub2_path / "broken_link2" - - self.sub11_path.mkdir(parents=True) - self.sub2_path.mkdir(parents=True) - t2_path.mkdir(parents=True) - - for path in tmp1_path, tmp2_path, tmp3_path, tmp4_path: - with path.open("w", encoding='utf-8') as f: - f.write(f"I'm {path} and proud of it. Blame test_pathlib.\n") - - if self.can_symlink: - self.link_path.symlink_to(t2_path) - broken_link_path.symlink_to('broken') - broken_link2_path.symlink_to(self.cls('tmp3', 'broken')) - self.sub2_tree = (self.sub2_path, [], ["broken_link", "broken_link2", "link", "tmp3"]) - else: - self.sub2_tree = (self.sub2_path, [], ["tmp3"]) - - def test_walk_topdown(self): - self.setUpWalk() - walker = self.walk_path.walk() - entry = next(walker) - entry[1].sort() # Ensure we visit SUB1 before SUB2 - self.assertEqual(entry, (self.walk_path, ["SUB1", "SUB2"], ["tmp1"])) - entry = next(walker) - self.assertEqual(entry, (self.sub1_path, ["SUB11"], ["tmp2"])) - entry = next(walker) - self.assertEqual(entry, (self.sub11_path, [], [])) - entry = next(walker) - entry[1].sort() - entry[2].sort() - self.assertEqual(entry, self.sub2_tree) - with self.assertRaises(StopIteration): - next(walker) - - def test_walk_prune(self): - self.setUpWalk() - # Prune the search. - all = [] - for root, dirs, files in self.walk_path.walk(): - all.append((root, dirs, files)) - if 'SUB1' in dirs: - # Note that this also mutates the dirs we appended to all! - dirs.remove('SUB1') - - self.assertEqual(len(all), 2) - self.assertEqual(all[0], (self.walk_path, ["SUB2"], ["tmp1"])) - - all[1][-1].sort() - all[1][1].sort() - self.assertEqual(all[1], self.sub2_tree) - - def test_walk_bottom_up(self): - self.setUpWalk() - seen_testfn = seen_sub1 = seen_sub11 = seen_sub2 = False - for path, dirnames, filenames in self.walk_path.walk(top_down=False): - if path == self.walk_path: - self.assertFalse(seen_testfn) - self.assertTrue(seen_sub1) - self.assertTrue(seen_sub2) - self.assertEqual(sorted(dirnames), ["SUB1", "SUB2"]) - self.assertEqual(filenames, ["tmp1"]) - seen_testfn = True - elif path == self.sub1_path: - self.assertFalse(seen_testfn) - self.assertFalse(seen_sub1) - self.assertTrue(seen_sub11) - self.assertEqual(dirnames, ["SUB11"]) - self.assertEqual(filenames, ["tmp2"]) - seen_sub1 = True - elif path == self.sub11_path: - self.assertFalse(seen_sub1) - self.assertFalse(seen_sub11) - self.assertEqual(dirnames, []) - self.assertEqual(filenames, []) - seen_sub11 = True - elif path == self.sub2_path: - self.assertFalse(seen_testfn) - self.assertFalse(seen_sub2) - self.assertEqual(sorted(dirnames), sorted(self.sub2_tree[1])) - self.assertEqual(sorted(filenames), sorted(self.sub2_tree[2])) - seen_sub2 = True - else: - raise AssertionError(f"Unexpected path: {path}") - self.assertTrue(seen_testfn) - - def test_walk_follow_symlinks(self): - if not self.can_symlink: - self.skipTest("symlinks required") - self.setUpWalk() - walk_it = self.walk_path.walk(follow_symlinks=True) - for root, dirs, files in walk_it: - if root == self.link_path: - self.assertEqual(dirs, []) - self.assertEqual(files, ["tmp4"]) - break - else: - self.fail("Didn't follow symlink with follow_symlinks=True") - - def test_walk_symlink_location(self): - if not self.can_symlink: - self.skipTest("symlinks required") - self.setUpWalk() - # Tests whether symlinks end up in filenames or dirnames depending - # on the `follow_symlinks` argument. - walk_it = self.walk_path.walk(follow_symlinks=False) - for root, dirs, files in walk_it: - if root == self.sub2_path: - self.assertIn("link", files) - break - else: - self.fail("symlink not found") - - walk_it = self.walk_path.walk(follow_symlinks=True) - for root, dirs, files in walk_it: - if root == self.sub2_path: - self.assertIn("link", dirs) - break - else: - self.fail("symlink not found") - - def test_walk_above_recursion_limit(self): - recursion_limit = 40 - # directory_depth > recursion_limit - directory_depth = recursion_limit + 10 - base = self.cls(BASE, 'deep') - path = base.joinpath(*(['d'] * directory_depth)) - path.mkdir(parents=True) - - with set_recursion_limit(recursion_limit): - list(base.walk()) - list(base.walk(top_down=False)) - -class DummyPathWithSymlinks(DummyPath): - def readlink(self): - path = str(self.parent.resolve() / self.name) - if path in self._symlinks: - return self.with_segments(self._symlinks[path]) - elif path in self._files or path in self._directories: - raise OSError(errno.EINVAL, "Not a symlink", path) - else: - raise FileNotFoundError(errno.ENOENT, "File not found", path) - - def symlink_to(self, target, target_is_directory=False): - self._directories[str(self.parent)].add(self.name) - self._symlinks[str(self)] = str(target) - - -class DummyPathWithSymlinksTest(DummyPathTest): - cls = DummyPathWithSymlinks - can_symlink = True - - # # Tests for the concrete classes. # -class PathTest(DummyPathTest, PurePathTest): +class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest): """Tests for the FS-accessing functionalities of the Path classes.""" cls = pathlib.Path can_symlink = os_helper.can_symlink() diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py new file mode 100644 index 00000000000000..61ed3cb6a4a7f8 --- /dev/null +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -0,0 +1,1918 @@ +import collections.abc +import io +import os +import errno +import pathlib +import pickle +import posixpath +import stat +import unittest + +from test.support import set_recursion_limit +from test.support.os_helper import TESTFN + + +class UnsupportedOperationTest(unittest.TestCase): + def test_is_notimplemented(self): + self.assertTrue(issubclass(pathlib.UnsupportedOperation, NotImplementedError)) + self.assertTrue(isinstance(pathlib.UnsupportedOperation(), NotImplementedError)) + + +# Make sure any symbolic links in the base test path are resolved. +BASE = os.path.realpath(TESTFN) +join = lambda *x: os.path.join(BASE, *x) + +only_nt = unittest.skipIf(os.name != 'nt', + 'test requires a Windows-compatible system') +only_posix = unittest.skipIf(os.name == 'nt', + 'test requires a POSIX-compatible system') + + +# +# Tests for the pure classes. +# + + +class PurePathBaseTest(unittest.TestCase): + cls = pathlib._abc.PurePathBase + + def test_magic_methods(self): + P = self.cls + self.assertFalse(hasattr(P, '__fspath__')) + self.assertFalse(hasattr(P, '__bytes__')) + self.assertIs(P.__reduce__, object.__reduce__) + self.assertIs(P.__hash__, object.__hash__) + self.assertIs(P.__eq__, object.__eq__) + self.assertIs(P.__lt__, object.__lt__) + self.assertIs(P.__le__, object.__le__) + self.assertIs(P.__gt__, object.__gt__) + self.assertIs(P.__ge__, object.__ge__) + + +class DummyPurePath(pathlib._abc.PurePathBase): + def __eq__(self, other): + if not isinstance(other, DummyPurePath): + return NotImplemented + return str(self) == str(other) + + def __hash__(self): + return hash(str(self)) + + +class DummyPurePathTest(unittest.TestCase): + cls = DummyPurePath + + # Keys are canonical paths, values are list of tuples of arguments + # supposed to produce equal paths. + equivalences = { + 'a/b': [ + ('a', 'b'), ('a/', 'b'), ('a', 'b/'), ('a/', 'b/'), + ('a/b/',), ('a//b',), ('a//b//',), + # Empty components get removed. + ('', 'a', 'b'), ('a', '', 'b'), ('a', 'b', ''), + ], + '/b/c/d': [ + ('a', '/b/c', 'd'), ('/a', '/b/c', 'd'), + # Empty components get removed. + ('/', 'b', '', 'c/d'), ('/', '', 'b/c/d'), ('', '/b/c/d'), + ], + } + + def setUp(self): + p = self.cls('a') + self.pathmod = p.pathmod + self.sep = self.pathmod.sep + self.altsep = self.pathmod.altsep + + def test_constructor_common(self): + P = self.cls + p = P('a') + self.assertIsInstance(p, P) + P('a', 'b', 'c') + P('/a', 'b', 'c') + P('a/b/c') + P('/a/b/c') + + def test_concrete_class(self): + if self.cls is pathlib.PurePath: + expected = pathlib.PureWindowsPath if os.name == 'nt' else pathlib.PurePosixPath + else: + expected = self.cls + p = self.cls('a') + self.assertIs(type(p), expected) + + def test_different_pathmods_unequal(self): + p = self.cls('a') + if p.pathmod is posixpath: + q = pathlib.PureWindowsPath('a') + else: + q = pathlib.PurePosixPath('a') + self.assertNotEqual(p, q) + + def test_different_pathmods_unordered(self): + p = self.cls('a') + if p.pathmod is posixpath: + q = pathlib.PureWindowsPath('a') + else: + q = pathlib.PurePosixPath('a') + with self.assertRaises(TypeError): + p < q + with self.assertRaises(TypeError): + p <= q + with self.assertRaises(TypeError): + p > q + with self.assertRaises(TypeError): + p >= q + + def _check_str_subclass(self, *args): + # Issue #21127: it should be possible to construct a PurePath object + # from a str subclass instance, and it then gets converted to + # a pure str object. + class StrSubclass(str): + pass + P = self.cls + p = P(*(StrSubclass(x) for x in args)) + self.assertEqual(p, P(*args)) + for part in p.parts: + self.assertIs(type(part), str) + + def test_str_subclass_common(self): + self._check_str_subclass('') + self._check_str_subclass('.') + self._check_str_subclass('a') + self._check_str_subclass('a/b.txt') + self._check_str_subclass('/a/b.txt') + + def test_with_segments_common(self): + class P(self.cls): + def __init__(self, *pathsegments, session_id): + super().__init__(*pathsegments) + self.session_id = session_id + + def with_segments(self, *pathsegments): + return type(self)(*pathsegments, session_id=self.session_id) + p = P('foo', 'bar', session_id=42) + self.assertEqual(42, (p / 'foo').session_id) + self.assertEqual(42, ('foo' / p).session_id) + self.assertEqual(42, p.joinpath('foo').session_id) + self.assertEqual(42, p.with_name('foo').session_id) + self.assertEqual(42, p.with_stem('foo').session_id) + self.assertEqual(42, p.with_suffix('.foo').session_id) + self.assertEqual(42, p.with_segments('foo').session_id) + self.assertEqual(42, p.relative_to('foo').session_id) + self.assertEqual(42, p.parent.session_id) + for parent in p.parents: + self.assertEqual(42, parent.session_id) + + def _check_parse_path(self, raw_path, *expected): + sep = self.pathmod.sep + actual = self.cls._parse_path(raw_path.replace('/', sep)) + self.assertEqual(actual, expected) + if altsep := self.pathmod.altsep: + actual = self.cls._parse_path(raw_path.replace('/', altsep)) + self.assertEqual(actual, expected) + + def test_parse_path_common(self): + check = self._check_parse_path + sep = self.pathmod.sep + check('', '', '', []) + check('a', '', '', ['a']) + check('a/', '', '', ['a']) + check('a/b', '', '', ['a', 'b']) + check('a/b/', '', '', ['a', 'b']) + check('a/b/c/d', '', '', ['a', 'b', 'c', 'd']) + check('a/b//c/d', '', '', ['a', 'b', 'c', 'd']) + check('a/b/c/d', '', '', ['a', 'b', 'c', 'd']) + check('.', '', '', []) + check('././b', '', '', ['b']) + check('a/./b', '', '', ['a', 'b']) + check('a/./.', '', '', ['a']) + check('/a/b', '', sep, ['a', 'b']) + + def test_join_common(self): + P = self.cls + p = P('a/b') + pp = p.joinpath('c') + self.assertEqual(pp, P('a/b/c')) + self.assertIs(type(pp), type(p)) + pp = p.joinpath('c', 'd') + self.assertEqual(pp, P('a/b/c/d')) + pp = p.joinpath('/c') + self.assertEqual(pp, P('/c')) + + def test_div_common(self): + # Basically the same as joinpath(). + P = self.cls + p = P('a/b') + pp = p / 'c' + self.assertEqual(pp, P('a/b/c')) + self.assertIs(type(pp), type(p)) + pp = p / 'c/d' + self.assertEqual(pp, P('a/b/c/d')) + pp = p / 'c' / 'd' + self.assertEqual(pp, P('a/b/c/d')) + pp = 'c' / p / 'd' + self.assertEqual(pp, P('c/a/b/d')) + pp = p/ '/c' + self.assertEqual(pp, P('/c')) + + def _check_str(self, expected, args): + p = self.cls(*args) + self.assertEqual(str(p), expected.replace('/', self.sep)) + + def test_str_common(self): + # Canonicalized paths roundtrip. + for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'): + self._check_str(pathstr, (pathstr,)) + # Special case for the empty path. + self._check_str('.', ('',)) + # Other tests for str() are in test_equivalences(). + + def test_as_posix_common(self): + P = self.cls + for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'): + self.assertEqual(P(pathstr).as_posix(), pathstr) + # Other tests for as_posix() are in test_equivalences(). + + def test_repr_common(self): + for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'): + with self.subTest(pathstr=pathstr): + p = self.cls(pathstr) + clsname = p.__class__.__name__ + r = repr(p) + # The repr() is in the form ClassName("forward-slashes path"). + self.assertTrue(r.startswith(clsname + '('), r) + self.assertTrue(r.endswith(')'), r) + inner = r[len(clsname) + 1 : -1] + self.assertEqual(eval(inner), p.as_posix()) + + def test_eq_common(self): + P = self.cls + self.assertEqual(P('a/b'), P('a/b')) + self.assertEqual(P('a/b'), P('a', 'b')) + self.assertNotEqual(P('a/b'), P('a')) + self.assertNotEqual(P('a/b'), P('/a/b')) + self.assertNotEqual(P('a/b'), P()) + self.assertNotEqual(P('/a/b'), P('/')) + self.assertNotEqual(P(), P('/')) + self.assertNotEqual(P(), "") + self.assertNotEqual(P(), {}) + self.assertNotEqual(P(), int) + + def test_match_common(self): + P = self.cls + self.assertRaises(ValueError, P('a').match, '') + self.assertRaises(ValueError, P('a').match, '.') + # Simple relative pattern. + self.assertTrue(P('b.py').match('b.py')) + self.assertTrue(P('a/b.py').match('b.py')) + self.assertTrue(P('/a/b.py').match('b.py')) + self.assertFalse(P('a.py').match('b.py')) + self.assertFalse(P('b/py').match('b.py')) + self.assertFalse(P('/a.py').match('b.py')) + self.assertFalse(P('b.py/c').match('b.py')) + # Wildcard relative pattern. + self.assertTrue(P('b.py').match('*.py')) + self.assertTrue(P('a/b.py').match('*.py')) + self.assertTrue(P('/a/b.py').match('*.py')) + self.assertFalse(P('b.pyc').match('*.py')) + self.assertFalse(P('b./py').match('*.py')) + self.assertFalse(P('b.py/c').match('*.py')) + # Multi-part relative pattern. + self.assertTrue(P('ab/c.py').match('a*/*.py')) + self.assertTrue(P('/d/ab/c.py').match('a*/*.py')) + self.assertFalse(P('a.py').match('a*/*.py')) + self.assertFalse(P('/dab/c.py').match('a*/*.py')) + self.assertFalse(P('ab/c.py/d').match('a*/*.py')) + # Absolute pattern. + self.assertTrue(P('/b.py').match('/*.py')) + self.assertFalse(P('b.py').match('/*.py')) + self.assertFalse(P('a/b.py').match('/*.py')) + self.assertFalse(P('/a/b.py').match('/*.py')) + # Multi-part absolute pattern. + self.assertTrue(P('/a/b.py').match('/a/*.py')) + self.assertFalse(P('/ab.py').match('/a/*.py')) + self.assertFalse(P('/a/b/c.py').match('/a/*.py')) + # Multi-part glob-style pattern. + self.assertTrue(P('a').match('**')) + self.assertTrue(P('c.py').match('**')) + self.assertTrue(P('a/b/c.py').match('**')) + self.assertTrue(P('/a/b/c.py').match('**')) + self.assertTrue(P('/a/b/c.py').match('/**')) + self.assertTrue(P('/a/b/c.py').match('**/')) + self.assertTrue(P('/a/b/c.py').match('/a/**')) + self.assertTrue(P('/a/b/c.py').match('**/*.py')) + self.assertTrue(P('/a/b/c.py').match('/**/*.py')) + self.assertTrue(P('/a/b/c.py').match('/a/**/*.py')) + self.assertTrue(P('/a/b/c.py').match('/a/b/**/*.py')) + self.assertTrue(P('/a/b/c.py').match('/**/**/**/**/*.py')) + self.assertFalse(P('c.py').match('**/a.py')) + self.assertFalse(P('c.py').match('c/**')) + self.assertFalse(P('a/b/c.py').match('**/a')) + self.assertFalse(P('a/b/c.py').match('**/a/b')) + self.assertFalse(P('a/b/c.py').match('**/a/b/c')) + self.assertFalse(P('a/b/c.py').match('**/a/b/c.')) + self.assertFalse(P('a/b/c.py').match('**/a/b/c./**')) + self.assertFalse(P('a/b/c.py').match('**/a/b/c./**')) + self.assertFalse(P('a/b/c.py').match('/a/b/c.py/**')) + self.assertFalse(P('a/b/c.py').match('/**/a/b/c.py')) + self.assertRaises(ValueError, P('a').match, '**a/b/c') + self.assertRaises(ValueError, P('a').match, 'a/b/c**') + # Case-sensitive flag + self.assertFalse(P('A.py').match('a.PY', case_sensitive=True)) + self.assertTrue(P('A.py').match('a.PY', case_sensitive=False)) + self.assertFalse(P('c:/a/B.Py').match('C:/A/*.pY', case_sensitive=True)) + self.assertTrue(P('/a/b/c.py').match('/A/*/*.Py', case_sensitive=False)) + # Matching against empty path + self.assertFalse(P().match('*')) + self.assertTrue(P().match('**')) + self.assertFalse(P().match('**/*')) + + def test_parts_common(self): + # `parts` returns a tuple. + sep = self.sep + P = self.cls + p = P('a/b') + parts = p.parts + self.assertEqual(parts, ('a', 'b')) + # When the path is absolute, the anchor is a separate part. + p = P('/a/b') + parts = p.parts + self.assertEqual(parts, (sep, 'a', 'b')) + + def test_equivalences(self): + for k, tuples in self.equivalences.items(): + canon = k.replace('/', self.sep) + posix = k.replace(self.sep, '/') + if canon != posix: + tuples = tuples + [ + tuple(part.replace('/', self.sep) for part in t) + for t in tuples + ] + tuples.append((posix, )) + pcanon = self.cls(canon) + for t in tuples: + p = self.cls(*t) + self.assertEqual(p, pcanon, "failed with args {}".format(t)) + self.assertEqual(hash(p), hash(pcanon)) + self.assertEqual(str(p), canon) + self.assertEqual(p.as_posix(), posix) + + def test_parent_common(self): + # Relative + P = self.cls + p = P('a/b/c') + self.assertEqual(p.parent, P('a/b')) + self.assertEqual(p.parent.parent, P('a')) + self.assertEqual(p.parent.parent.parent, P()) + self.assertEqual(p.parent.parent.parent.parent, P()) + # Anchored + p = P('/a/b/c') + self.assertEqual(p.parent, P('/a/b')) + self.assertEqual(p.parent.parent, P('/a')) + self.assertEqual(p.parent.parent.parent, P('/')) + self.assertEqual(p.parent.parent.parent.parent, P('/')) + + def test_parents_common(self): + # Relative + P = self.cls + p = P('a/b/c') + par = p.parents + self.assertEqual(len(par), 3) + self.assertEqual(par[0], P('a/b')) + self.assertEqual(par[1], P('a')) + self.assertEqual(par[2], P('.')) + self.assertEqual(par[-1], P('.')) + self.assertEqual(par[-2], P('a')) + self.assertEqual(par[-3], P('a/b')) + self.assertEqual(par[0:1], (P('a/b'),)) + self.assertEqual(par[:2], (P('a/b'), P('a'))) + self.assertEqual(par[:-1], (P('a/b'), P('a'))) + self.assertEqual(par[1:], (P('a'), P('.'))) + self.assertEqual(par[::2], (P('a/b'), P('.'))) + self.assertEqual(par[::-1], (P('.'), P('a'), P('a/b'))) + self.assertEqual(list(par), [P('a/b'), P('a'), P('.')]) + with self.assertRaises(IndexError): + par[-4] + with self.assertRaises(IndexError): + par[3] + with self.assertRaises(TypeError): + par[0] = p + # Anchored + p = P('/a/b/c') + par = p.parents + self.assertEqual(len(par), 3) + self.assertEqual(par[0], P('/a/b')) + self.assertEqual(par[1], P('/a')) + self.assertEqual(par[2], P('/')) + self.assertEqual(par[-1], P('/')) + self.assertEqual(par[-2], P('/a')) + self.assertEqual(par[-3], P('/a/b')) + self.assertEqual(par[0:1], (P('/a/b'),)) + self.assertEqual(par[:2], (P('/a/b'), P('/a'))) + self.assertEqual(par[:-1], (P('/a/b'), P('/a'))) + self.assertEqual(par[1:], (P('/a'), P('/'))) + self.assertEqual(par[::2], (P('/a/b'), P('/'))) + self.assertEqual(par[::-1], (P('/'), P('/a'), P('/a/b'))) + self.assertEqual(list(par), [P('/a/b'), P('/a'), P('/')]) + with self.assertRaises(IndexError): + par[-4] + with self.assertRaises(IndexError): + par[3] + + def test_drive_common(self): + P = self.cls + self.assertEqual(P('a/b').drive, '') + self.assertEqual(P('/a/b').drive, '') + self.assertEqual(P('').drive, '') + + def test_root_common(self): + P = self.cls + sep = self.sep + self.assertEqual(P('').root, '') + self.assertEqual(P('a/b').root, '') + self.assertEqual(P('/').root, sep) + self.assertEqual(P('/a/b').root, sep) + + def test_anchor_common(self): + P = self.cls + sep = self.sep + self.assertEqual(P('').anchor, '') + self.assertEqual(P('a/b').anchor, '') + self.assertEqual(P('/').anchor, sep) + self.assertEqual(P('/a/b').anchor, sep) + + def test_name_common(self): + P = self.cls + self.assertEqual(P('').name, '') + self.assertEqual(P('.').name, '') + self.assertEqual(P('/').name, '') + self.assertEqual(P('a/b').name, 'b') + self.assertEqual(P('/a/b').name, 'b') + self.assertEqual(P('/a/b/.').name, 'b') + self.assertEqual(P('a/b.py').name, 'b.py') + self.assertEqual(P('/a/b.py').name, 'b.py') + + def test_suffix_common(self): + P = self.cls + self.assertEqual(P('').suffix, '') + self.assertEqual(P('.').suffix, '') + self.assertEqual(P('..').suffix, '') + self.assertEqual(P('/').suffix, '') + self.assertEqual(P('a/b').suffix, '') + self.assertEqual(P('/a/b').suffix, '') + self.assertEqual(P('/a/b/.').suffix, '') + self.assertEqual(P('a/b.py').suffix, '.py') + self.assertEqual(P('/a/b.py').suffix, '.py') + self.assertEqual(P('a/.hgrc').suffix, '') + self.assertEqual(P('/a/.hgrc').suffix, '') + self.assertEqual(P('a/.hg.rc').suffix, '.rc') + self.assertEqual(P('/a/.hg.rc').suffix, '.rc') + self.assertEqual(P('a/b.tar.gz').suffix, '.gz') + self.assertEqual(P('/a/b.tar.gz').suffix, '.gz') + self.assertEqual(P('a/Some name. Ending with a dot.').suffix, '') + self.assertEqual(P('/a/Some name. Ending with a dot.').suffix, '') + + def test_suffixes_common(self): + P = self.cls + self.assertEqual(P('').suffixes, []) + self.assertEqual(P('.').suffixes, []) + self.assertEqual(P('/').suffixes, []) + self.assertEqual(P('a/b').suffixes, []) + self.assertEqual(P('/a/b').suffixes, []) + self.assertEqual(P('/a/b/.').suffixes, []) + self.assertEqual(P('a/b.py').suffixes, ['.py']) + self.assertEqual(P('/a/b.py').suffixes, ['.py']) + self.assertEqual(P('a/.hgrc').suffixes, []) + self.assertEqual(P('/a/.hgrc').suffixes, []) + self.assertEqual(P('a/.hg.rc').suffixes, ['.rc']) + self.assertEqual(P('/a/.hg.rc').suffixes, ['.rc']) + self.assertEqual(P('a/b.tar.gz').suffixes, ['.tar', '.gz']) + self.assertEqual(P('/a/b.tar.gz').suffixes, ['.tar', '.gz']) + self.assertEqual(P('a/Some name. Ending with a dot.').suffixes, []) + self.assertEqual(P('/a/Some name. Ending with a dot.').suffixes, []) + + def test_stem_common(self): + P = self.cls + self.assertEqual(P('').stem, '') + self.assertEqual(P('.').stem, '') + self.assertEqual(P('..').stem, '..') + self.assertEqual(P('/').stem, '') + self.assertEqual(P('a/b').stem, 'b') + self.assertEqual(P('a/b.py').stem, 'b') + self.assertEqual(P('a/.hgrc').stem, '.hgrc') + self.assertEqual(P('a/.hg.rc').stem, '.hg') + self.assertEqual(P('a/b.tar.gz').stem, 'b.tar') + self.assertEqual(P('a/Some name. Ending with a dot.').stem, + 'Some name. Ending with a dot.') + + def test_with_name_common(self): + P = self.cls + self.assertEqual(P('a/b').with_name('d.xml'), P('a/d.xml')) + self.assertEqual(P('/a/b').with_name('d.xml'), P('/a/d.xml')) + self.assertEqual(P('a/b.py').with_name('d.xml'), P('a/d.xml')) + self.assertEqual(P('/a/b.py').with_name('d.xml'), P('/a/d.xml')) + self.assertEqual(P('a/Dot ending.').with_name('d.xml'), P('a/d.xml')) + self.assertEqual(P('/a/Dot ending.').with_name('d.xml'), P('/a/d.xml')) + self.assertRaises(ValueError, P('').with_name, 'd.xml') + self.assertRaises(ValueError, P('.').with_name, 'd.xml') + self.assertRaises(ValueError, P('/').with_name, 'd.xml') + self.assertRaises(ValueError, P('a/b').with_name, '') + self.assertRaises(ValueError, P('a/b').with_name, '.') + self.assertRaises(ValueError, P('a/b').with_name, '/c') + self.assertRaises(ValueError, P('a/b').with_name, 'c/') + self.assertRaises(ValueError, P('a/b').with_name, 'c/d') + + def test_with_stem_common(self): + P = self.cls + self.assertEqual(P('a/b').with_stem('d'), P('a/d')) + self.assertEqual(P('/a/b').with_stem('d'), P('/a/d')) + self.assertEqual(P('a/b.py').with_stem('d'), P('a/d.py')) + self.assertEqual(P('/a/b.py').with_stem('d'), P('/a/d.py')) + self.assertEqual(P('/a/b.tar.gz').with_stem('d'), P('/a/d.gz')) + self.assertEqual(P('a/Dot ending.').with_stem('d'), P('a/d')) + self.assertEqual(P('/a/Dot ending.').with_stem('d'), P('/a/d')) + self.assertRaises(ValueError, P('').with_stem, 'd') + self.assertRaises(ValueError, P('.').with_stem, 'd') + self.assertRaises(ValueError, P('/').with_stem, 'd') + self.assertRaises(ValueError, P('a/b').with_stem, '') + self.assertRaises(ValueError, P('a/b').with_stem, '.') + self.assertRaises(ValueError, P('a/b').with_stem, '/c') + self.assertRaises(ValueError, P('a/b').with_stem, 'c/') + self.assertRaises(ValueError, P('a/b').with_stem, 'c/d') + + def test_with_suffix_common(self): + P = self.cls + self.assertEqual(P('a/b').with_suffix('.gz'), P('a/b.gz')) + self.assertEqual(P('/a/b').with_suffix('.gz'), P('/a/b.gz')) + self.assertEqual(P('a/b.py').with_suffix('.gz'), P('a/b.gz')) + self.assertEqual(P('/a/b.py').with_suffix('.gz'), P('/a/b.gz')) + # Stripping suffix. + self.assertEqual(P('a/b.py').with_suffix(''), P('a/b')) + self.assertEqual(P('/a/b').with_suffix(''), P('/a/b')) + # Path doesn't have a "filename" component. + self.assertRaises(ValueError, P('').with_suffix, '.gz') + self.assertRaises(ValueError, P('.').with_suffix, '.gz') + self.assertRaises(ValueError, P('/').with_suffix, '.gz') + # Invalid suffix. + self.assertRaises(ValueError, P('a/b').with_suffix, 'gz') + self.assertRaises(ValueError, P('a/b').with_suffix, '/') + self.assertRaises(ValueError, P('a/b').with_suffix, '.') + self.assertRaises(ValueError, P('a/b').with_suffix, '/.gz') + self.assertRaises(ValueError, P('a/b').with_suffix, 'c/d') + self.assertRaises(ValueError, P('a/b').with_suffix, '.c/.d') + self.assertRaises(ValueError, P('a/b').with_suffix, './.d') + self.assertRaises(ValueError, P('a/b').with_suffix, '.d/.') + + def test_relative_to_common(self): + P = self.cls + p = P('a/b') + self.assertRaises(TypeError, p.relative_to) + self.assertRaises(TypeError, p.relative_to, b'a') + self.assertEqual(p.relative_to(P()), P('a/b')) + self.assertEqual(p.relative_to(''), P('a/b')) + self.assertEqual(p.relative_to(P('a')), P('b')) + self.assertEqual(p.relative_to('a'), P('b')) + self.assertEqual(p.relative_to('a/'), P('b')) + self.assertEqual(p.relative_to(P('a/b')), P()) + self.assertEqual(p.relative_to('a/b'), P()) + self.assertEqual(p.relative_to(P(), walk_up=True), P('a/b')) + self.assertEqual(p.relative_to('', walk_up=True), P('a/b')) + self.assertEqual(p.relative_to(P('a'), walk_up=True), P('b')) + self.assertEqual(p.relative_to('a', walk_up=True), P('b')) + self.assertEqual(p.relative_to('a/', walk_up=True), P('b')) + self.assertEqual(p.relative_to(P('a/b'), walk_up=True), P()) + self.assertEqual(p.relative_to('a/b', walk_up=True), P()) + self.assertEqual(p.relative_to(P('a/c'), walk_up=True), P('../b')) + self.assertEqual(p.relative_to('a/c', walk_up=True), P('../b')) + self.assertEqual(p.relative_to(P('a/b/c'), walk_up=True), P('..')) + self.assertEqual(p.relative_to('a/b/c', walk_up=True), P('..')) + self.assertEqual(p.relative_to(P('c'), walk_up=True), P('../a/b')) + self.assertEqual(p.relative_to('c', walk_up=True), P('../a/b')) + # With several args. + with self.assertWarns(DeprecationWarning): + p.relative_to('a', 'b') + p.relative_to('a', 'b', walk_up=True) + # Unrelated paths. + self.assertRaises(ValueError, p.relative_to, P('c')) + self.assertRaises(ValueError, p.relative_to, P('a/b/c')) + self.assertRaises(ValueError, p.relative_to, P('a/c')) + self.assertRaises(ValueError, p.relative_to, P('/a')) + self.assertRaises(ValueError, p.relative_to, P("../a")) + self.assertRaises(ValueError, p.relative_to, P("a/..")) + self.assertRaises(ValueError, p.relative_to, P("/a/..")) + self.assertRaises(ValueError, p.relative_to, P('/'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('/a'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P("../a"), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P("a/.."), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P("/a/.."), walk_up=True) + p = P('/a/b') + self.assertEqual(p.relative_to(P('/')), P('a/b')) + self.assertEqual(p.relative_to('/'), P('a/b')) + self.assertEqual(p.relative_to(P('/a')), P('b')) + self.assertEqual(p.relative_to('/a'), P('b')) + self.assertEqual(p.relative_to('/a/'), P('b')) + self.assertEqual(p.relative_to(P('/a/b')), P()) + self.assertEqual(p.relative_to('/a/b'), P()) + self.assertEqual(p.relative_to(P('/'), walk_up=True), P('a/b')) + self.assertEqual(p.relative_to('/', walk_up=True), P('a/b')) + self.assertEqual(p.relative_to(P('/a'), walk_up=True), P('b')) + self.assertEqual(p.relative_to('/a', walk_up=True), P('b')) + self.assertEqual(p.relative_to('/a/', walk_up=True), P('b')) + self.assertEqual(p.relative_to(P('/a/b'), walk_up=True), P()) + self.assertEqual(p.relative_to('/a/b', walk_up=True), P()) + self.assertEqual(p.relative_to(P('/a/c'), walk_up=True), P('../b')) + self.assertEqual(p.relative_to('/a/c', walk_up=True), P('../b')) + self.assertEqual(p.relative_to(P('/a/b/c'), walk_up=True), P('..')) + self.assertEqual(p.relative_to('/a/b/c', walk_up=True), P('..')) + self.assertEqual(p.relative_to(P('/c'), walk_up=True), P('../a/b')) + self.assertEqual(p.relative_to('/c', walk_up=True), P('../a/b')) + # Unrelated paths. + self.assertRaises(ValueError, p.relative_to, P('/c')) + self.assertRaises(ValueError, p.relative_to, P('/a/b/c')) + self.assertRaises(ValueError, p.relative_to, P('/a/c')) + self.assertRaises(ValueError, p.relative_to, P()) + self.assertRaises(ValueError, p.relative_to, '') + self.assertRaises(ValueError, p.relative_to, P('a')) + self.assertRaises(ValueError, p.relative_to, P("../a")) + self.assertRaises(ValueError, p.relative_to, P("a/..")) + self.assertRaises(ValueError, p.relative_to, P("/a/..")) + self.assertRaises(ValueError, p.relative_to, P(''), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('a'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P("../a"), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P("a/.."), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P("/a/.."), walk_up=True) + + def test_is_relative_to_common(self): + P = self.cls + p = P('a/b') + self.assertRaises(TypeError, p.is_relative_to) + self.assertRaises(TypeError, p.is_relative_to, b'a') + self.assertTrue(p.is_relative_to(P())) + self.assertTrue(p.is_relative_to('')) + self.assertTrue(p.is_relative_to(P('a'))) + self.assertTrue(p.is_relative_to('a/')) + self.assertTrue(p.is_relative_to(P('a/b'))) + self.assertTrue(p.is_relative_to('a/b')) + # With several args. + with self.assertWarns(DeprecationWarning): + p.is_relative_to('a', 'b') + # Unrelated paths. + self.assertFalse(p.is_relative_to(P('c'))) + self.assertFalse(p.is_relative_to(P('a/b/c'))) + self.assertFalse(p.is_relative_to(P('a/c'))) + self.assertFalse(p.is_relative_to(P('/a'))) + p = P('/a/b') + self.assertTrue(p.is_relative_to(P('/'))) + self.assertTrue(p.is_relative_to('/')) + self.assertTrue(p.is_relative_to(P('/a'))) + self.assertTrue(p.is_relative_to('/a')) + self.assertTrue(p.is_relative_to('/a/')) + self.assertTrue(p.is_relative_to(P('/a/b'))) + self.assertTrue(p.is_relative_to('/a/b')) + # Unrelated paths. + self.assertFalse(p.is_relative_to(P('/c'))) + self.assertFalse(p.is_relative_to(P('/a/b/c'))) + self.assertFalse(p.is_relative_to(P('/a/c'))) + self.assertFalse(p.is_relative_to(P())) + self.assertFalse(p.is_relative_to('')) + self.assertFalse(p.is_relative_to(P('a'))) + + +# +# Tests for the virtual classes. +# + +class PathBaseTest(PurePathBaseTest): + cls = pathlib._abc.PathBase + + def test_unsupported_operation(self): + P = self.cls + p = self.cls() + e = pathlib.UnsupportedOperation + self.assertRaises(e, p.stat) + self.assertRaises(e, p.lstat) + self.assertRaises(e, p.exists) + self.assertRaises(e, p.samefile, 'foo') + self.assertRaises(e, p.is_dir) + self.assertRaises(e, p.is_file) + self.assertRaises(e, p.is_mount) + self.assertRaises(e, p.is_symlink) + self.assertRaises(e, p.is_block_device) + self.assertRaises(e, p.is_char_device) + self.assertRaises(e, p.is_fifo) + self.assertRaises(e, p.is_socket) + self.assertRaises(e, p.open) + self.assertRaises(e, p.read_bytes) + self.assertRaises(e, p.read_text) + self.assertRaises(e, p.write_bytes, b'foo') + self.assertRaises(e, p.write_text, 'foo') + self.assertRaises(e, p.iterdir) + self.assertRaises(e, p.glob, '*') + self.assertRaises(e, p.rglob, '*') + self.assertRaises(e, lambda: list(p.walk())) + self.assertRaises(e, p.absolute) + self.assertRaises(e, P.cwd) + self.assertRaises(e, p.expanduser) + self.assertRaises(e, p.home) + self.assertRaises(e, p.readlink) + self.assertRaises(e, p.symlink_to, 'foo') + self.assertRaises(e, p.hardlink_to, 'foo') + self.assertRaises(e, p.mkdir) + self.assertRaises(e, p.touch) + self.assertRaises(e, p.rename, 'foo') + self.assertRaises(e, p.replace, 'foo') + self.assertRaises(e, p.chmod, 0o755) + self.assertRaises(e, p.lchmod, 0o755) + self.assertRaises(e, p.unlink) + self.assertRaises(e, p.rmdir) + self.assertRaises(e, p.owner) + self.assertRaises(e, p.group) + self.assertRaises(e, p.as_uri) + + def test_as_uri_common(self): + e = pathlib.UnsupportedOperation + self.assertRaises(e, self.cls().as_uri) + + def test_fspath_common(self): + self.assertRaises(TypeError, os.fspath, self.cls()) + + def test_as_bytes_common(self): + self.assertRaises(TypeError, bytes, self.cls()) + + def test_matches_path_api(self): + our_names = {name for name in dir(self.cls) if name[0] != '_'} + path_names = {name for name in dir(pathlib.Path) if name[0] != '_'} + self.assertEqual(our_names, path_names) + for attr_name in our_names: + our_attr = getattr(self.cls, attr_name) + path_attr = getattr(pathlib.Path, attr_name) + self.assertEqual(our_attr.__doc__, path_attr.__doc__) + + +class DummyPathIO(io.BytesIO): + """ + Used by DummyPath to implement `open('w')` + """ + + def __init__(self, files, path): + super().__init__() + self.files = files + self.path = path + + def close(self): + self.files[self.path] = self.getvalue() + super().close() + + +class DummyPath(pathlib._abc.PathBase): + """ + Simple implementation of PathBase that keeps files and directories in + memory. + """ + _files = {} + _directories = {} + _symlinks = {} + + def __eq__(self, other): + if not isinstance(other, DummyPath): + return NotImplemented + return str(self) == str(other) + + def __hash__(self): + return hash(str(self)) + + def stat(self, *, follow_symlinks=True): + if follow_symlinks: + path = str(self.resolve()) + else: + path = str(self.parent.resolve() / self.name) + if path in self._files: + st_mode = stat.S_IFREG + elif path in self._directories: + st_mode = stat.S_IFDIR + elif path in self._symlinks: + st_mode = stat.S_IFLNK + else: + raise FileNotFoundError(errno.ENOENT, "Not found", str(self)) + return os.stat_result((st_mode, hash(str(self)), 0, 0, 0, 0, 0, 0, 0, 0)) + + def open(self, mode='r', buffering=-1, encoding=None, + errors=None, newline=None): + if buffering != -1: + raise NotImplementedError + path_obj = self.resolve() + path = str(path_obj) + name = path_obj.name + parent = str(path_obj.parent) + if path in self._directories: + raise IsADirectoryError(errno.EISDIR, "Is a directory", path) + + text = 'b' not in mode + mode = ''.join(c for c in mode if c not in 'btU') + if mode == 'r': + if path not in self._files: + raise FileNotFoundError(errno.ENOENT, "File not found", path) + stream = io.BytesIO(self._files[path]) + elif mode == 'w': + if parent not in self._directories: + raise FileNotFoundError(errno.ENOENT, "File not found", parent) + stream = DummyPathIO(self._files, path) + self._files[path] = b'' + self._directories[parent].add(name) + else: + raise NotImplementedError + if text: + stream = io.TextIOWrapper(stream, encoding=encoding, errors=errors, newline=newline) + return stream + + def iterdir(self): + path = str(self.resolve()) + if path in self._files: + raise NotADirectoryError(errno.ENOTDIR, "Not a directory", path) + elif path in self._directories: + return (self / name for name in self._directories[path]) + else: + raise FileNotFoundError(errno.ENOENT, "File not found", path) + + def mkdir(self, mode=0o777, parents=False, exist_ok=False): + path = str(self.resolve()) + if path in self._directories: + if exist_ok: + return + else: + raise FileExistsError(errno.EEXIST, "File exists", path) + try: + if self.name: + self._directories[str(self.parent)].add(self.name) + self._directories[path] = set() + except KeyError: + if not parents: + raise FileNotFoundError(errno.ENOENT, "File not found", str(self.parent)) from None + self.parent.mkdir(parents=True, exist_ok=True) + self.mkdir(mode, parents=False, exist_ok=exist_ok) + + +class DummyPathTest(DummyPurePathTest): + """Tests for PathBase methods that use stat(), open() and iterdir().""" + + cls = DummyPath + can_symlink = False + + # (BASE) + # | + # |-- brokenLink -> non-existing + # |-- dirA + # | `-- linkC -> ../dirB + # |-- dirB + # | |-- fileB + # | `-- linkD -> ../dirB + # |-- dirC + # | |-- dirD + # | | `-- fileD + # | `-- fileC + # | `-- novel.txt + # |-- dirE # No permissions + # |-- fileA + # |-- linkA -> fileA + # |-- linkB -> dirB + # `-- brokenLinkLoop -> brokenLinkLoop + # + + def setUp(self): + super().setUp() + pathmod = self.cls.pathmod + p = self.cls(BASE) + p.mkdir(parents=True) + p.joinpath('dirA').mkdir() + p.joinpath('dirB').mkdir() + p.joinpath('dirC').mkdir() + p.joinpath('dirC', 'dirD').mkdir() + p.joinpath('dirE').mkdir() + with p.joinpath('fileA').open('wb') as f: + f.write(b"this is file A\n") + with p.joinpath('dirB', 'fileB').open('wb') as f: + f.write(b"this is file B\n") + with p.joinpath('dirC', 'fileC').open('wb') as f: + f.write(b"this is file C\n") + with p.joinpath('dirC', 'novel.txt').open('wb') as f: + f.write(b"this is a novel\n") + with p.joinpath('dirC', 'dirD', 'fileD').open('wb') as f: + f.write(b"this is file D\n") + if self.can_symlink: + p.joinpath('linkA').symlink_to('fileA') + p.joinpath('brokenLink').symlink_to('non-existing') + p.joinpath('linkB').symlink_to('dirB') + p.joinpath('dirA', 'linkC').symlink_to(pathmod.join('..', 'dirB')) + p.joinpath('dirB', 'linkD').symlink_to(pathmod.join('..', 'dirB')) + p.joinpath('brokenLinkLoop').symlink_to('brokenLinkLoop') + + def tearDown(self): + cls = self.cls + cls._files.clear() + cls._directories.clear() + cls._symlinks.clear() + + def tempdir(self): + path = self.cls(BASE).with_name('tmp-dirD') + path.mkdir() + return path + + def assertFileNotFound(self, func, *args, **kwargs): + with self.assertRaises(FileNotFoundError) as cm: + func(*args, **kwargs) + self.assertEqual(cm.exception.errno, errno.ENOENT) + + def assertEqualNormCase(self, path_a, path_b): + self.assertEqual(os.path.normcase(path_a), os.path.normcase(path_b)) + + def test_samefile(self): + fileA_path = os.path.join(BASE, 'fileA') + fileB_path = os.path.join(BASE, 'dirB', 'fileB') + p = self.cls(fileA_path) + pp = self.cls(fileA_path) + q = self.cls(fileB_path) + self.assertTrue(p.samefile(fileA_path)) + self.assertTrue(p.samefile(pp)) + self.assertFalse(p.samefile(fileB_path)) + self.assertFalse(p.samefile(q)) + # Test the non-existent file case + non_existent = os.path.join(BASE, 'foo') + r = self.cls(non_existent) + self.assertRaises(FileNotFoundError, p.samefile, r) + self.assertRaises(FileNotFoundError, p.samefile, non_existent) + self.assertRaises(FileNotFoundError, r.samefile, p) + self.assertRaises(FileNotFoundError, r.samefile, non_existent) + self.assertRaises(FileNotFoundError, r.samefile, r) + self.assertRaises(FileNotFoundError, r.samefile, non_existent) + + def test_empty_path(self): + # The empty path points to '.' + p = self.cls('') + self.assertEqual(str(p), '.') + + def test_exists(self): + P = self.cls + p = P(BASE) + self.assertIs(True, p.exists()) + self.assertIs(True, (p / 'dirA').exists()) + self.assertIs(True, (p / 'fileA').exists()) + self.assertIs(False, (p / 'fileA' / 'bah').exists()) + if self.can_symlink: + self.assertIs(True, (p / 'linkA').exists()) + self.assertIs(True, (p / 'linkB').exists()) + self.assertIs(True, (p / 'linkB' / 'fileB').exists()) + self.assertIs(False, (p / 'linkA' / 'bah').exists()) + self.assertIs(False, (p / 'brokenLink').exists()) + self.assertIs(True, (p / 'brokenLink').exists(follow_symlinks=False)) + self.assertIs(False, (p / 'foo').exists()) + self.assertIs(False, P('/xyzzy').exists()) + self.assertIs(False, P(BASE + '\udfff').exists()) + self.assertIs(False, P(BASE + '\x00').exists()) + + def test_open_common(self): + p = self.cls(BASE) + with (p / 'fileA').open('r') as f: + self.assertIsInstance(f, io.TextIOBase) + self.assertEqual(f.read(), "this is file A\n") + with (p / 'fileA').open('rb') as f: + self.assertIsInstance(f, io.BufferedIOBase) + self.assertEqual(f.read().strip(), b"this is file A") + + def test_read_write_bytes(self): + p = self.cls(BASE) + (p / 'fileA').write_bytes(b'abcdefg') + self.assertEqual((p / 'fileA').read_bytes(), b'abcdefg') + # Check that trying to write str does not truncate the file. + self.assertRaises(TypeError, (p / 'fileA').write_bytes, 'somestr') + self.assertEqual((p / 'fileA').read_bytes(), b'abcdefg') + + def test_read_write_text(self): + p = self.cls(BASE) + (p / 'fileA').write_text('äbcdefg', encoding='latin-1') + self.assertEqual((p / 'fileA').read_text( + encoding='utf-8', errors='ignore'), 'bcdefg') + # Check that trying to write bytes does not truncate the file. + self.assertRaises(TypeError, (p / 'fileA').write_text, b'somebytes') + self.assertEqual((p / 'fileA').read_text(encoding='latin-1'), 'äbcdefg') + + def test_read_text_with_newlines(self): + p = self.cls(BASE) + # Check that `\n` character change nothing + (p / 'fileA').write_bytes(b'abcde\r\nfghlk\n\rmnopq') + self.assertEqual((p / 'fileA').read_text(newline='\n'), + 'abcde\r\nfghlk\n\rmnopq') + # Check that `\r` character replaces `\n` + (p / 'fileA').write_bytes(b'abcde\r\nfghlk\n\rmnopq') + self.assertEqual((p / 'fileA').read_text(newline='\r'), + 'abcde\r\nfghlk\n\rmnopq') + # Check that `\r\n` character replaces `\n` + (p / 'fileA').write_bytes(b'abcde\r\nfghlk\n\rmnopq') + self.assertEqual((p / 'fileA').read_text(newline='\r\n'), + 'abcde\r\nfghlk\n\rmnopq') + + def test_write_text_with_newlines(self): + p = self.cls(BASE) + # Check that `\n` character change nothing + (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\n') + self.assertEqual((p / 'fileA').read_bytes(), + b'abcde\r\nfghlk\n\rmnopq') + # Check that `\r` character replaces `\n` + (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\r') + self.assertEqual((p / 'fileA').read_bytes(), + b'abcde\r\rfghlk\r\rmnopq') + # Check that `\r\n` character replaces `\n` + (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\r\n') + self.assertEqual((p / 'fileA').read_bytes(), + b'abcde\r\r\nfghlk\r\n\rmnopq') + # Check that no argument passed will change `\n` to `os.linesep` + os_linesep_byte = bytes(os.linesep, encoding='ascii') + (p / 'fileA').write_text('abcde\nfghlk\n\rmnopq') + self.assertEqual((p / 'fileA').read_bytes(), + b'abcde' + os_linesep_byte + b'fghlk' + os_linesep_byte + b'\rmnopq') + + def test_iterdir(self): + P = self.cls + p = P(BASE) + it = p.iterdir() + paths = set(it) + expected = ['dirA', 'dirB', 'dirC', 'dirE', 'fileA'] + if self.can_symlink: + expected += ['linkA', 'linkB', 'brokenLink', 'brokenLinkLoop'] + self.assertEqual(paths, { P(BASE, q) for q in expected }) + + def test_iterdir_symlink(self): + if not self.can_symlink: + self.skipTest("symlinks required") + # __iter__ on a symlink to a directory. + P = self.cls + p = P(BASE, 'linkB') + paths = set(p.iterdir()) + expected = { P(BASE, 'linkB', q) for q in ['fileB', 'linkD'] } + self.assertEqual(paths, expected) + + def test_iterdir_nodir(self): + # __iter__ on something that is not a directory. + p = self.cls(BASE, 'fileA') + with self.assertRaises(OSError) as cm: + p.iterdir() + # ENOENT or EINVAL under Windows, ENOTDIR otherwise + # (see issue #12802). + self.assertIn(cm.exception.errno, (errno.ENOTDIR, + errno.ENOENT, errno.EINVAL)) + + def test_glob_common(self): + def _check(glob, expected): + self.assertEqual(set(glob), { P(BASE, q) for q in expected }) + P = self.cls + p = P(BASE) + it = p.glob("fileA") + self.assertIsInstance(it, collections.abc.Iterator) + _check(it, ["fileA"]) + _check(p.glob("fileB"), []) + _check(p.glob("dir*/file*"), ["dirB/fileB", "dirC/fileC"]) + if not self.can_symlink: + _check(p.glob("*A"), ['dirA', 'fileA']) + else: + _check(p.glob("*A"), ['dirA', 'fileA', 'linkA']) + if not self.can_symlink: + _check(p.glob("*B/*"), ['dirB/fileB']) + else: + _check(p.glob("*B/*"), ['dirB/fileB', 'dirB/linkD', + 'linkB/fileB', 'linkB/linkD']) + if not self.can_symlink: + _check(p.glob("*/fileB"), ['dirB/fileB']) + else: + _check(p.glob("*/fileB"), ['dirB/fileB', 'linkB/fileB']) + if self.can_symlink: + _check(p.glob("brokenLink"), ['brokenLink']) + + if not self.can_symlink: + _check(p.glob("*/"), ["dirA/", "dirB/", "dirC/", "dirE/"]) + else: + _check(p.glob("*/"), ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"]) + + def test_glob_empty_pattern(self): + p = self.cls() + with self.assertRaisesRegex(ValueError, 'Unacceptable pattern'): + list(p.glob('')) + + def test_glob_case_sensitive(self): + P = self.cls + def _check(path, pattern, case_sensitive, expected): + actual = {str(q) for q in path.glob(pattern, case_sensitive=case_sensitive)} + expected = {str(P(BASE, q)) for q in expected} + self.assertEqual(actual, expected) + path = P(BASE) + _check(path, "DIRB/FILE*", True, []) + _check(path, "DIRB/FILE*", False, ["dirB/fileB"]) + _check(path, "dirb/file*", True, []) + _check(path, "dirb/file*", False, ["dirB/fileB"]) + + def test_glob_follow_symlinks_common(self): + if not self.can_symlink: + self.skipTest("symlinks required") + def _check(path, glob, expected): + actual = {path for path in path.glob(glob, follow_symlinks=True) + if "linkD" not in path.parent.parts} # exclude symlink loop. + self.assertEqual(actual, { P(BASE, q) for q in expected }) + P = self.cls + p = P(BASE) + _check(p, "fileB", []) + _check(p, "dir*/file*", ["dirB/fileB", "dirC/fileC"]) + _check(p, "*A", ["dirA", "fileA", "linkA"]) + _check(p, "*B/*", ["dirB/fileB", "dirB/linkD", "linkB/fileB", "linkB/linkD"]) + _check(p, "*/fileB", ["dirB/fileB", "linkB/fileB"]) + _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"]) + _check(p, "dir*/*/..", ["dirC/dirD/..", "dirA/linkC/.."]) + _check(p, "dir*/**/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/", + "dirC/", "dirC/dirD/", "dirE/"]) + _check(p, "dir*/**/..", ["dirA/..", "dirA/linkC/..", "dirB/..", + "dirC/..", "dirC/dirD/..", "dirE/.."]) + _check(p, "dir*/*/**/", ["dirA/linkC/", "dirA/linkC/linkD/", "dirB/linkD/", "dirC/dirD/"]) + _check(p, "dir*/*/**/..", ["dirA/linkC/..", "dirC/dirD/.."]) + _check(p, "dir*/**/fileC", ["dirC/fileC"]) + _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"]) + _check(p, "*/dirD/**/", ["dirC/dirD/"]) + + def test_glob_no_follow_symlinks_common(self): + if not self.can_symlink: + self.skipTest("symlinks required") + def _check(path, glob, expected): + actual = {path for path in path.glob(glob, follow_symlinks=False)} + self.assertEqual(actual, { P(BASE, q) for q in expected }) + P = self.cls + p = P(BASE) + _check(p, "fileB", []) + _check(p, "dir*/file*", ["dirB/fileB", "dirC/fileC"]) + _check(p, "*A", ["dirA", "fileA", "linkA"]) + _check(p, "*B/*", ["dirB/fileB", "dirB/linkD"]) + _check(p, "*/fileB", ["dirB/fileB"]) + _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/"]) + _check(p, "dir*/*/..", ["dirC/dirD/.."]) + _check(p, "dir*/**/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"]) + _check(p, "dir*/**/..", ["dirA/..", "dirB/..", "dirC/..", "dirC/dirD/..", "dirE/.."]) + _check(p, "dir*/*/**/", ["dirC/dirD/"]) + _check(p, "dir*/*/**/..", ["dirC/dirD/.."]) + _check(p, "dir*/**/fileC", ["dirC/fileC"]) + _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"]) + _check(p, "*/dirD/**/", ["dirC/dirD/"]) + + def test_rglob_common(self): + def _check(glob, expected): + self.assertEqual(set(glob), {P(BASE, q) for q in expected}) + P = self.cls + p = P(BASE) + it = p.rglob("fileA") + self.assertIsInstance(it, collections.abc.Iterator) + _check(it, ["fileA"]) + _check(p.rglob("fileB"), ["dirB/fileB"]) + _check(p.rglob("**/fileB"), ["dirB/fileB"]) + _check(p.rglob("*/fileA"), []) + if not self.can_symlink: + _check(p.rglob("*/fileB"), ["dirB/fileB"]) + else: + _check(p.rglob("*/fileB"), ["dirB/fileB", "dirB/linkD/fileB", + "linkB/fileB", "dirA/linkC/fileB"]) + _check(p.rglob("file*"), ["fileA", "dirB/fileB", + "dirC/fileC", "dirC/dirD/fileD"]) + if not self.can_symlink: + _check(p.rglob("*/"), [ + "dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/", + ]) + else: + _check(p.rglob("*/"), [ + "dirA/", "dirA/linkC/", "dirB/", "dirB/linkD/", "dirC/", + "dirC/dirD/", "dirE/", "linkB/", + ]) + _check(p.rglob(""), ["./", "dirA/", "dirB/", "dirC/", "dirE/", "dirC/dirD/"]) + + p = P(BASE, "dirC") + _check(p.rglob("*"), ["dirC/fileC", "dirC/novel.txt", + "dirC/dirD", "dirC/dirD/fileD"]) + _check(p.rglob("file*"), ["dirC/fileC", "dirC/dirD/fileD"]) + _check(p.rglob("**/file*"), ["dirC/fileC", "dirC/dirD/fileD"]) + _check(p.rglob("dir*/**/"), ["dirC/dirD/"]) + _check(p.rglob("*/*"), ["dirC/dirD/fileD"]) + _check(p.rglob("*/"), ["dirC/dirD/"]) + _check(p.rglob(""), ["dirC/", "dirC/dirD/"]) + _check(p.rglob("**/"), ["dirC/", "dirC/dirD/"]) + # gh-91616, a re module regression + _check(p.rglob("*.txt"), ["dirC/novel.txt"]) + _check(p.rglob("*.*"), ["dirC/novel.txt"]) + + def test_rglob_follow_symlinks_common(self): + if not self.can_symlink: + self.skipTest("symlinks required") + def _check(path, glob, expected): + actual = {path for path in path.rglob(glob, follow_symlinks=True) + if 'linkD' not in path.parent.parts} # exclude symlink loop. + self.assertEqual(actual, { P(BASE, q) for q in expected }) + P = self.cls + p = P(BASE) + _check(p, "fileB", ["dirB/fileB", "dirA/linkC/fileB", "linkB/fileB"]) + _check(p, "*/fileA", []) + _check(p, "*/fileB", ["dirB/fileB", "dirA/linkC/fileB", "linkB/fileB"]) + _check(p, "file*", ["fileA", "dirA/linkC/fileB", "dirB/fileB", + "dirC/fileC", "dirC/dirD/fileD", "linkB/fileB"]) + _check(p, "*/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/", + "dirC/", "dirC/dirD/", "dirE/", "linkB/", "linkB/linkD/"]) + _check(p, "", ["./", "dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/", + "dirC/", "dirE/", "dirC/dirD/", "linkB/", "linkB/linkD/"]) + + p = P(BASE, "dirC") + _check(p, "*", ["dirC/fileC", "dirC/novel.txt", + "dirC/dirD", "dirC/dirD/fileD"]) + _check(p, "file*", ["dirC/fileC", "dirC/dirD/fileD"]) + _check(p, "*/*", ["dirC/dirD/fileD"]) + _check(p, "*/", ["dirC/dirD/"]) + _check(p, "", ["dirC/", "dirC/dirD/"]) + # gh-91616, a re module regression + _check(p, "*.txt", ["dirC/novel.txt"]) + _check(p, "*.*", ["dirC/novel.txt"]) + + def test_rglob_no_follow_symlinks_common(self): + if not self.can_symlink: + self.skipTest("symlinks required") + def _check(path, glob, expected): + actual = {path for path in path.rglob(glob, follow_symlinks=False)} + self.assertEqual(actual, { P(BASE, q) for q in expected }) + P = self.cls + p = P(BASE) + _check(p, "fileB", ["dirB/fileB"]) + _check(p, "*/fileA", []) + _check(p, "*/fileB", ["dirB/fileB"]) + _check(p, "file*", ["fileA", "dirB/fileB", "dirC/fileC", "dirC/dirD/fileD", ]) + _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"]) + _check(p, "", ["./", "dirA/", "dirB/", "dirC/", "dirE/", "dirC/dirD/"]) + + p = P(BASE, "dirC") + _check(p, "*", ["dirC/fileC", "dirC/novel.txt", + "dirC/dirD", "dirC/dirD/fileD"]) + _check(p, "file*", ["dirC/fileC", "dirC/dirD/fileD"]) + _check(p, "*/*", ["dirC/dirD/fileD"]) + _check(p, "*/", ["dirC/dirD/"]) + _check(p, "", ["dirC/", "dirC/dirD/"]) + # gh-91616, a re module regression + _check(p, "*.txt", ["dirC/novel.txt"]) + _check(p, "*.*", ["dirC/novel.txt"]) + + def test_rglob_symlink_loop(self): + # Don't get fooled by symlink loops (Issue #26012). + if not self.can_symlink: + self.skipTest("symlinks required") + P = self.cls + p = P(BASE) + given = set(p.rglob('*')) + expect = {'brokenLink', + 'dirA', 'dirA/linkC', + 'dirB', 'dirB/fileB', 'dirB/linkD', + 'dirC', 'dirC/dirD', 'dirC/dirD/fileD', + 'dirC/fileC', 'dirC/novel.txt', + 'dirE', + 'fileA', + 'linkA', + 'linkB', + 'brokenLinkLoop', + } + self.assertEqual(given, {p / x for x in expect}) + + def test_glob_many_open_files(self): + depth = 30 + P = self.cls + p = base = P(BASE) / 'deep' + p.mkdir() + for _ in range(depth): + p /= 'd' + p.mkdir() + pattern = '/'.join(['*'] * depth) + iters = [base.glob(pattern) for j in range(100)] + for it in iters: + self.assertEqual(next(it), p) + iters = [base.rglob('d') for j in range(100)] + p = base + for i in range(depth): + p = p / 'd' + for it in iters: + self.assertEqual(next(it), p) + + def test_glob_dotdot(self): + # ".." is not special in globs. + P = self.cls + p = P(BASE) + self.assertEqual(set(p.glob("..")), { P(BASE, "..") }) + self.assertEqual(set(p.glob("../..")), { P(BASE, "..", "..") }) + self.assertEqual(set(p.glob("dirA/..")), { P(BASE, "dirA", "..") }) + self.assertEqual(set(p.glob("dirA/../file*")), { P(BASE, "dirA/../fileA") }) + self.assertEqual(set(p.glob("dirA/../file*/..")), set()) + self.assertEqual(set(p.glob("../xyzzy")), set()) + self.assertEqual(set(p.glob("xyzzy/..")), set()) + self.assertEqual(set(p.glob("/".join([".."] * 50))), { P(BASE, *[".."] * 50)}) + + def test_glob_permissions(self): + # See bpo-38894 + if not self.can_symlink: + self.skipTest("symlinks required") + P = self.cls + base = P(BASE) / 'permissions' + base.mkdir() + + for i in range(100): + link = base / f"link{i}" + if i % 2: + link.symlink_to(P(BASE, "dirE", "nonexistent")) + else: + link.symlink_to(P(BASE, "dirC")) + + self.assertEqual(len(set(base.glob("*"))), 100) + self.assertEqual(len(set(base.glob("*/"))), 50) + self.assertEqual(len(set(base.glob("*/fileC"))), 50) + self.assertEqual(len(set(base.glob("*/file*"))), 50) + + def test_glob_long_symlink(self): + # See gh-87695 + if not self.can_symlink: + self.skipTest("symlinks required") + base = self.cls(BASE) / 'long_symlink' + base.mkdir() + bad_link = base / 'bad_link' + bad_link.symlink_to("bad" * 200) + self.assertEqual(sorted(base.glob('**/*')), [bad_link]) + + def test_glob_above_recursion_limit(self): + recursion_limit = 50 + # directory_depth > recursion_limit + directory_depth = recursion_limit + 10 + base = self.cls(BASE, 'deep') + path = base.joinpath(*(['d'] * directory_depth)) + path.mkdir(parents=True) + + with set_recursion_limit(recursion_limit): + list(base.glob('**/')) + + def test_glob_recursive_no_trailing_slash(self): + P = self.cls + p = P(BASE) + with self.assertWarns(FutureWarning): + p.glob('**') + with self.assertWarns(FutureWarning): + p.glob('*/**') + with self.assertWarns(FutureWarning): + p.rglob('**') + with self.assertWarns(FutureWarning): + p.rglob('*/**') + + + def test_readlink(self): + if not self.can_symlink: + self.skipTest("symlinks required") + P = self.cls(BASE) + self.assertEqual((P / 'linkA').readlink(), self.cls('fileA')) + self.assertEqual((P / 'brokenLink').readlink(), + self.cls('non-existing')) + self.assertEqual((P / 'linkB').readlink(), self.cls('dirB')) + self.assertEqual((P / 'linkB' / 'linkD').readlink(), self.cls('../dirB')) + with self.assertRaises(OSError): + (P / 'fileA').readlink() + + @unittest.skipIf(hasattr(os, "readlink"), "os.readlink() is present") + def test_readlink_unsupported(self): + P = self.cls(BASE) + p = P / 'fileA' + with self.assertRaises(pathlib.UnsupportedOperation): + q.readlink(p) + + def _check_resolve(self, p, expected, strict=True): + q = p.resolve(strict) + self.assertEqual(q, expected) + + # This can be used to check both relative and absolute resolutions. + _check_resolve_relative = _check_resolve_absolute = _check_resolve + + def test_resolve_common(self): + if not self.can_symlink: + self.skipTest("symlinks required") + P = self.cls + p = P(BASE, 'foo') + with self.assertRaises(OSError) as cm: + p.resolve(strict=True) + self.assertEqual(cm.exception.errno, errno.ENOENT) + # Non-strict + self.assertEqualNormCase(str(p.resolve(strict=False)), + os.path.join(BASE, 'foo')) + p = P(BASE, 'foo', 'in', 'spam') + self.assertEqualNormCase(str(p.resolve(strict=False)), + os.path.join(BASE, 'foo', 'in', 'spam')) + p = P(BASE, '..', 'foo', 'in', 'spam') + self.assertEqualNormCase(str(p.resolve(strict=False)), + os.path.abspath(os.path.join('foo', 'in', 'spam'))) + # These are all relative symlinks. + p = P(BASE, 'dirB', 'fileB') + self._check_resolve_relative(p, p) + p = P(BASE, 'linkA') + self._check_resolve_relative(p, P(BASE, 'fileA')) + p = P(BASE, 'dirA', 'linkC', 'fileB') + self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB')) + p = P(BASE, 'dirB', 'linkD', 'fileB') + self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB')) + # Non-strict + p = P(BASE, 'dirA', 'linkC', 'fileB', 'foo', 'in', 'spam') + self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB', 'foo', 'in', + 'spam'), False) + p = P(BASE, 'dirA', 'linkC', '..', 'foo', 'in', 'spam') + if os.name == 'nt' and isinstance(p, pathlib.Path): + # In Windows, if linkY points to dirB, 'dirA\linkY\..' + # resolves to 'dirA' without resolving linkY first. + self._check_resolve_relative(p, P(BASE, 'dirA', 'foo', 'in', + 'spam'), False) + else: + # In Posix, if linkY points to dirB, 'dirA/linkY/..' + # resolves to 'dirB/..' first before resolving to parent of dirB. + self._check_resolve_relative(p, P(BASE, 'foo', 'in', 'spam'), False) + # Now create absolute symlinks. + d = self.tempdir() + P(BASE, 'dirA', 'linkX').symlink_to(d) + P(BASE, str(d), 'linkY').symlink_to(join('dirB')) + p = P(BASE, 'dirA', 'linkX', 'linkY', 'fileB') + self._check_resolve_absolute(p, P(BASE, 'dirB', 'fileB')) + # Non-strict + p = P(BASE, 'dirA', 'linkX', 'linkY', 'foo', 'in', 'spam') + self._check_resolve_relative(p, P(BASE, 'dirB', 'foo', 'in', 'spam'), + False) + p = P(BASE, 'dirA', 'linkX', 'linkY', '..', 'foo', 'in', 'spam') + if os.name == 'nt' and isinstance(p, pathlib.Path): + # In Windows, if linkY points to dirB, 'dirA\linkY\..' + # resolves to 'dirA' without resolving linkY first. + self._check_resolve_relative(p, P(d, 'foo', 'in', 'spam'), False) + else: + # In Posix, if linkY points to dirB, 'dirA/linkY/..' + # resolves to 'dirB/..' first before resolving to parent of dirB. + self._check_resolve_relative(p, P(BASE, 'foo', 'in', 'spam'), False) + + def test_resolve_dot(self): + # See http://web.archive.org/web/20200623062557/https://bitbucket.org/pitrou/pathlib/issues/9/ + if not self.can_symlink: + self.skipTest("symlinks required") + p = self.cls(BASE) + p.joinpath('0').symlink_to('.', target_is_directory=True) + p.joinpath('1').symlink_to(os.path.join('0', '0'), target_is_directory=True) + p.joinpath('2').symlink_to(os.path.join('1', '1'), target_is_directory=True) + q = p / '2' + self.assertEqual(q.resolve(strict=True), p) + r = q / '3' / '4' + self.assertRaises(FileNotFoundError, r.resolve, strict=True) + # Non-strict + self.assertEqual(r.resolve(strict=False), p / '3' / '4') + + def _check_symlink_loop(self, *args): + path = self.cls(*args) + with self.assertRaises(OSError) as cm: + path.resolve(strict=True) + self.assertEqual(cm.exception.errno, errno.ELOOP) + + def test_resolve_loop(self): + if not self.can_symlink: + self.skipTest("symlinks required") + if os.name == 'nt' and issubclass(self.cls, pathlib.Path): + self.skipTest("symlink loops work differently with concrete Windows paths") + # Loops with relative symlinks. + self.cls(BASE, 'linkX').symlink_to('linkX/inside') + self._check_symlink_loop(BASE, 'linkX') + self.cls(BASE, 'linkY').symlink_to('linkY') + self._check_symlink_loop(BASE, 'linkY') + self.cls(BASE, 'linkZ').symlink_to('linkZ/../linkZ') + self._check_symlink_loop(BASE, 'linkZ') + # Non-strict + p = self.cls(BASE, 'linkZ', 'foo') + self.assertEqual(p.resolve(strict=False), p) + # Loops with absolute symlinks. + self.cls(BASE, 'linkU').symlink_to(join('linkU/inside')) + self._check_symlink_loop(BASE, 'linkU') + self.cls(BASE, 'linkV').symlink_to(join('linkV')) + self._check_symlink_loop(BASE, 'linkV') + self.cls(BASE, 'linkW').symlink_to(join('linkW/../linkW')) + self._check_symlink_loop(BASE, 'linkW') + # Non-strict + q = self.cls(BASE, 'linkW', 'foo') + self.assertEqual(q.resolve(strict=False), q) + + def test_stat(self): + statA = self.cls(BASE).joinpath('fileA').stat() + statB = self.cls(BASE).joinpath('dirB', 'fileB').stat() + statC = self.cls(BASE).joinpath('dirC').stat() + # st_mode: files are the same, directory differs. + self.assertIsInstance(statA.st_mode, int) + self.assertEqual(statA.st_mode, statB.st_mode) + self.assertNotEqual(statA.st_mode, statC.st_mode) + self.assertNotEqual(statB.st_mode, statC.st_mode) + # st_ino: all different, + self.assertIsInstance(statA.st_ino, int) + self.assertNotEqual(statA.st_ino, statB.st_ino) + self.assertNotEqual(statA.st_ino, statC.st_ino) + self.assertNotEqual(statB.st_ino, statC.st_ino) + # st_dev: all the same. + self.assertIsInstance(statA.st_dev, int) + self.assertEqual(statA.st_dev, statB.st_dev) + self.assertEqual(statA.st_dev, statC.st_dev) + # other attributes not used by pathlib. + + def test_stat_no_follow_symlinks(self): + if not self.can_symlink: + self.skipTest("symlinks required") + p = self.cls(BASE) / 'linkA' + st = p.stat() + self.assertNotEqual(st, p.stat(follow_symlinks=False)) + + def test_stat_no_follow_symlinks_nosymlink(self): + p = self.cls(BASE) / 'fileA' + st = p.stat() + self.assertEqual(st, p.stat(follow_symlinks=False)) + + def test_lstat(self): + if not self.can_symlink: + self.skipTest("symlinks required") + p = self.cls(BASE)/ 'linkA' + st = p.stat() + self.assertNotEqual(st, p.lstat()) + + def test_lstat_nosymlink(self): + p = self.cls(BASE) / 'fileA' + st = p.stat() + self.assertEqual(st, p.lstat()) + + def test_is_dir(self): + P = self.cls(BASE) + self.assertTrue((P / 'dirA').is_dir()) + self.assertFalse((P / 'fileA').is_dir()) + self.assertFalse((P / 'non-existing').is_dir()) + self.assertFalse((P / 'fileA' / 'bah').is_dir()) + if self.can_symlink: + self.assertFalse((P / 'linkA').is_dir()) + self.assertTrue((P / 'linkB').is_dir()) + self.assertFalse((P/ 'brokenLink').is_dir()) + self.assertFalse((P / 'dirA\udfff').is_dir()) + self.assertFalse((P / 'dirA\x00').is_dir()) + + def test_is_dir_no_follow_symlinks(self): + P = self.cls(BASE) + self.assertTrue((P / 'dirA').is_dir(follow_symlinks=False)) + self.assertFalse((P / 'fileA').is_dir(follow_symlinks=False)) + self.assertFalse((P / 'non-existing').is_dir(follow_symlinks=False)) + self.assertFalse((P / 'fileA' / 'bah').is_dir(follow_symlinks=False)) + if self.can_symlink: + self.assertFalse((P / 'linkA').is_dir(follow_symlinks=False)) + self.assertFalse((P / 'linkB').is_dir(follow_symlinks=False)) + self.assertFalse((P/ 'brokenLink').is_dir(follow_symlinks=False)) + self.assertFalse((P / 'dirA\udfff').is_dir(follow_symlinks=False)) + self.assertFalse((P / 'dirA\x00').is_dir(follow_symlinks=False)) + + def test_is_file(self): + P = self.cls(BASE) + self.assertTrue((P / 'fileA').is_file()) + self.assertFalse((P / 'dirA').is_file()) + self.assertFalse((P / 'non-existing').is_file()) + self.assertFalse((P / 'fileA' / 'bah').is_file()) + if self.can_symlink: + self.assertTrue((P / 'linkA').is_file()) + self.assertFalse((P / 'linkB').is_file()) + self.assertFalse((P/ 'brokenLink').is_file()) + self.assertFalse((P / 'fileA\udfff').is_file()) + self.assertFalse((P / 'fileA\x00').is_file()) + + def test_is_file_no_follow_symlinks(self): + P = self.cls(BASE) + self.assertTrue((P / 'fileA').is_file(follow_symlinks=False)) + self.assertFalse((P / 'dirA').is_file(follow_symlinks=False)) + self.assertFalse((P / 'non-existing').is_file(follow_symlinks=False)) + self.assertFalse((P / 'fileA' / 'bah').is_file(follow_symlinks=False)) + if self.can_symlink: + self.assertFalse((P / 'linkA').is_file(follow_symlinks=False)) + self.assertFalse((P / 'linkB').is_file(follow_symlinks=False)) + self.assertFalse((P/ 'brokenLink').is_file(follow_symlinks=False)) + self.assertFalse((P / 'fileA\udfff').is_file(follow_symlinks=False)) + self.assertFalse((P / 'fileA\x00').is_file(follow_symlinks=False)) + + def test_is_mount(self): + P = self.cls(BASE) + self.assertFalse((P / 'fileA').is_mount()) + self.assertFalse((P / 'dirA').is_mount()) + self.assertFalse((P / 'non-existing').is_mount()) + self.assertFalse((P / 'fileA' / 'bah').is_mount()) + if self.can_symlink: + self.assertFalse((P / 'linkA').is_mount()) + + def test_is_symlink(self): + P = self.cls(BASE) + self.assertFalse((P / 'fileA').is_symlink()) + self.assertFalse((P / 'dirA').is_symlink()) + self.assertFalse((P / 'non-existing').is_symlink()) + self.assertFalse((P / 'fileA' / 'bah').is_symlink()) + if self.can_symlink: + self.assertTrue((P / 'linkA').is_symlink()) + self.assertTrue((P / 'linkB').is_symlink()) + self.assertTrue((P/ 'brokenLink').is_symlink()) + self.assertIs((P / 'fileA\udfff').is_file(), False) + self.assertIs((P / 'fileA\x00').is_file(), False) + if self.can_symlink: + self.assertIs((P / 'linkA\udfff').is_file(), False) + self.assertIs((P / 'linkA\x00').is_file(), False) + + def test_is_junction_false(self): + P = self.cls(BASE) + self.assertFalse((P / 'fileA').is_junction()) + self.assertFalse((P / 'dirA').is_junction()) + self.assertFalse((P / 'non-existing').is_junction()) + self.assertFalse((P / 'fileA' / 'bah').is_junction()) + self.assertFalse((P / 'fileA\udfff').is_junction()) + self.assertFalse((P / 'fileA\x00').is_junction()) + + def test_is_fifo_false(self): + P = self.cls(BASE) + self.assertFalse((P / 'fileA').is_fifo()) + self.assertFalse((P / 'dirA').is_fifo()) + self.assertFalse((P / 'non-existing').is_fifo()) + self.assertFalse((P / 'fileA' / 'bah').is_fifo()) + self.assertIs((P / 'fileA\udfff').is_fifo(), False) + self.assertIs((P / 'fileA\x00').is_fifo(), False) + + def test_is_socket_false(self): + P = self.cls(BASE) + self.assertFalse((P / 'fileA').is_socket()) + self.assertFalse((P / 'dirA').is_socket()) + self.assertFalse((P / 'non-existing').is_socket()) + self.assertFalse((P / 'fileA' / 'bah').is_socket()) + self.assertIs((P / 'fileA\udfff').is_socket(), False) + self.assertIs((P / 'fileA\x00').is_socket(), False) + + def test_is_block_device_false(self): + P = self.cls(BASE) + self.assertFalse((P / 'fileA').is_block_device()) + self.assertFalse((P / 'dirA').is_block_device()) + self.assertFalse((P / 'non-existing').is_block_device()) + self.assertFalse((P / 'fileA' / 'bah').is_block_device()) + self.assertIs((P / 'fileA\udfff').is_block_device(), False) + self.assertIs((P / 'fileA\x00').is_block_device(), False) + + def test_is_char_device_false(self): + P = self.cls(BASE) + self.assertFalse((P / 'fileA').is_char_device()) + self.assertFalse((P / 'dirA').is_char_device()) + self.assertFalse((P / 'non-existing').is_char_device()) + self.assertFalse((P / 'fileA' / 'bah').is_char_device()) + self.assertIs((P / 'fileA\udfff').is_char_device(), False) + self.assertIs((P / 'fileA\x00').is_char_device(), False) + + def test_pickling_common(self): + p = self.cls(BASE, 'fileA') + for proto in range(0, pickle.HIGHEST_PROTOCOL + 1): + dumped = pickle.dumps(p, proto) + pp = pickle.loads(dumped) + self.assertEqual(pp.stat(), p.stat()) + + def test_parts_interning(self): + P = self.cls + p = P('/usr/bin/foo') + q = P('/usr/local/bin') + # 'usr' + self.assertIs(p.parts[1], q.parts[1]) + # 'bin' + self.assertIs(p.parts[2], q.parts[3]) + + def _check_complex_symlinks(self, link0_target): + if not self.can_symlink: + self.skipTest("symlinks required") + + # Test solving a non-looping chain of symlinks (issue #19887). + P = self.cls(BASE) + P.joinpath('link1').symlink_to(os.path.join('link0', 'link0'), target_is_directory=True) + P.joinpath('link2').symlink_to(os.path.join('link1', 'link1'), target_is_directory=True) + P.joinpath('link3').symlink_to(os.path.join('link2', 'link2'), target_is_directory=True) + P.joinpath('link0').symlink_to(link0_target, target_is_directory=True) + + # Resolve absolute paths. + p = (P / 'link0').resolve() + self.assertEqual(p, P) + self.assertEqualNormCase(str(p), BASE) + p = (P / 'link1').resolve() + self.assertEqual(p, P) + self.assertEqualNormCase(str(p), BASE) + p = (P / 'link2').resolve() + self.assertEqual(p, P) + self.assertEqualNormCase(str(p), BASE) + p = (P / 'link3').resolve() + self.assertEqual(p, P) + self.assertEqualNormCase(str(p), BASE) + + # Resolve relative paths. + try: + self.cls().absolute() + except pathlib.UnsupportedOperation: + return + old_path = os.getcwd() + os.chdir(BASE) + try: + p = self.cls('link0').resolve() + self.assertEqual(p, P) + self.assertEqualNormCase(str(p), BASE) + p = self.cls('link1').resolve() + self.assertEqual(p, P) + self.assertEqualNormCase(str(p), BASE) + p = self.cls('link2').resolve() + self.assertEqual(p, P) + self.assertEqualNormCase(str(p), BASE) + p = self.cls('link3').resolve() + self.assertEqual(p, P) + self.assertEqualNormCase(str(p), BASE) + finally: + os.chdir(old_path) + + def test_complex_symlinks_absolute(self): + self._check_complex_symlinks(BASE) + + def test_complex_symlinks_relative(self): + self._check_complex_symlinks('.') + + def test_complex_symlinks_relative_dot_dot(self): + self._check_complex_symlinks(os.path.join('dirA', '..')) + + def setUpWalk(self): + # Build: + # TESTFN/ + # TEST1/ a file kid and two directory kids + # tmp1 + # SUB1/ a file kid and a directory kid + # tmp2 + # SUB11/ no kids + # SUB2/ a file kid and a dirsymlink kid + # tmp3 + # link/ a symlink to TEST2 + # broken_link + # broken_link2 + # TEST2/ + # tmp4 a lone file + self.walk_path = self.cls(BASE, "TEST1") + self.sub1_path = self.walk_path / "SUB1" + self.sub11_path = self.sub1_path / "SUB11" + self.sub2_path = self.walk_path / "SUB2" + tmp1_path = self.walk_path / "tmp1" + tmp2_path = self.sub1_path / "tmp2" + tmp3_path = self.sub2_path / "tmp3" + self.link_path = self.sub2_path / "link" + t2_path = self.cls(BASE, "TEST2") + tmp4_path = self.cls(BASE, "TEST2", "tmp4") + broken_link_path = self.sub2_path / "broken_link" + broken_link2_path = self.sub2_path / "broken_link2" + + self.sub11_path.mkdir(parents=True) + self.sub2_path.mkdir(parents=True) + t2_path.mkdir(parents=True) + + for path in tmp1_path, tmp2_path, tmp3_path, tmp4_path: + with path.open("w", encoding='utf-8') as f: + f.write(f"I'm {path} and proud of it. Blame test_pathlib.\n") + + if self.can_symlink: + self.link_path.symlink_to(t2_path) + broken_link_path.symlink_to('broken') + broken_link2_path.symlink_to(self.cls('tmp3', 'broken')) + self.sub2_tree = (self.sub2_path, [], ["broken_link", "broken_link2", "link", "tmp3"]) + else: + self.sub2_tree = (self.sub2_path, [], ["tmp3"]) + + def test_walk_topdown(self): + self.setUpWalk() + walker = self.walk_path.walk() + entry = next(walker) + entry[1].sort() # Ensure we visit SUB1 before SUB2 + self.assertEqual(entry, (self.walk_path, ["SUB1", "SUB2"], ["tmp1"])) + entry = next(walker) + self.assertEqual(entry, (self.sub1_path, ["SUB11"], ["tmp2"])) + entry = next(walker) + self.assertEqual(entry, (self.sub11_path, [], [])) + entry = next(walker) + entry[1].sort() + entry[2].sort() + self.assertEqual(entry, self.sub2_tree) + with self.assertRaises(StopIteration): + next(walker) + + def test_walk_prune(self): + self.setUpWalk() + # Prune the search. + all = [] + for root, dirs, files in self.walk_path.walk(): + all.append((root, dirs, files)) + if 'SUB1' in dirs: + # Note that this also mutates the dirs we appended to all! + dirs.remove('SUB1') + + self.assertEqual(len(all), 2) + self.assertEqual(all[0], (self.walk_path, ["SUB2"], ["tmp1"])) + + all[1][-1].sort() + all[1][1].sort() + self.assertEqual(all[1], self.sub2_tree) + + def test_walk_bottom_up(self): + self.setUpWalk() + seen_testfn = seen_sub1 = seen_sub11 = seen_sub2 = False + for path, dirnames, filenames in self.walk_path.walk(top_down=False): + if path == self.walk_path: + self.assertFalse(seen_testfn) + self.assertTrue(seen_sub1) + self.assertTrue(seen_sub2) + self.assertEqual(sorted(dirnames), ["SUB1", "SUB2"]) + self.assertEqual(filenames, ["tmp1"]) + seen_testfn = True + elif path == self.sub1_path: + self.assertFalse(seen_testfn) + self.assertFalse(seen_sub1) + self.assertTrue(seen_sub11) + self.assertEqual(dirnames, ["SUB11"]) + self.assertEqual(filenames, ["tmp2"]) + seen_sub1 = True + elif path == self.sub11_path: + self.assertFalse(seen_sub1) + self.assertFalse(seen_sub11) + self.assertEqual(dirnames, []) + self.assertEqual(filenames, []) + seen_sub11 = True + elif path == self.sub2_path: + self.assertFalse(seen_testfn) + self.assertFalse(seen_sub2) + self.assertEqual(sorted(dirnames), sorted(self.sub2_tree[1])) + self.assertEqual(sorted(filenames), sorted(self.sub2_tree[2])) + seen_sub2 = True + else: + raise AssertionError(f"Unexpected path: {path}") + self.assertTrue(seen_testfn) + + def test_walk_follow_symlinks(self): + if not self.can_symlink: + self.skipTest("symlinks required") + self.setUpWalk() + walk_it = self.walk_path.walk(follow_symlinks=True) + for root, dirs, files in walk_it: + if root == self.link_path: + self.assertEqual(dirs, []) + self.assertEqual(files, ["tmp4"]) + break + else: + self.fail("Didn't follow symlink with follow_symlinks=True") + + def test_walk_symlink_location(self): + if not self.can_symlink: + self.skipTest("symlinks required") + self.setUpWalk() + # Tests whether symlinks end up in filenames or dirnames depending + # on the `follow_symlinks` argument. + walk_it = self.walk_path.walk(follow_symlinks=False) + for root, dirs, files in walk_it: + if root == self.sub2_path: + self.assertIn("link", files) + break + else: + self.fail("symlink not found") + + walk_it = self.walk_path.walk(follow_symlinks=True) + for root, dirs, files in walk_it: + if root == self.sub2_path: + self.assertIn("link", dirs) + break + else: + self.fail("symlink not found") + + def test_walk_above_recursion_limit(self): + recursion_limit = 40 + # directory_depth > recursion_limit + directory_depth = recursion_limit + 10 + base = self.cls(BASE, 'deep') + path = base.joinpath(*(['d'] * directory_depth)) + path.mkdir(parents=True) + + with set_recursion_limit(recursion_limit): + list(base.walk()) + list(base.walk(top_down=False)) + +class DummyPathWithSymlinks(DummyPath): + def readlink(self): + path = str(self.parent.resolve() / self.name) + if path in self._symlinks: + return self.with_segments(self._symlinks[path]) + elif path in self._files or path in self._directories: + raise OSError(errno.EINVAL, "Not a symlink", path) + else: + raise FileNotFoundError(errno.ENOENT, "File not found", path) + + def symlink_to(self, target, target_is_directory=False): + self._directories[str(self.parent)].add(self.name) + self._symlinks[str(self)] = str(target) + + +class DummyPathWithSymlinksTest(DummyPathTest): + cls = DummyPathWithSymlinks + can_symlink = True + + +if __name__ == "__main__": + unittest.main() diff --git a/Makefile.pre.in b/Makefile.pre.in index 5fb6ffc4e8f0cf..195fc0ddddecd3 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -2272,6 +2272,7 @@ TESTSUBDIRS= idlelib/idle_test \ test/test_importlib/source \ test/test_json \ test/test_module \ + test/test_pathlib \ test/test_peg_generator \ test/test_sqlite3 \ test/test_tkinter \ From 2f0ec7fa9450caeac820a6dc819d17d14fd16a4b Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Sun, 17 Dec 2023 00:07:32 +0000 Subject: [PATCH 46/62] GH-110109: pathlib tests: store base directory as test class attribute (#113221) Store the test base directory as a class attribute named `base` rather than module constants named `BASE`. The base directory is a local file path, and therefore not ideally suited to the pathlib ABC tests. In a future commit we'll change its value in `test_pathlib_abc.py` such that it points to a totally fictitious path, which will help to ensure we're not touching the local filesystem. --- Lib/test/test_pathlib/test_pathlib.py | 167 +++++++------- Lib/test/test_pathlib/test_pathlib_abc.py | 265 +++++++++++----------- 2 files changed, 210 insertions(+), 222 deletions(-) diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index 304786fc6f7fd9..00cfdd37e568a6 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -23,11 +23,6 @@ grp = pwd = None -# Make sure any symbolic links in the base test path are resolved. -BASE = os.path.realpath(TESTFN) -join = lambda *x: os.path.join(BASE, *x) -rel_join = lambda *x: os.path.join(TESTFN, *x) - only_nt = unittest.skipIf(os.name != 'nt', 'test requires a Windows-compatible system') only_posix = unittest.skipIf(os.name == 'nt', @@ -937,11 +932,11 @@ class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest): def setUp(self): super().setUp() - os.chmod(join('dirE'), 0) + os.chmod(self.pathmod.join(self.base, 'dirE'), 0) def tearDown(self): - os.chmod(join('dirE'), 0o777) - os_helper.rmtree(BASE) + os.chmod(self.pathmod.join(self.base, 'dirE'), 0o777) + os_helper.rmtree(self.base) def tempdir(self): d = os_helper._longpath(tempfile.mkdtemp(suffix='-dirD', @@ -978,23 +973,23 @@ def test_absolute_common(self): P = self.cls with mock.patch("os.getcwd") as getcwd: - getcwd.return_value = BASE + getcwd.return_value = self.base # Simple relative paths. - self.assertEqual(str(P().absolute()), BASE) - self.assertEqual(str(P('.').absolute()), BASE) - self.assertEqual(str(P('a').absolute()), os.path.join(BASE, 'a')) - self.assertEqual(str(P('a', 'b', 'c').absolute()), os.path.join(BASE, 'a', 'b', 'c')) + self.assertEqual(str(P().absolute()), self.base) + self.assertEqual(str(P('.').absolute()), self.base) + self.assertEqual(str(P('a').absolute()), os.path.join(self.base, 'a')) + self.assertEqual(str(P('a', 'b', 'c').absolute()), os.path.join(self.base, 'a', 'b', 'c')) # Symlinks should not be resolved. - self.assertEqual(str(P('linkB', 'fileB').absolute()), os.path.join(BASE, 'linkB', 'fileB')) - self.assertEqual(str(P('brokenLink').absolute()), os.path.join(BASE, 'brokenLink')) - self.assertEqual(str(P('brokenLinkLoop').absolute()), os.path.join(BASE, 'brokenLinkLoop')) + self.assertEqual(str(P('linkB', 'fileB').absolute()), os.path.join(self.base, 'linkB', 'fileB')) + self.assertEqual(str(P('brokenLink').absolute()), os.path.join(self.base, 'brokenLink')) + self.assertEqual(str(P('brokenLinkLoop').absolute()), os.path.join(self.base, 'brokenLinkLoop')) # '..' entries should be preserved and not normalised. - self.assertEqual(str(P('..').absolute()), os.path.join(BASE, '..')) - self.assertEqual(str(P('a', '..').absolute()), os.path.join(BASE, 'a', '..')) - self.assertEqual(str(P('..', 'b').absolute()), os.path.join(BASE, '..', 'b')) + self.assertEqual(str(P('..').absolute()), os.path.join(self.base, '..')) + self.assertEqual(str(P('a', '..').absolute()), os.path.join(self.base, 'a', '..')) + self.assertEqual(str(P('..', 'b').absolute()), os.path.join(self.base, '..', 'b')) def _test_home(self, p): q = self.cls(os.path.expanduser('~')) @@ -1011,11 +1006,11 @@ def test_home(self): self._test_home(self.cls.home()) env.clear() - env['USERPROFILE'] = os.path.join(BASE, 'userprofile') + env['USERPROFILE'] = os.path.join(self.base, 'userprofile') self._test_home(self.cls.home()) # bpo-38883: ignore `HOME` when set on windows - env['HOME'] = os.path.join(BASE, 'home') + env['HOME'] = os.path.join(self.base, 'home') self._test_home(self.cls.home()) @unittest.skipIf(is_wasi, "WASI has no user accounts.") @@ -1042,7 +1037,7 @@ def __init__(self, *pathsegments, session_id): def with_segments(self, *pathsegments): return type(self)(*pathsegments, session_id=self.session_id) - p = P(BASE, session_id=42) + p = P(self.base, session_id=42) self.assertEqual(42, p.absolute().session_id) self.assertEqual(42, p.resolve().session_id) if not is_wasi: # WASI has no user accounts. @@ -1061,7 +1056,7 @@ def with_segments(self, *pathsegments): self.assertEqual(42, dirpath.session_id) def test_open_unbuffered(self): - p = self.cls(BASE) + p = self.cls(self.base) with (p / 'fileA').open('rb', buffering=0) as f: self.assertIsInstance(f, io.RawIOBase) self.assertEqual(f.read().strip(), b"this is file A") @@ -1070,15 +1065,15 @@ def test_resolve_nonexist_relative_issue38671(self): p = self.cls('non', 'exist') old_cwd = os.getcwd() - os.chdir(BASE) + os.chdir(self.base) try: - self.assertEqual(p.resolve(), self.cls(BASE, p)) + self.assertEqual(p.resolve(), self.cls(self.base, p)) finally: os.chdir(old_cwd) @os_helper.skip_unless_working_chmod def test_chmod(self): - p = self.cls(BASE) / 'fileA' + p = self.cls(self.base) / 'fileA' mode = p.stat().st_mode # Clear writable bit. new_mode = mode & ~0o222 @@ -1093,7 +1088,7 @@ def test_chmod(self): @only_posix @os_helper.skip_unless_working_chmod def test_chmod_follow_symlinks_true(self): - p = self.cls(BASE) / 'linkA' + p = self.cls(self.base) / 'linkA' q = p.resolve() mode = q.stat().st_mode # Clear writable bit. @@ -1116,7 +1111,7 @@ def _get_pw_name_or_skip_test(self, uid): @unittest.skipUnless(pwd, "the pwd module is needed for this test") def test_owner(self): - p = self.cls(BASE) / 'fileA' + p = self.cls(self.base) / 'fileA' expected_uid = p.stat().st_uid expected_name = self._get_pw_name_or_skip_test(expected_uid) @@ -1129,8 +1124,8 @@ def test_owner_no_follow_symlinks(self): if len(all_users) < 2: self.skipTest("test needs more than one user") - target = self.cls(BASE) / 'fileA' - link = self.cls(BASE) / 'linkA' + target = self.cls(self.base) / 'fileA' + link = self.cls(self.base) / 'linkA' uid_1, uid_2 = all_users[:2] os.chown(target, uid_1, -1) @@ -1151,7 +1146,7 @@ def _get_gr_name_or_skip_test(self, gid): @unittest.skipUnless(grp, "the grp module is needed for this test") def test_group(self): - p = self.cls(BASE) / 'fileA' + p = self.cls(self.base) / 'fileA' expected_gid = p.stat().st_gid expected_name = self._get_gr_name_or_skip_test(expected_gid) @@ -1164,8 +1159,8 @@ def test_group_no_follow_symlinks(self): if len(all_groups) < 2: self.skipTest("test needs more than one group") - target = self.cls(BASE) / 'fileA' - link = self.cls(BASE) / 'linkA' + target = self.cls(self.base) / 'fileA' + link = self.cls(self.base) / 'linkA' gid_1, gid_2 = all_groups[:2] os.chown(target, -1, gid_1) @@ -1178,18 +1173,18 @@ def test_group_no_follow_symlinks(self): self.assertEqual(expected_name, link.group(follow_symlinks=False)) def test_unlink(self): - p = self.cls(BASE) / 'fileA' + p = self.cls(self.base) / 'fileA' p.unlink() self.assertFileNotFound(p.stat) self.assertFileNotFound(p.unlink) def test_unlink_missing_ok(self): - p = self.cls(BASE) / 'fileAAA' + p = self.cls(self.base) / 'fileAAA' self.assertFileNotFound(p.unlink) p.unlink(missing_ok=True) def test_rmdir(self): - p = self.cls(BASE) / 'dirA' + p = self.cls(self.base) / 'dirA' for q in p.iterdir(): q.unlink() p.rmdir() @@ -1198,7 +1193,7 @@ def test_rmdir(self): @unittest.skipUnless(hasattr(os, "link"), "os.link() is not present") def test_hardlink_to(self): - P = self.cls(BASE) + P = self.cls(self.base) target = P / 'fileA' size = target.stat().st_size # linking to another path. @@ -1209,14 +1204,14 @@ def test_hardlink_to(self): self.assertTrue(target.exists()) # Linking to a str of a relative path. link2 = P / 'dirA' / 'fileAAA' - target2 = rel_join('fileA') + target2 = self.pathmod.join(TESTFN, 'fileA') link2.hardlink_to(target2) self.assertEqual(os.stat(target2).st_size, size) self.assertTrue(link2.exists()) @unittest.skipIf(hasattr(os, "link"), "os.link() is present") def test_hardlink_to_unsupported(self): - P = self.cls(BASE) + P = self.cls(self.base) p = P / 'fileA' # linking to another path. q = P / 'dirA' / 'fileAA' @@ -1224,7 +1219,7 @@ def test_hardlink_to_unsupported(self): q.hardlink_to(p) def test_rename(self): - P = self.cls(BASE) + P = self.cls(self.base) p = P / 'fileA' size = p.stat().st_size # Renaming to another path. @@ -1234,14 +1229,14 @@ def test_rename(self): self.assertEqual(q.stat().st_size, size) self.assertFileNotFound(p.stat) # Renaming to a str of a relative path. - r = rel_join('fileAAA') + r = self.pathmod.join(TESTFN, 'fileAAA') renamed_q = q.rename(r) self.assertEqual(renamed_q, self.cls(r)) self.assertEqual(os.stat(r).st_size, size) self.assertFileNotFound(q.stat) def test_replace(self): - P = self.cls(BASE) + P = self.cls(self.base) p = P / 'fileA' size = p.stat().st_size # Replacing a non-existing path. @@ -1251,14 +1246,14 @@ def test_replace(self): self.assertEqual(q.stat().st_size, size) self.assertFileNotFound(p.stat) # Replacing another (existing) path. - r = rel_join('dirB', 'fileB') + r = self.pathmod.join(TESTFN, 'dirB', 'fileB') replaced_q = q.replace(r) self.assertEqual(replaced_q, self.cls(r)) self.assertEqual(os.stat(r).st_size, size) self.assertFileNotFound(q.stat) def test_touch_common(self): - P = self.cls(BASE) + P = self.cls(self.base) p = P / 'newfileA' self.assertFalse(p.exists()) p.touch() @@ -1282,14 +1277,14 @@ def test_touch_common(self): self.assertRaises(OSError, p.touch, exist_ok=False) def test_touch_nochange(self): - P = self.cls(BASE) + P = self.cls(self.base) p = P / 'fileA' p.touch() with p.open('rb') as f: self.assertEqual(f.read().strip(), b"this is file A") def test_mkdir(self): - P = self.cls(BASE) + P = self.cls(self.base) p = P / 'newdirA' self.assertFalse(p.exists()) p.mkdir() @@ -1301,7 +1296,7 @@ def test_mkdir(self): def test_mkdir_parents(self): # Creating a chain of directories. - p = self.cls(BASE, 'newdirB', 'newdirC') + p = self.cls(self.base, 'newdirB', 'newdirC') self.assertFalse(p.exists()) with self.assertRaises(OSError) as cm: p.mkdir() @@ -1314,7 +1309,7 @@ def test_mkdir_parents(self): self.assertEqual(cm.exception.errno, errno.EEXIST) # Test `mode` arg. mode = stat.S_IMODE(p.stat().st_mode) # Default mode. - p = self.cls(BASE, 'newdirD', 'newdirE') + p = self.cls(self.base, 'newdirD', 'newdirE') p.mkdir(0o555, parents=True) self.assertTrue(p.exists()) self.assertTrue(p.is_dir()) @@ -1325,7 +1320,7 @@ def test_mkdir_parents(self): self.assertEqual(stat.S_IMODE(p.parent.stat().st_mode), mode) def test_mkdir_exist_ok(self): - p = self.cls(BASE, 'dirB') + p = self.cls(self.base, 'dirB') st_ctime_first = p.stat().st_ctime self.assertTrue(p.exists()) self.assertTrue(p.is_dir()) @@ -1337,7 +1332,7 @@ def test_mkdir_exist_ok(self): self.assertEqual(p.stat().st_ctime, st_ctime_first) def test_mkdir_exist_ok_with_parent(self): - p = self.cls(BASE, 'dirC') + p = self.cls(self.base, 'dirC') self.assertTrue(p.exists()) with self.assertRaises(FileExistsError) as cm: p.mkdir() @@ -1371,7 +1366,7 @@ def test_mkdir_with_unknown_drive(self): (p / 'child' / 'path').mkdir(parents=True) def test_mkdir_with_child_file(self): - p = self.cls(BASE, 'dirB', 'fileB') + p = self.cls(self.base, 'dirB', 'fileB') self.assertTrue(p.exists()) # An exception is raised when the last path component is an existing # regular file, regardless of whether exist_ok is true or not. @@ -1383,7 +1378,7 @@ def test_mkdir_with_child_file(self): self.assertEqual(cm.exception.errno, errno.EEXIST) def test_mkdir_no_parents_file(self): - p = self.cls(BASE, 'fileA') + p = self.cls(self.base, 'fileA') self.assertTrue(p.exists()) # An exception is raised when the last path component is an existing # regular file, regardless of whether exist_ok is true or not. @@ -1396,7 +1391,7 @@ def test_mkdir_no_parents_file(self): def test_mkdir_concurrent_parent_creation(self): for pattern_num in range(32): - p = self.cls(BASE, 'dirCPC%d' % pattern_num) + p = self.cls(self.base, 'dirCPC%d' % pattern_num) self.assertFalse(p.exists()) real_mkdir = os.mkdir @@ -1427,7 +1422,7 @@ def my_mkdir(path, mode=0o777): def test_symlink_to(self): if not self.can_symlink: self.skipTest("symlinks required") - P = self.cls(BASE) + P = self.cls(self.base) target = P / 'fileA' # Symlinking a path target. link = P / 'dirA' / 'linkAA' @@ -1451,7 +1446,7 @@ def test_symlink_to(self): @unittest.skipIf(hasattr(os, "symlink"), "os.symlink() is present") def test_symlink_to_unsupported(self): - P = self.cls(BASE) + P = self.cls(self.base) p = P / 'fileA' # linking to another path. q = P / 'dirA' / 'fileAA' @@ -1459,7 +1454,7 @@ def test_symlink_to_unsupported(self): q.symlink_to(p) def test_is_junction(self): - P = self.cls(BASE) + P = self.cls(self.base) with mock.patch.object(P.pathmod, 'isjunction'): self.assertEqual(P.is_junction(), P.pathmod.isjunction.return_value) @@ -1469,7 +1464,7 @@ def test_is_junction(self): @unittest.skipIf(sys.platform == "vxworks", "fifo requires special path on VxWorks") def test_is_fifo_true(self): - P = self.cls(BASE, 'myfifo') + P = self.cls(self.base, 'myfifo') try: os.mkfifo(str(P)) except PermissionError as e: @@ -1477,8 +1472,8 @@ def test_is_fifo_true(self): self.assertTrue(P.is_fifo()) self.assertFalse(P.is_socket()) self.assertFalse(P.is_file()) - self.assertIs(self.cls(BASE, 'myfifo\udfff').is_fifo(), False) - self.assertIs(self.cls(BASE, 'myfifo\x00').is_fifo(), False) + self.assertIs(self.cls(self.base, 'myfifo\udfff').is_fifo(), False) + self.assertIs(self.cls(self.base, 'myfifo\x00').is_fifo(), False) @unittest.skipUnless(hasattr(socket, "AF_UNIX"), "Unix sockets required") @unittest.skipIf( @@ -1488,7 +1483,7 @@ def test_is_fifo_true(self): is_wasi, "Cannot create socket on WASI." ) def test_is_socket_true(self): - P = self.cls(BASE, 'mysock') + P = self.cls(self.base, 'mysock') sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.addCleanup(sock.close) try: @@ -1500,8 +1495,8 @@ def test_is_socket_true(self): self.assertTrue(P.is_socket()) self.assertFalse(P.is_fifo()) self.assertFalse(P.is_file()) - self.assertIs(self.cls(BASE, 'mysock\udfff').is_socket(), False) - self.assertIs(self.cls(BASE, 'mysock\x00').is_socket(), False) + self.assertIs(self.cls(self.base, 'mysock\udfff').is_socket(), False) + self.assertIs(self.cls(self.base, 'mysock\x00').is_socket(), False) def test_is_char_device_true(self): # Under Unix, /dev/null should generally be a char device. @@ -1573,7 +1568,7 @@ def test_walk_bad_dir(self): def test_walk_many_open_files(self): depth = 30 - base = self.cls(BASE, 'deep') + base = self.cls(self.base, 'deep') path = self.cls(base, *(['d']*depth)) path.mkdir(parents=True) @@ -1615,15 +1610,15 @@ def test_absolute(self): def test_open_mode(self): old_mask = os.umask(0) self.addCleanup(os.umask, old_mask) - p = self.cls(BASE) + p = self.cls(self.base) with (p / 'new_file').open('wb'): pass - st = os.stat(join('new_file')) + st = os.stat(self.pathmod.join(self.base, 'new_file')) self.assertEqual(stat.S_IMODE(st.st_mode), 0o666) os.umask(0o022) with (p / 'other_new_file').open('wb'): pass - st = os.stat(join('other_new_file')) + st = os.stat(self.pathmod.join(self.base, 'other_new_file')) self.assertEqual(stat.S_IMODE(st.st_mode), 0o644) def test_resolve_root(self): @@ -1642,31 +1637,31 @@ def test_resolve_root(self): def test_touch_mode(self): old_mask = os.umask(0) self.addCleanup(os.umask, old_mask) - p = self.cls(BASE) + p = self.cls(self.base) (p / 'new_file').touch() - st = os.stat(join('new_file')) + st = os.stat(self.pathmod.join(self.base, 'new_file')) self.assertEqual(stat.S_IMODE(st.st_mode), 0o666) os.umask(0o022) (p / 'other_new_file').touch() - st = os.stat(join('other_new_file')) + st = os.stat(self.pathmod.join(self.base, 'other_new_file')) self.assertEqual(stat.S_IMODE(st.st_mode), 0o644) (p / 'masked_new_file').touch(mode=0o750) - st = os.stat(join('masked_new_file')) + st = os.stat(self.pathmod.join(self.base, 'masked_new_file')) self.assertEqual(stat.S_IMODE(st.st_mode), 0o750) def test_glob(self): P = self.cls - p = P(BASE) + p = P(self.base) given = set(p.glob("FILEa")) - expect = set() if not os_helper.fs_is_case_insensitive(BASE) else given + expect = set() if not os_helper.fs_is_case_insensitive(self.base) else given self.assertEqual(given, expect) self.assertEqual(set(p.glob("FILEa*")), set()) def test_rglob(self): P = self.cls - p = P(BASE, "dirC") + p = P(self.base, "dirC") given = set(p.rglob("FILEd")) - expect = set() if not os_helper.fs_is_case_insensitive(BASE) else given + expect = set() if not os_helper.fs_is_case_insensitive(self.base) else given self.assertEqual(given, expect) self.assertEqual(set(p.rglob("FILEd*")), set()) @@ -1797,17 +1792,17 @@ def test_absolute(self): self.assertEqual(str(P('a', 'b', 'c').absolute()), os.path.join(share, 'a', 'b', 'c')) - drive = os.path.splitdrive(BASE)[0] - with os_helper.change_cwd(BASE): + drive = os.path.splitdrive(self.base)[0] + with os_helper.change_cwd(self.base): # Relative path with root self.assertEqual(str(P('\\').absolute()), drive + '\\') self.assertEqual(str(P('\\foo').absolute()), drive + '\\foo') # Relative path on current drive - self.assertEqual(str(P(drive).absolute()), BASE) - self.assertEqual(str(P(drive + 'foo').absolute()), os.path.join(BASE, 'foo')) + self.assertEqual(str(P(drive).absolute()), self.base) + self.assertEqual(str(P(drive + 'foo').absolute()), os.path.join(self.base, 'foo')) - with os_helper.subst_drive(BASE) as other_drive: + with os_helper.subst_drive(self.base) as other_drive: # Set the working directory on the substitute drive saved_cwd = os.getcwd() other_cwd = f'{other_drive}\\dirA' @@ -1820,18 +1815,18 @@ def test_absolute(self): def test_glob(self): P = self.cls - p = P(BASE) - self.assertEqual(set(p.glob("FILEa")), { P(BASE, "fileA") }) - self.assertEqual(set(p.glob("*a\\")), { P(BASE, "dirA/") }) - self.assertEqual(set(p.glob("F*a")), { P(BASE, "fileA") }) + p = P(self.base) + self.assertEqual(set(p.glob("FILEa")), { P(self.base, "fileA") }) + self.assertEqual(set(p.glob("*a\\")), { P(self.base, "dirA/") }) + self.assertEqual(set(p.glob("F*a")), { P(self.base, "fileA") }) self.assertEqual(set(map(str, p.glob("FILEa"))), {f"{p}\\fileA"}) self.assertEqual(set(map(str, p.glob("F*a"))), {f"{p}\\fileA"}) def test_rglob(self): P = self.cls - p = P(BASE, "dirC") - self.assertEqual(set(p.rglob("FILEd")), { P(BASE, "dirC/dirD/fileD") }) - self.assertEqual(set(p.rglob("*\\")), { P(BASE, "dirC/dirD/") }) + p = P(self.base, "dirC") + self.assertEqual(set(p.rglob("FILEd")), { P(self.base, "dirC/dirD/fileD") }) + self.assertEqual(set(p.rglob("*\\")), { P(self.base, "dirC/dirD/") }) self.assertEqual(set(map(str, p.rglob("FILEd"))), {f"{p}\\dirD\\fileD"}) def test_expanduser(self): diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index 61ed3cb6a4a7f8..a272973d9c1d61 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -18,16 +18,6 @@ def test_is_notimplemented(self): self.assertTrue(isinstance(pathlib.UnsupportedOperation(), NotImplementedError)) -# Make sure any symbolic links in the base test path are resolved. -BASE = os.path.realpath(TESTFN) -join = lambda *x: os.path.join(BASE, *x) - -only_nt = unittest.skipIf(os.name != 'nt', - 'test requires a Windows-compatible system') -only_posix = unittest.skipIf(os.name == 'nt', - 'test requires a POSIX-compatible system') - - # # Tests for the pure classes. # @@ -62,6 +52,9 @@ def __hash__(self): class DummyPurePathTest(unittest.TestCase): cls = DummyPurePath + # Make sure any symbolic links in the base test path are resolved. + base = os.path.realpath(TESTFN) + # Keys are canonical paths, values are list of tuples of arguments # supposed to produce equal paths. equivalences = { @@ -859,7 +852,7 @@ class DummyPathTest(DummyPurePathTest): cls = DummyPath can_symlink = False - # (BASE) + # (self.base) # | # |-- brokenLink -> non-existing # |-- dirA @@ -882,7 +875,7 @@ class DummyPathTest(DummyPurePathTest): def setUp(self): super().setUp() pathmod = self.cls.pathmod - p = self.cls(BASE) + p = self.cls(self.base) p.mkdir(parents=True) p.joinpath('dirA').mkdir() p.joinpath('dirB').mkdir() @@ -914,7 +907,7 @@ def tearDown(self): cls._symlinks.clear() def tempdir(self): - path = self.cls(BASE).with_name('tmp-dirD') + path = self.cls(self.base).with_name('tmp-dirD') path.mkdir() return path @@ -927,8 +920,8 @@ def assertEqualNormCase(self, path_a, path_b): self.assertEqual(os.path.normcase(path_a), os.path.normcase(path_b)) def test_samefile(self): - fileA_path = os.path.join(BASE, 'fileA') - fileB_path = os.path.join(BASE, 'dirB', 'fileB') + fileA_path = os.path.join(self.base, 'fileA') + fileB_path = os.path.join(self.base, 'dirB', 'fileB') p = self.cls(fileA_path) pp = self.cls(fileA_path) q = self.cls(fileB_path) @@ -937,7 +930,7 @@ def test_samefile(self): self.assertFalse(p.samefile(fileB_path)) self.assertFalse(p.samefile(q)) # Test the non-existent file case - non_existent = os.path.join(BASE, 'foo') + non_existent = os.path.join(self.base, 'foo') r = self.cls(non_existent) self.assertRaises(FileNotFoundError, p.samefile, r) self.assertRaises(FileNotFoundError, p.samefile, non_existent) @@ -953,7 +946,7 @@ def test_empty_path(self): def test_exists(self): P = self.cls - p = P(BASE) + p = P(self.base) self.assertIs(True, p.exists()) self.assertIs(True, (p / 'dirA').exists()) self.assertIs(True, (p / 'fileA').exists()) @@ -967,11 +960,11 @@ def test_exists(self): self.assertIs(True, (p / 'brokenLink').exists(follow_symlinks=False)) self.assertIs(False, (p / 'foo').exists()) self.assertIs(False, P('/xyzzy').exists()) - self.assertIs(False, P(BASE + '\udfff').exists()) - self.assertIs(False, P(BASE + '\x00').exists()) + self.assertIs(False, P(self.base + '\udfff').exists()) + self.assertIs(False, P(self.base + '\x00').exists()) def test_open_common(self): - p = self.cls(BASE) + p = self.cls(self.base) with (p / 'fileA').open('r') as f: self.assertIsInstance(f, io.TextIOBase) self.assertEqual(f.read(), "this is file A\n") @@ -980,7 +973,7 @@ def test_open_common(self): self.assertEqual(f.read().strip(), b"this is file A") def test_read_write_bytes(self): - p = self.cls(BASE) + p = self.cls(self.base) (p / 'fileA').write_bytes(b'abcdefg') self.assertEqual((p / 'fileA').read_bytes(), b'abcdefg') # Check that trying to write str does not truncate the file. @@ -988,7 +981,7 @@ def test_read_write_bytes(self): self.assertEqual((p / 'fileA').read_bytes(), b'abcdefg') def test_read_write_text(self): - p = self.cls(BASE) + p = self.cls(self.base) (p / 'fileA').write_text('äbcdefg', encoding='latin-1') self.assertEqual((p / 'fileA').read_text( encoding='utf-8', errors='ignore'), 'bcdefg') @@ -997,7 +990,7 @@ def test_read_write_text(self): self.assertEqual((p / 'fileA').read_text(encoding='latin-1'), 'äbcdefg') def test_read_text_with_newlines(self): - p = self.cls(BASE) + p = self.cls(self.base) # Check that `\n` character change nothing (p / 'fileA').write_bytes(b'abcde\r\nfghlk\n\rmnopq') self.assertEqual((p / 'fileA').read_text(newline='\n'), @@ -1012,7 +1005,7 @@ def test_read_text_with_newlines(self): 'abcde\r\nfghlk\n\rmnopq') def test_write_text_with_newlines(self): - p = self.cls(BASE) + p = self.cls(self.base) # Check that `\n` character change nothing (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\n') self.assertEqual((p / 'fileA').read_bytes(), @@ -1033,27 +1026,27 @@ def test_write_text_with_newlines(self): def test_iterdir(self): P = self.cls - p = P(BASE) + p = P(self.base) it = p.iterdir() paths = set(it) expected = ['dirA', 'dirB', 'dirC', 'dirE', 'fileA'] if self.can_symlink: expected += ['linkA', 'linkB', 'brokenLink', 'brokenLinkLoop'] - self.assertEqual(paths, { P(BASE, q) for q in expected }) + self.assertEqual(paths, { P(self.base, q) for q in expected }) def test_iterdir_symlink(self): if not self.can_symlink: self.skipTest("symlinks required") # __iter__ on a symlink to a directory. P = self.cls - p = P(BASE, 'linkB') + p = P(self.base, 'linkB') paths = set(p.iterdir()) - expected = { P(BASE, 'linkB', q) for q in ['fileB', 'linkD'] } + expected = { P(self.base, 'linkB', q) for q in ['fileB', 'linkD'] } self.assertEqual(paths, expected) def test_iterdir_nodir(self): # __iter__ on something that is not a directory. - p = self.cls(BASE, 'fileA') + p = self.cls(self.base, 'fileA') with self.assertRaises(OSError) as cm: p.iterdir() # ENOENT or EINVAL under Windows, ENOTDIR otherwise @@ -1063,9 +1056,9 @@ def test_iterdir_nodir(self): def test_glob_common(self): def _check(glob, expected): - self.assertEqual(set(glob), { P(BASE, q) for q in expected }) + self.assertEqual(set(glob), { P(self.base, q) for q in expected }) P = self.cls - p = P(BASE) + p = P(self.base) it = p.glob("fileA") self.assertIsInstance(it, collections.abc.Iterator) _check(it, ["fileA"]) @@ -1101,9 +1094,9 @@ def test_glob_case_sensitive(self): P = self.cls def _check(path, pattern, case_sensitive, expected): actual = {str(q) for q in path.glob(pattern, case_sensitive=case_sensitive)} - expected = {str(P(BASE, q)) for q in expected} + expected = {str(P(self.base, q)) for q in expected} self.assertEqual(actual, expected) - path = P(BASE) + path = P(self.base) _check(path, "DIRB/FILE*", True, []) _check(path, "DIRB/FILE*", False, ["dirB/fileB"]) _check(path, "dirb/file*", True, []) @@ -1115,9 +1108,9 @@ def test_glob_follow_symlinks_common(self): def _check(path, glob, expected): actual = {path for path in path.glob(glob, follow_symlinks=True) if "linkD" not in path.parent.parts} # exclude symlink loop. - self.assertEqual(actual, { P(BASE, q) for q in expected }) + self.assertEqual(actual, { P(self.base, q) for q in expected }) P = self.cls - p = P(BASE) + p = P(self.base) _check(p, "fileB", []) _check(p, "dir*/file*", ["dirB/fileB", "dirC/fileC"]) _check(p, "*A", ["dirA", "fileA", "linkA"]) @@ -1140,9 +1133,9 @@ def test_glob_no_follow_symlinks_common(self): self.skipTest("symlinks required") def _check(path, glob, expected): actual = {path for path in path.glob(glob, follow_symlinks=False)} - self.assertEqual(actual, { P(BASE, q) for q in expected }) + self.assertEqual(actual, { P(self.base, q) for q in expected }) P = self.cls - p = P(BASE) + p = P(self.base) _check(p, "fileB", []) _check(p, "dir*/file*", ["dirB/fileB", "dirC/fileC"]) _check(p, "*A", ["dirA", "fileA", "linkA"]) @@ -1160,9 +1153,9 @@ def _check(path, glob, expected): def test_rglob_common(self): def _check(glob, expected): - self.assertEqual(set(glob), {P(BASE, q) for q in expected}) + self.assertEqual(set(glob), {P(self.base, q) for q in expected}) P = self.cls - p = P(BASE) + p = P(self.base) it = p.rglob("fileA") self.assertIsInstance(it, collections.abc.Iterator) _check(it, ["fileA"]) @@ -1187,7 +1180,7 @@ def _check(glob, expected): ]) _check(p.rglob(""), ["./", "dirA/", "dirB/", "dirC/", "dirE/", "dirC/dirD/"]) - p = P(BASE, "dirC") + p = P(self.base, "dirC") _check(p.rglob("*"), ["dirC/fileC", "dirC/novel.txt", "dirC/dirD", "dirC/dirD/fileD"]) _check(p.rglob("file*"), ["dirC/fileC", "dirC/dirD/fileD"]) @@ -1207,9 +1200,9 @@ def test_rglob_follow_symlinks_common(self): def _check(path, glob, expected): actual = {path for path in path.rglob(glob, follow_symlinks=True) if 'linkD' not in path.parent.parts} # exclude symlink loop. - self.assertEqual(actual, { P(BASE, q) for q in expected }) + self.assertEqual(actual, { P(self.base, q) for q in expected }) P = self.cls - p = P(BASE) + p = P(self.base) _check(p, "fileB", ["dirB/fileB", "dirA/linkC/fileB", "linkB/fileB"]) _check(p, "*/fileA", []) _check(p, "*/fileB", ["dirB/fileB", "dirA/linkC/fileB", "linkB/fileB"]) @@ -1220,7 +1213,7 @@ def _check(path, glob, expected): _check(p, "", ["./", "dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/", "dirC/", "dirE/", "dirC/dirD/", "linkB/", "linkB/linkD/"]) - p = P(BASE, "dirC") + p = P(self.base, "dirC") _check(p, "*", ["dirC/fileC", "dirC/novel.txt", "dirC/dirD", "dirC/dirD/fileD"]) _check(p, "file*", ["dirC/fileC", "dirC/dirD/fileD"]) @@ -1236,9 +1229,9 @@ def test_rglob_no_follow_symlinks_common(self): self.skipTest("symlinks required") def _check(path, glob, expected): actual = {path for path in path.rglob(glob, follow_symlinks=False)} - self.assertEqual(actual, { P(BASE, q) for q in expected }) + self.assertEqual(actual, { P(self.base, q) for q in expected }) P = self.cls - p = P(BASE) + p = P(self.base) _check(p, "fileB", ["dirB/fileB"]) _check(p, "*/fileA", []) _check(p, "*/fileB", ["dirB/fileB"]) @@ -1246,7 +1239,7 @@ def _check(path, glob, expected): _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"]) _check(p, "", ["./", "dirA/", "dirB/", "dirC/", "dirE/", "dirC/dirD/"]) - p = P(BASE, "dirC") + p = P(self.base, "dirC") _check(p, "*", ["dirC/fileC", "dirC/novel.txt", "dirC/dirD", "dirC/dirD/fileD"]) _check(p, "file*", ["dirC/fileC", "dirC/dirD/fileD"]) @@ -1262,7 +1255,7 @@ def test_rglob_symlink_loop(self): if not self.can_symlink: self.skipTest("symlinks required") P = self.cls - p = P(BASE) + p = P(self.base) given = set(p.rglob('*')) expect = {'brokenLink', 'dirA', 'dirA/linkC', @@ -1280,7 +1273,7 @@ def test_rglob_symlink_loop(self): def test_glob_many_open_files(self): depth = 30 P = self.cls - p = base = P(BASE) / 'deep' + p = base = P(self.base) / 'deep' p.mkdir() for _ in range(depth): p /= 'd' @@ -1299,30 +1292,30 @@ def test_glob_many_open_files(self): def test_glob_dotdot(self): # ".." is not special in globs. P = self.cls - p = P(BASE) - self.assertEqual(set(p.glob("..")), { P(BASE, "..") }) - self.assertEqual(set(p.glob("../..")), { P(BASE, "..", "..") }) - self.assertEqual(set(p.glob("dirA/..")), { P(BASE, "dirA", "..") }) - self.assertEqual(set(p.glob("dirA/../file*")), { P(BASE, "dirA/../fileA") }) + p = P(self.base) + self.assertEqual(set(p.glob("..")), { P(self.base, "..") }) + self.assertEqual(set(p.glob("../..")), { P(self.base, "..", "..") }) + self.assertEqual(set(p.glob("dirA/..")), { P(self.base, "dirA", "..") }) + self.assertEqual(set(p.glob("dirA/../file*")), { P(self.base, "dirA/../fileA") }) self.assertEqual(set(p.glob("dirA/../file*/..")), set()) self.assertEqual(set(p.glob("../xyzzy")), set()) self.assertEqual(set(p.glob("xyzzy/..")), set()) - self.assertEqual(set(p.glob("/".join([".."] * 50))), { P(BASE, *[".."] * 50)}) + self.assertEqual(set(p.glob("/".join([".."] * 50))), { P(self.base, *[".."] * 50)}) def test_glob_permissions(self): # See bpo-38894 if not self.can_symlink: self.skipTest("symlinks required") P = self.cls - base = P(BASE) / 'permissions' + base = P(self.base) / 'permissions' base.mkdir() for i in range(100): link = base / f"link{i}" if i % 2: - link.symlink_to(P(BASE, "dirE", "nonexistent")) + link.symlink_to(P(self.base, "dirE", "nonexistent")) else: - link.symlink_to(P(BASE, "dirC")) + link.symlink_to(P(self.base, "dirC")) self.assertEqual(len(set(base.glob("*"))), 100) self.assertEqual(len(set(base.glob("*/"))), 50) @@ -1333,7 +1326,7 @@ def test_glob_long_symlink(self): # See gh-87695 if not self.can_symlink: self.skipTest("symlinks required") - base = self.cls(BASE) / 'long_symlink' + base = self.cls(self.base) / 'long_symlink' base.mkdir() bad_link = base / 'bad_link' bad_link.symlink_to("bad" * 200) @@ -1343,7 +1336,7 @@ def test_glob_above_recursion_limit(self): recursion_limit = 50 # directory_depth > recursion_limit directory_depth = recursion_limit + 10 - base = self.cls(BASE, 'deep') + base = self.cls(self.base, 'deep') path = base.joinpath(*(['d'] * directory_depth)) path.mkdir(parents=True) @@ -1352,7 +1345,7 @@ def test_glob_above_recursion_limit(self): def test_glob_recursive_no_trailing_slash(self): P = self.cls - p = P(BASE) + p = P(self.base) with self.assertWarns(FutureWarning): p.glob('**') with self.assertWarns(FutureWarning): @@ -1366,7 +1359,7 @@ def test_glob_recursive_no_trailing_slash(self): def test_readlink(self): if not self.can_symlink: self.skipTest("symlinks required") - P = self.cls(BASE) + P = self.cls(self.base) self.assertEqual((P / 'linkA').readlink(), self.cls('fileA')) self.assertEqual((P / 'brokenLink').readlink(), self.cls('non-existing')) @@ -1377,7 +1370,7 @@ def test_readlink(self): @unittest.skipIf(hasattr(os, "readlink"), "os.readlink() is present") def test_readlink_unsupported(self): - P = self.cls(BASE) + P = self.cls(self.base) p = P / 'fileA' with self.assertRaises(pathlib.UnsupportedOperation): q.readlink(p) @@ -1393,53 +1386,53 @@ def test_resolve_common(self): if not self.can_symlink: self.skipTest("symlinks required") P = self.cls - p = P(BASE, 'foo') + p = P(self.base, 'foo') with self.assertRaises(OSError) as cm: p.resolve(strict=True) self.assertEqual(cm.exception.errno, errno.ENOENT) # Non-strict self.assertEqualNormCase(str(p.resolve(strict=False)), - os.path.join(BASE, 'foo')) - p = P(BASE, 'foo', 'in', 'spam') + os.path.join(self.base, 'foo')) + p = P(self.base, 'foo', 'in', 'spam') self.assertEqualNormCase(str(p.resolve(strict=False)), - os.path.join(BASE, 'foo', 'in', 'spam')) - p = P(BASE, '..', 'foo', 'in', 'spam') + os.path.join(self.base, 'foo', 'in', 'spam')) + p = P(self.base, '..', 'foo', 'in', 'spam') self.assertEqualNormCase(str(p.resolve(strict=False)), os.path.abspath(os.path.join('foo', 'in', 'spam'))) # These are all relative symlinks. - p = P(BASE, 'dirB', 'fileB') + p = P(self.base, 'dirB', 'fileB') self._check_resolve_relative(p, p) - p = P(BASE, 'linkA') - self._check_resolve_relative(p, P(BASE, 'fileA')) - p = P(BASE, 'dirA', 'linkC', 'fileB') - self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB')) - p = P(BASE, 'dirB', 'linkD', 'fileB') - self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB')) + p = P(self.base, 'linkA') + self._check_resolve_relative(p, P(self.base, 'fileA')) + p = P(self.base, 'dirA', 'linkC', 'fileB') + self._check_resolve_relative(p, P(self.base, 'dirB', 'fileB')) + p = P(self.base, 'dirB', 'linkD', 'fileB') + self._check_resolve_relative(p, P(self.base, 'dirB', 'fileB')) # Non-strict - p = P(BASE, 'dirA', 'linkC', 'fileB', 'foo', 'in', 'spam') - self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB', 'foo', 'in', + p = P(self.base, 'dirA', 'linkC', 'fileB', 'foo', 'in', 'spam') + self._check_resolve_relative(p, P(self.base, 'dirB', 'fileB', 'foo', 'in', 'spam'), False) - p = P(BASE, 'dirA', 'linkC', '..', 'foo', 'in', 'spam') + p = P(self.base, 'dirA', 'linkC', '..', 'foo', 'in', 'spam') if os.name == 'nt' and isinstance(p, pathlib.Path): # In Windows, if linkY points to dirB, 'dirA\linkY\..' # resolves to 'dirA' without resolving linkY first. - self._check_resolve_relative(p, P(BASE, 'dirA', 'foo', 'in', + self._check_resolve_relative(p, P(self.base, 'dirA', 'foo', 'in', 'spam'), False) else: # In Posix, if linkY points to dirB, 'dirA/linkY/..' # resolves to 'dirB/..' first before resolving to parent of dirB. - self._check_resolve_relative(p, P(BASE, 'foo', 'in', 'spam'), False) + self._check_resolve_relative(p, P(self.base, 'foo', 'in', 'spam'), False) # Now create absolute symlinks. d = self.tempdir() - P(BASE, 'dirA', 'linkX').symlink_to(d) - P(BASE, str(d), 'linkY').symlink_to(join('dirB')) - p = P(BASE, 'dirA', 'linkX', 'linkY', 'fileB') - self._check_resolve_absolute(p, P(BASE, 'dirB', 'fileB')) + P(self.base, 'dirA', 'linkX').symlink_to(d) + P(self.base, str(d), 'linkY').symlink_to(self.pathmod.join(self.base, 'dirB')) + p = P(self.base, 'dirA', 'linkX', 'linkY', 'fileB') + self._check_resolve_absolute(p, P(self.base, 'dirB', 'fileB')) # Non-strict - p = P(BASE, 'dirA', 'linkX', 'linkY', 'foo', 'in', 'spam') - self._check_resolve_relative(p, P(BASE, 'dirB', 'foo', 'in', 'spam'), + p = P(self.base, 'dirA', 'linkX', 'linkY', 'foo', 'in', 'spam') + self._check_resolve_relative(p, P(self.base, 'dirB', 'foo', 'in', 'spam'), False) - p = P(BASE, 'dirA', 'linkX', 'linkY', '..', 'foo', 'in', 'spam') + p = P(self.base, 'dirA', 'linkX', 'linkY', '..', 'foo', 'in', 'spam') if os.name == 'nt' and isinstance(p, pathlib.Path): # In Windows, if linkY points to dirB, 'dirA\linkY\..' # resolves to 'dirA' without resolving linkY first. @@ -1447,13 +1440,13 @@ def test_resolve_common(self): else: # In Posix, if linkY points to dirB, 'dirA/linkY/..' # resolves to 'dirB/..' first before resolving to parent of dirB. - self._check_resolve_relative(p, P(BASE, 'foo', 'in', 'spam'), False) + self._check_resolve_relative(p, P(self.base, 'foo', 'in', 'spam'), False) def test_resolve_dot(self): # See http://web.archive.org/web/20200623062557/https://bitbucket.org/pitrou/pathlib/issues/9/ if not self.can_symlink: self.skipTest("symlinks required") - p = self.cls(BASE) + p = self.cls(self.base) p.joinpath('0').symlink_to('.', target_is_directory=True) p.joinpath('1').symlink_to(os.path.join('0', '0'), target_is_directory=True) p.joinpath('2').symlink_to(os.path.join('1', '1'), target_is_directory=True) @@ -1476,30 +1469,30 @@ def test_resolve_loop(self): if os.name == 'nt' and issubclass(self.cls, pathlib.Path): self.skipTest("symlink loops work differently with concrete Windows paths") # Loops with relative symlinks. - self.cls(BASE, 'linkX').symlink_to('linkX/inside') - self._check_symlink_loop(BASE, 'linkX') - self.cls(BASE, 'linkY').symlink_to('linkY') - self._check_symlink_loop(BASE, 'linkY') - self.cls(BASE, 'linkZ').symlink_to('linkZ/../linkZ') - self._check_symlink_loop(BASE, 'linkZ') + self.cls(self.base, 'linkX').symlink_to('linkX/inside') + self._check_symlink_loop(self.base, 'linkX') + self.cls(self.base, 'linkY').symlink_to('linkY') + self._check_symlink_loop(self.base, 'linkY') + self.cls(self.base, 'linkZ').symlink_to('linkZ/../linkZ') + self._check_symlink_loop(self.base, 'linkZ') # Non-strict - p = self.cls(BASE, 'linkZ', 'foo') + p = self.cls(self.base, 'linkZ', 'foo') self.assertEqual(p.resolve(strict=False), p) # Loops with absolute symlinks. - self.cls(BASE, 'linkU').symlink_to(join('linkU/inside')) - self._check_symlink_loop(BASE, 'linkU') - self.cls(BASE, 'linkV').symlink_to(join('linkV')) - self._check_symlink_loop(BASE, 'linkV') - self.cls(BASE, 'linkW').symlink_to(join('linkW/../linkW')) - self._check_symlink_loop(BASE, 'linkW') + self.cls(self.base, 'linkU').symlink_to(self.pathmod.join(self.base, 'linkU/inside')) + self._check_symlink_loop(self.base, 'linkU') + self.cls(self.base, 'linkV').symlink_to(self.pathmod.join(self.base, 'linkV')) + self._check_symlink_loop(self.base, 'linkV') + self.cls(self.base, 'linkW').symlink_to(self.pathmod.join(self.base, 'linkW/../linkW')) + self._check_symlink_loop(self.base, 'linkW') # Non-strict - q = self.cls(BASE, 'linkW', 'foo') + q = self.cls(self.base, 'linkW', 'foo') self.assertEqual(q.resolve(strict=False), q) def test_stat(self): - statA = self.cls(BASE).joinpath('fileA').stat() - statB = self.cls(BASE).joinpath('dirB', 'fileB').stat() - statC = self.cls(BASE).joinpath('dirC').stat() + statA = self.cls(self.base).joinpath('fileA').stat() + statB = self.cls(self.base).joinpath('dirB', 'fileB').stat() + statC = self.cls(self.base).joinpath('dirC').stat() # st_mode: files are the same, directory differs. self.assertIsInstance(statA.st_mode, int) self.assertEqual(statA.st_mode, statB.st_mode) @@ -1519,29 +1512,29 @@ def test_stat(self): def test_stat_no_follow_symlinks(self): if not self.can_symlink: self.skipTest("symlinks required") - p = self.cls(BASE) / 'linkA' + p = self.cls(self.base) / 'linkA' st = p.stat() self.assertNotEqual(st, p.stat(follow_symlinks=False)) def test_stat_no_follow_symlinks_nosymlink(self): - p = self.cls(BASE) / 'fileA' + p = self.cls(self.base) / 'fileA' st = p.stat() self.assertEqual(st, p.stat(follow_symlinks=False)) def test_lstat(self): if not self.can_symlink: self.skipTest("symlinks required") - p = self.cls(BASE)/ 'linkA' + p = self.cls(self.base)/ 'linkA' st = p.stat() self.assertNotEqual(st, p.lstat()) def test_lstat_nosymlink(self): - p = self.cls(BASE) / 'fileA' + p = self.cls(self.base) / 'fileA' st = p.stat() self.assertEqual(st, p.lstat()) def test_is_dir(self): - P = self.cls(BASE) + P = self.cls(self.base) self.assertTrue((P / 'dirA').is_dir()) self.assertFalse((P / 'fileA').is_dir()) self.assertFalse((P / 'non-existing').is_dir()) @@ -1554,7 +1547,7 @@ def test_is_dir(self): self.assertFalse((P / 'dirA\x00').is_dir()) def test_is_dir_no_follow_symlinks(self): - P = self.cls(BASE) + P = self.cls(self.base) self.assertTrue((P / 'dirA').is_dir(follow_symlinks=False)) self.assertFalse((P / 'fileA').is_dir(follow_symlinks=False)) self.assertFalse((P / 'non-existing').is_dir(follow_symlinks=False)) @@ -1567,7 +1560,7 @@ def test_is_dir_no_follow_symlinks(self): self.assertFalse((P / 'dirA\x00').is_dir(follow_symlinks=False)) def test_is_file(self): - P = self.cls(BASE) + P = self.cls(self.base) self.assertTrue((P / 'fileA').is_file()) self.assertFalse((P / 'dirA').is_file()) self.assertFalse((P / 'non-existing').is_file()) @@ -1580,7 +1573,7 @@ def test_is_file(self): self.assertFalse((P / 'fileA\x00').is_file()) def test_is_file_no_follow_symlinks(self): - P = self.cls(BASE) + P = self.cls(self.base) self.assertTrue((P / 'fileA').is_file(follow_symlinks=False)) self.assertFalse((P / 'dirA').is_file(follow_symlinks=False)) self.assertFalse((P / 'non-existing').is_file(follow_symlinks=False)) @@ -1593,7 +1586,7 @@ def test_is_file_no_follow_symlinks(self): self.assertFalse((P / 'fileA\x00').is_file(follow_symlinks=False)) def test_is_mount(self): - P = self.cls(BASE) + P = self.cls(self.base) self.assertFalse((P / 'fileA').is_mount()) self.assertFalse((P / 'dirA').is_mount()) self.assertFalse((P / 'non-existing').is_mount()) @@ -1602,7 +1595,7 @@ def test_is_mount(self): self.assertFalse((P / 'linkA').is_mount()) def test_is_symlink(self): - P = self.cls(BASE) + P = self.cls(self.base) self.assertFalse((P / 'fileA').is_symlink()) self.assertFalse((P / 'dirA').is_symlink()) self.assertFalse((P / 'non-existing').is_symlink()) @@ -1618,7 +1611,7 @@ def test_is_symlink(self): self.assertIs((P / 'linkA\x00').is_file(), False) def test_is_junction_false(self): - P = self.cls(BASE) + P = self.cls(self.base) self.assertFalse((P / 'fileA').is_junction()) self.assertFalse((P / 'dirA').is_junction()) self.assertFalse((P / 'non-existing').is_junction()) @@ -1627,7 +1620,7 @@ def test_is_junction_false(self): self.assertFalse((P / 'fileA\x00').is_junction()) def test_is_fifo_false(self): - P = self.cls(BASE) + P = self.cls(self.base) self.assertFalse((P / 'fileA').is_fifo()) self.assertFalse((P / 'dirA').is_fifo()) self.assertFalse((P / 'non-existing').is_fifo()) @@ -1636,7 +1629,7 @@ def test_is_fifo_false(self): self.assertIs((P / 'fileA\x00').is_fifo(), False) def test_is_socket_false(self): - P = self.cls(BASE) + P = self.cls(self.base) self.assertFalse((P / 'fileA').is_socket()) self.assertFalse((P / 'dirA').is_socket()) self.assertFalse((P / 'non-existing').is_socket()) @@ -1645,7 +1638,7 @@ def test_is_socket_false(self): self.assertIs((P / 'fileA\x00').is_socket(), False) def test_is_block_device_false(self): - P = self.cls(BASE) + P = self.cls(self.base) self.assertFalse((P / 'fileA').is_block_device()) self.assertFalse((P / 'dirA').is_block_device()) self.assertFalse((P / 'non-existing').is_block_device()) @@ -1654,7 +1647,7 @@ def test_is_block_device_false(self): self.assertIs((P / 'fileA\x00').is_block_device(), False) def test_is_char_device_false(self): - P = self.cls(BASE) + P = self.cls(self.base) self.assertFalse((P / 'fileA').is_char_device()) self.assertFalse((P / 'dirA').is_char_device()) self.assertFalse((P / 'non-existing').is_char_device()) @@ -1663,7 +1656,7 @@ def test_is_char_device_false(self): self.assertIs((P / 'fileA\x00').is_char_device(), False) def test_pickling_common(self): - p = self.cls(BASE, 'fileA') + p = self.cls(self.base, 'fileA') for proto in range(0, pickle.HIGHEST_PROTOCOL + 1): dumped = pickle.dumps(p, proto) pp = pickle.loads(dumped) @@ -1683,7 +1676,7 @@ def _check_complex_symlinks(self, link0_target): self.skipTest("symlinks required") # Test solving a non-looping chain of symlinks (issue #19887). - P = self.cls(BASE) + P = self.cls(self.base) P.joinpath('link1').symlink_to(os.path.join('link0', 'link0'), target_is_directory=True) P.joinpath('link2').symlink_to(os.path.join('link1', 'link1'), target_is_directory=True) P.joinpath('link3').symlink_to(os.path.join('link2', 'link2'), target_is_directory=True) @@ -1692,16 +1685,16 @@ def _check_complex_symlinks(self, link0_target): # Resolve absolute paths. p = (P / 'link0').resolve() self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) + self.assertEqualNormCase(str(p), self.base) p = (P / 'link1').resolve() self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) + self.assertEqualNormCase(str(p), self.base) p = (P / 'link2').resolve() self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) + self.assertEqualNormCase(str(p), self.base) p = (P / 'link3').resolve() self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) + self.assertEqualNormCase(str(p), self.base) # Resolve relative paths. try: @@ -1709,25 +1702,25 @@ def _check_complex_symlinks(self, link0_target): except pathlib.UnsupportedOperation: return old_path = os.getcwd() - os.chdir(BASE) + os.chdir(self.base) try: p = self.cls('link0').resolve() self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) + self.assertEqualNormCase(str(p), self.base) p = self.cls('link1').resolve() self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) + self.assertEqualNormCase(str(p), self.base) p = self.cls('link2').resolve() self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) + self.assertEqualNormCase(str(p), self.base) p = self.cls('link3').resolve() self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) + self.assertEqualNormCase(str(p), self.base) finally: os.chdir(old_path) def test_complex_symlinks_absolute(self): - self._check_complex_symlinks(BASE) + self._check_complex_symlinks(self.base) def test_complex_symlinks_relative(self): self._check_complex_symlinks('.') @@ -1750,7 +1743,7 @@ def setUpWalk(self): # broken_link2 # TEST2/ # tmp4 a lone file - self.walk_path = self.cls(BASE, "TEST1") + self.walk_path = self.cls(self.base, "TEST1") self.sub1_path = self.walk_path / "SUB1" self.sub11_path = self.sub1_path / "SUB11" self.sub2_path = self.walk_path / "SUB2" @@ -1758,8 +1751,8 @@ def setUpWalk(self): tmp2_path = self.sub1_path / "tmp2" tmp3_path = self.sub2_path / "tmp3" self.link_path = self.sub2_path / "link" - t2_path = self.cls(BASE, "TEST2") - tmp4_path = self.cls(BASE, "TEST2", "tmp4") + t2_path = self.cls(self.base, "TEST2") + tmp4_path = self.cls(self.base, "TEST2", "tmp4") broken_link_path = self.sub2_path / "broken_link" broken_link2_path = self.sub2_path / "broken_link2" @@ -1886,7 +1879,7 @@ def test_walk_above_recursion_limit(self): recursion_limit = 40 # directory_depth > recursion_limit directory_depth = recursion_limit + 10 - base = self.cls(BASE, 'deep') + base = self.cls(self.base, 'deep') path = base.joinpath(*(['d'] * directory_depth)) path.mkdir(parents=True) From cde1335485b7bffb12c378d230ae48ad77d76ab1 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Sat, 16 Dec 2023 19:51:25 -0500 Subject: [PATCH 47/62] IDLE: Add util and stub example comments (#113222) --- Lib/idlelib/idle_test/example_stub.pyi | 2 ++ Lib/idlelib/util.py | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Lib/idlelib/idle_test/example_stub.pyi b/Lib/idlelib/idle_test/example_stub.pyi index a9811a78d10a6e..17b58010a9d8de 100644 --- a/Lib/idlelib/idle_test/example_stub.pyi +++ b/Lib/idlelib/idle_test/example_stub.pyi @@ -1,2 +1,4 @@ +" Example to test recognition of .pyi file as Python source code. + class Example: def method(self, argument1: str, argument2: list[int]) -> None: ... diff --git a/Lib/idlelib/util.py b/Lib/idlelib/util.py index 5ac69a7b94cb56..a7ae74b0579004 100644 --- a/Lib/idlelib/util.py +++ b/Lib/idlelib/util.py @@ -13,9 +13,9 @@ * warning stuff (pyshell, run). """ -# .pyw is for Windows; .pyi is for stub files. -py_extensions = ('.py', '.pyw', '.pyi') # Order needed for open/save dialogs. - +# .pyw is for Windows; .pyi is for typing stub files. +# The extension order is needed for iomenu open/save dialogs. +py_extensions = ('.py', '.pyw', '.pyi') if __name__ == '__main__': from unittest import main From 48c907a15ceae7202fcfeb435943addff896c42c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kul=C3=ADk?= Date: Sun, 17 Dec 2023 06:19:05 +0100 Subject: [PATCH 48/62] gh-113119 fix environment handling in subprocess.Popen when posix_spawn is used (#113120) * Allow posix_spawn to inherit environment form parent environ variable. With this change, posix_spawn call can behave similarly to execv with regards to environments when used in subprocess functions. --- Doc/library/os.rst | 6 +++++- Doc/whatsnew/3.13.rst | 4 ++++ Lib/subprocess.py | 3 --- ...023-12-15-18-13-59.gh-issue-113119.al-569.rst | 2 ++ Modules/posixmodule.c | 16 ++++++++++------ 5 files changed, 21 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-15-18-13-59.gh-issue-113119.al-569.rst diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 2ff0b73560ac4c..a079f1fa604bf4 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -4570,7 +4570,8 @@ written in Python, such as a mail server's external command delivery program. Most users should use :func:`subprocess.run` instead of :func:`posix_spawn`. The positional-only arguments *path*, *args*, and *env* are similar to - :func:`execve`. + :func:`execve`. *env* is allowed to be ``None``, in which case current + process' environment is used. The *path* parameter is the path to the executable file. The *path* should contain a directory. Use :func:`posix_spawnp` to pass an executable file @@ -4645,6 +4646,9 @@ written in Python, such as a mail server's external command delivery program. .. versionadded:: 3.8 + .. versionchanged:: 3.13 + *env* parameter accepts ``None``. + .. availability:: Unix, not Emscripten, not WASI. .. function:: posix_spawnp(path, argv, env, *, file_actions=None, \ diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index ce4f66b97a0be4..4af023566ff0bc 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -289,6 +289,10 @@ os ``False`` on Windows. (Contributed by Serhiy Storchaka in :gh:`59616`) +* :func:`os.posix_spawn` now accepts ``env=None``, which makes the newly spawned + process use the current process environment. + (Contributed by Jakub Kulik in :gh:`113119`.) + pathlib ------- diff --git a/Lib/subprocess.py b/Lib/subprocess.py index d6edd1a9807d1b..1919ea4bddeeda 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -1756,9 +1756,6 @@ def _posix_spawn(self, args, executable, env, restore_signals, c2pread, c2pwrite, errread, errwrite): """Execute program using os.posix_spawn().""" - if env is None: - env = os.environ - kwargs = {} if restore_signals: # See _Py_RestoreSignals() in Python/pylifecycle.c diff --git a/Misc/NEWS.d/next/Library/2023-12-15-18-13-59.gh-issue-113119.al-569.rst b/Misc/NEWS.d/next/Library/2023-12-15-18-13-59.gh-issue-113119.al-569.rst new file mode 100644 index 00000000000000..94087b00515e97 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-15-18-13-59.gh-issue-113119.al-569.rst @@ -0,0 +1,2 @@ +:func:`os.posix_spawn` now accepts ``env=None``, which makes the newly spawned +process use the current process environment. Patch by Jakub Kulik. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index b464a28e63b8ac..2dc5d7d81db973 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -7129,9 +7129,9 @@ py_posix_spawn(int use_posix_spawnp, PyObject *module, path_t *path, PyObject *a return NULL; } - if (!PyMapping_Check(env)) { + if (!PyMapping_Check(env) && env != Py_None) { PyErr_Format(PyExc_TypeError, - "%s: environment must be a mapping object", func_name); + "%s: environment must be a mapping object or None", func_name); goto exit; } @@ -7145,9 +7145,13 @@ py_posix_spawn(int use_posix_spawnp, PyObject *module, path_t *path, PyObject *a goto exit; } - envlist = parse_envlist(env, &envc); - if (envlist == NULL) { - goto exit; + if (env == Py_None) { + envlist = environ; + } else { + envlist = parse_envlist(env, &envc); + if (envlist == NULL) { + goto exit; + } } if (file_actions != NULL && file_actions != Py_None) { @@ -7210,7 +7214,7 @@ py_posix_spawn(int use_posix_spawnp, PyObject *module, path_t *path, PyObject *a if (attrp) { (void)posix_spawnattr_destroy(attrp); } - if (envlist) { + if (envlist && envlist != environ) { free_string_array(envlist, envc); } if (argvlist) { From 21d52995ea490328edf9be3ba072821cd445dd30 Mon Sep 17 00:00:00 2001 From: Taylor Packard <3.t.packard@gmail.com> Date: Sun, 17 Dec 2023 06:14:21 -0500 Subject: [PATCH 49/62] gh-112890: `unittest` Test Discovery page updated "`unittest` dropped the namspace packages support" (GH-113195) --- Doc/library/unittest.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst index 9aad96bdf11bf8..70b4c84c05f818 100644 --- a/Doc/library/unittest.rst +++ b/Doc/library/unittest.rst @@ -346,8 +346,8 @@ the `load_tests protocol`_. ``python -m unittest discover -s root/namespace -t root``). .. versionchanged:: 3.11 - Python 3.11 dropped the :term:`namespace packages ` - support. It has been broken since Python 3.7. Start directory and + :mod:`unittest` dropped the :term:`namespace packages ` + support in Python 3.11. It has been broken since Python 3.7. Start directory and subdirectories containing tests must be regular package that have ``__init__.py`` file. From cfa25fe3e39e09612a6ba8409c46cf35a091de3c Mon Sep 17 00:00:00 2001 From: Carson Radtke Date: Sun, 17 Dec 2023 12:52:26 -0600 Subject: [PATCH 50/62] gh-113149: Improve error message when JSON has trailing comma (GH-113227) --- Lib/json/decoder.py | 7 +++++++ Lib/test/test_json/test_fail.py | 8 +++++--- .../2023-12-16-23-56-42.gh-issue-113149.7LWgTS.rst | 2 ++ Modules/_json.c | 14 ++++++++++++++ 4 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-16-23-56-42.gh-issue-113149.7LWgTS.rst diff --git a/Lib/json/decoder.py b/Lib/json/decoder.py index c5d9ae2d0d5d04..d69a45d6793069 100644 --- a/Lib/json/decoder.py +++ b/Lib/json/decoder.py @@ -200,10 +200,13 @@ def JSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook, break elif nextchar != ',': raise JSONDecodeError("Expecting ',' delimiter", s, end - 1) + comma_idx = end - 1 end = _w(s, end).end() nextchar = s[end:end + 1] end += 1 if nextchar != '"': + if nextchar == '}': + raise JSONDecodeError("Illegal trailing comma before end of object", s, comma_idx) raise JSONDecodeError( "Expecting property name enclosed in double quotes", s, end - 1) if object_pairs_hook is not None: @@ -240,13 +243,17 @@ def JSONArray(s_and_end, scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR): break elif nextchar != ',': raise JSONDecodeError("Expecting ',' delimiter", s, end - 1) + comma_idx = end - 1 try: if s[end] in _ws: end += 1 if s[end] in _ws: end = _w(s, end + 1).end() + nextchar = s[end:end + 1] except IndexError: pass + if nextchar == ']': + raise JSONDecodeError("Illegal trailing comma before end of array", s, comma_idx) return values, end diff --git a/Lib/test/test_json/test_fail.py b/Lib/test/test_json/test_fail.py index efc982e8b0eb04..d6bce605e21463 100644 --- a/Lib/test/test_json/test_fail.py +++ b/Lib/test/test_json/test_fail.py @@ -143,11 +143,11 @@ def test_unexpected_data(self): ('{"spam":[}', 'Expecting value', 9), ('[42:', "Expecting ',' delimiter", 3), ('[42 "spam"', "Expecting ',' delimiter", 4), - ('[42,]', 'Expecting value', 4), + ('[42,]', "Illegal trailing comma before end of array", 3), ('{"spam":[42}', "Expecting ',' delimiter", 11), ('["]', 'Unterminated string starting at', 1), ('["spam":', "Expecting ',' delimiter", 7), - ('["spam",]', 'Expecting value', 8), + ('["spam",]', "Illegal trailing comma before end of array", 7), ('{:', 'Expecting property name enclosed in double quotes', 1), ('{,', 'Expecting property name enclosed in double quotes', 1), ('{42', 'Expecting property name enclosed in double quotes', 1), @@ -159,7 +159,9 @@ def test_unexpected_data(self): ('[{"spam":]', 'Expecting value', 9), ('{"spam":42 "ham"', "Expecting ',' delimiter", 11), ('[{"spam":42]', "Expecting ',' delimiter", 11), - ('{"spam":42,}', 'Expecting property name enclosed in double quotes', 11), + ('{"spam":42,}', "Illegal trailing comma before end of object", 10), + ('{"spam":42 , }', "Illegal trailing comma before end of object", 11), + ('[123 , ]', "Illegal trailing comma before end of array", 6), ] for data, msg, idx in test_cases: with self.assertRaises(self.JSONDecodeError) as cm: diff --git a/Misc/NEWS.d/next/Library/2023-12-16-23-56-42.gh-issue-113149.7LWgTS.rst b/Misc/NEWS.d/next/Library/2023-12-16-23-56-42.gh-issue-113149.7LWgTS.rst new file mode 100644 index 00000000000000..0faa67fefabeca --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-16-23-56-42.gh-issue-113149.7LWgTS.rst @@ -0,0 +1,2 @@ +Improve error message when a JSON array or object contains a trailing comma. +Patch by Carson Radtke. diff --git a/Modules/_json.c b/Modules/_json.c index 0b1bfe34ad9304..24b292ce70e5eb 100644 --- a/Modules/_json.c +++ b/Modules/_json.c @@ -662,6 +662,7 @@ _parse_object_unicode(PyScannerObject *s, PyObject *memo, PyObject *pystr, Py_ss PyObject *key = NULL; int has_pairs_hook = (s->object_pairs_hook != Py_None); Py_ssize_t next_idx; + Py_ssize_t comma_idx; str = PyUnicode_DATA(pystr); kind = PyUnicode_KIND(pystr); @@ -741,10 +742,16 @@ _parse_object_unicode(PyScannerObject *s, PyObject *memo, PyObject *pystr, Py_ss raise_errmsg("Expecting ',' delimiter", pystr, idx); goto bail; } + comma_idx = idx; idx++; /* skip whitespace after , delimiter */ while (idx <= end_idx && IS_WHITESPACE(PyUnicode_READ(kind, str, idx))) idx++; + + if (idx <= end_idx && PyUnicode_READ(kind, str, idx) == '}') { + raise_errmsg("Illegal trailing comma before end of object", pystr, comma_idx); + goto bail; + } } } @@ -785,6 +792,7 @@ _parse_array_unicode(PyScannerObject *s, PyObject *memo, PyObject *pystr, Py_ssi PyObject *val = NULL; PyObject *rval; Py_ssize_t next_idx; + Py_ssize_t comma_idx; rval = PyList_New(0); if (rval == NULL) @@ -822,10 +830,16 @@ _parse_array_unicode(PyScannerObject *s, PyObject *memo, PyObject *pystr, Py_ssi raise_errmsg("Expecting ',' delimiter", pystr, idx); goto bail; } + comma_idx = idx; idx++; /* skip whitespace after , */ while (idx <= end_idx && IS_WHITESPACE(PyUnicode_READ(kind, str, idx))) idx++; + + if (idx <= end_idx && PyUnicode_READ(kind, str, idx) == ']') { + raise_errmsg("Illegal trailing comma before end of array", pystr, comma_idx); + goto bail; + } } } From 32d87a88994c131a9f3857d01ae7c07918577a55 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sun, 17 Dec 2023 21:23:14 +0200 Subject: [PATCH 51/62] Docs: Add label to grammar spec for linking from PEPs (#113235) --- Doc/reference/grammar.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Doc/reference/grammar.rst b/Doc/reference/grammar.rst index bc1db7b039cd5a..b9cca4444c9141 100644 --- a/Doc/reference/grammar.rst +++ b/Doc/reference/grammar.rst @@ -1,3 +1,5 @@ +.. _full-grammar-specification: + Full Grammar specification ========================== From 2b93f5224216d10f8119373e72b5c2b3984e0af6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kul=C3=ADk?= Date: Sun, 17 Dec 2023 22:34:57 +0100 Subject: [PATCH 52/62] gh-113117: Support posix_spawn in subprocess.Popen with close_fds=True (#113118) Add support for `os.POSIX_SPAWN_CLOSEFROM` and `posix_spawn_file_actions_addclosefrom_np` and have the `subprocess` module use them when available. This means `posix_spawn` can now be used in the default `close_fds=True` situation on many platforms. Co-authored-by: Gregory P. Smith [Google LLC] --- Doc/library/os.rst | 15 +++++++++-- Doc/whatsnew/3.13.rst | 26 +++++++++++++++++++ Lib/subprocess.py | 11 +++++--- Lib/test/test_subprocess.py | 5 ++++ ...-12-16-10-58-34.gh-issue-113117.0zF7bH.rst | 4 +++ Modules/posixmodule.c | 24 +++++++++++++++++ configure | 6 +++++ configure.ac | 1 + pyconfig.h.in | 4 +++ 9 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-16-10-58-34.gh-issue-113117.0zF7bH.rst diff --git a/Doc/library/os.rst b/Doc/library/os.rst index a079f1fa604bf4..1138cc1f249ee7 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -4601,10 +4601,17 @@ written in Python, such as a mail server's external command delivery program. Performs ``os.dup2(fd, new_fd)``. + .. data:: POSIX_SPAWN_CLOSEFROM + + (``os.POSIX_SPAWN_CLOSEFROM``, *fd*) + + Performs ``os.closerange(fd, INF)``. + These tuples correspond to the C library :c:func:`!posix_spawn_file_actions_addopen`, - :c:func:`!posix_spawn_file_actions_addclose`, and - :c:func:`!posix_spawn_file_actions_adddup2` API calls used to prepare + :c:func:`!posix_spawn_file_actions_addclose`, + :c:func:`!posix_spawn_file_actions_adddup2`, and + :c:func:`!posix_spawn_file_actions_addclosefrom_np` API calls used to prepare for the :c:func:`!posix_spawn` call itself. The *setpgroup* argument will set the process group of the child to the value @@ -4649,6 +4656,10 @@ written in Python, such as a mail server's external command delivery program. .. versionchanged:: 3.13 *env* parameter accepts ``None``. + .. versionchanged:: 3.13 + ``os.POSIX_SPAWN_CLOSEFROM`` is available on platforms where + :c:func:`!posix_spawn_file_actions_addclosefrom_np` exists. + .. availability:: Unix, not Emscripten, not WASI. .. function:: posix_spawnp(path, argv, env, *, file_actions=None, \ diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 4af023566ff0bc..2c869cbe11396b 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -293,6 +293,11 @@ os process use the current process environment. (Contributed by Jakub Kulik in :gh:`113119`.) +* :func:`os.posix_spawn` gains an :attr:`os.POSIX_SPAWN_CLOSEFROM` attribute for + use in ``file_actions=`` on platforms that support + :c:func:`!posix_spawn_file_actions_addclosefrom_np`. + (Contributed by Jakub Kulik in :gh:`113117`.) + pathlib ------- @@ -342,6 +347,21 @@ sqlite3 object is not :meth:`closed ` explicitly. (Contributed by Erlend E. Aasland in :gh:`105539`.) +subprocess +---------- + +* The :mod:`subprocess` module now uses the :func:`os.posix_spawn` function in + more situations. Notably in the default case of ``close_fds=True`` on more + recent versions of platforms including Linux, FreeBSD, and Solaris where the + C library provides :c:func:`!posix_spawn_file_actions_addclosefrom_np`. + On Linux this should perform similar to our existing Linux :c:func:`!vfork` + based code. A private control knob :attr:`!subprocess._USE_POSIX_SPAWN` can + be set to ``False`` if you need to force :mod:`subprocess` not to ever use + :func:`os.posix_spawn`. Please report your reason and platform details in + the CPython issue tracker if you set this so that we can improve our API + selection logic for everyone. + (Contributed by Jakub Kulik in :gh:`113117`.) + sys --- @@ -415,6 +435,12 @@ Optimizations * :func:`textwrap.indent` is now ~30% faster than before for large input. (Contributed by Inada Naoki in :gh:`107369`.) +* The :mod:`subprocess` module uses :func:`os.posix_spawn` in more situations + including the default where ``close_fds=True`` on many modern platforms. This + should provide a noteworthy performance increase launching processes on + FreeBSD and Solaris. See the ``subprocess`` section above for details. + (Contributed by Jakub Kulik in :gh:`113117`.) + Deprecated ========== diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 1919ea4bddeeda..d5bd9a9e31aa04 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -748,6 +748,7 @@ def _use_posix_spawn(): # guarantee the given libc/syscall API will be used. _USE_POSIX_SPAWN = _use_posix_spawn() _USE_VFORK = True +_HAVE_POSIX_SPAWN_CLOSEFROM = hasattr(os, 'POSIX_SPAWN_CLOSEFROM') class Popen: @@ -1751,7 +1752,7 @@ def _get_handles(self, stdin, stdout, stderr): errread, errwrite) - def _posix_spawn(self, args, executable, env, restore_signals, + def _posix_spawn(self, args, executable, env, restore_signals, close_fds, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite): @@ -1777,6 +1778,10 @@ def _posix_spawn(self, args, executable, env, restore_signals, ): if fd != -1: file_actions.append((os.POSIX_SPAWN_DUP2, fd, fd2)) + + if close_fds: + file_actions.append((os.POSIX_SPAWN_CLOSEFROM, 3)) + if file_actions: kwargs['file_actions'] = file_actions @@ -1824,7 +1829,7 @@ def _execute_child(self, args, executable, preexec_fn, close_fds, if (_USE_POSIX_SPAWN and os.path.dirname(executable) and preexec_fn is None - and not close_fds + and (not close_fds or _HAVE_POSIX_SPAWN_CLOSEFROM) and not pass_fds and cwd is None and (p2cread == -1 or p2cread > 2) @@ -1836,7 +1841,7 @@ def _execute_child(self, args, executable, preexec_fn, close_fds, and gids is None and uid is None and umask < 0): - self._posix_spawn(args, executable, env, restore_signals, + self._posix_spawn(args, executable, env, restore_signals, close_fds, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite) diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 5eeea54fd55f1a..6d3228bf92f8ca 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -3348,6 +3348,7 @@ def exit_handler(): @unittest.skipIf(not sysconfig.get_config_var("HAVE_VFORK"), "vfork() not enabled by configure.") @mock.patch("subprocess._fork_exec") + @mock.patch("subprocess._USE_POSIX_SPAWN", new=False) def test__use_vfork(self, mock_fork_exec): self.assertTrue(subprocess._USE_VFORK) # The default value regardless. mock_fork_exec.side_effect = RuntimeError("just testing args") @@ -3366,9 +3367,13 @@ def test__use_vfork(self, mock_fork_exec): @unittest.skipIf(not sysconfig.get_config_var("HAVE_VFORK"), "vfork() not enabled by configure.") @unittest.skipIf(sys.platform != "linux", "Linux only, requires strace.") + @mock.patch("subprocess._USE_POSIX_SPAWN", new=False) def test_vfork_used_when_expected(self): # This is a performance regression test to ensure we default to using # vfork() when possible. + # Technically this test could pass when posix_spawn is used as well + # because libc tends to implement that internally using vfork. But + # that'd just be testing a libc+kernel implementation detail. strace_binary = "/usr/bin/strace" # The only system calls we are interested in. strace_filter = "--trace=clone,clone2,clone3,fork,vfork,exit,exit_group" diff --git a/Misc/NEWS.d/next/Library/2023-12-16-10-58-34.gh-issue-113117.0zF7bH.rst b/Misc/NEWS.d/next/Library/2023-12-16-10-58-34.gh-issue-113117.0zF7bH.rst new file mode 100644 index 00000000000000..718226a0021efe --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-16-10-58-34.gh-issue-113117.0zF7bH.rst @@ -0,0 +1,4 @@ +The :mod:`subprocess` module can now use the :func:`os.posix_spawn` function +with ``close_fds=True`` on platforms where +``posix_spawn_file_actions_addclosefrom_np`` is available. +Patch by Jakub Kulik. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 2dc5d7d81db973..8ffe0f5de1e7bd 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -6834,6 +6834,9 @@ enum posix_spawn_file_actions_identifier { POSIX_SPAWN_OPEN, POSIX_SPAWN_CLOSE, POSIX_SPAWN_DUP2 +#ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP + ,POSIX_SPAWN_CLOSEFROM +#endif }; #if defined(HAVE_SCHED_SETPARAM) || defined(HAVE_SCHED_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDPARAM) @@ -7074,6 +7077,24 @@ parse_file_actions(PyObject *file_actions, } break; } +#ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP + case POSIX_SPAWN_CLOSEFROM: { + int fd; + if (!PyArg_ParseTuple(file_action, "Oi" + ";A closefrom file_action tuple must have 2 elements", + &tag_obj, &fd)) + { + goto fail; + } + errno = posix_spawn_file_actions_addclosefrom_np(file_actionsp, + fd); + if (errno) { + posix_error(); + goto fail; + } + break; + } +#endif default: { PyErr_SetString(PyExc_TypeError, "Unknown file_actions identifier"); @@ -16774,6 +16795,9 @@ all_ins(PyObject *m) if (PyModule_AddIntConstant(m, "POSIX_SPAWN_OPEN", POSIX_SPAWN_OPEN)) return -1; if (PyModule_AddIntConstant(m, "POSIX_SPAWN_CLOSE", POSIX_SPAWN_CLOSE)) return -1; if (PyModule_AddIntConstant(m, "POSIX_SPAWN_DUP2", POSIX_SPAWN_DUP2)) return -1; +#ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP + if (PyModule_AddIntMacro(m, POSIX_SPAWN_CLOSEFROM)) return -1; +#endif #endif #if defined(HAVE_SPAWNV) || defined (HAVE_RTPSPAWN) diff --git a/configure b/configure index 668a0efd77db0e..7e50abc29d0c1a 100755 --- a/configure +++ b/configure @@ -17779,6 +17779,12 @@ if test "x$ac_cv_func_posix_spawnp" = xyes then : printf "%s\n" "#define HAVE_POSIX_SPAWNP 1" >>confdefs.h +fi +ac_fn_c_check_func "$LINENO" "posix_spawn_file_actions_addclosefrom_np" "ac_cv_func_posix_spawn_file_actions_addclosefrom_np" +if test "x$ac_cv_func_posix_spawn_file_actions_addclosefrom_np" = xyes +then : + printf "%s\n" "#define HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP 1" >>confdefs.h + fi ac_fn_c_check_func "$LINENO" "pread" "ac_cv_func_pread" if test "x$ac_cv_func_pread" = xyes diff --git a/configure.ac b/configure.ac index 020553abd71b4f..e064848af9ed1b 100644 --- a/configure.ac +++ b/configure.ac @@ -4757,6 +4757,7 @@ AC_CHECK_FUNCS([ \ lockf lstat lutimes madvise mbrtowc memrchr mkdirat mkfifo mkfifoat \ mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \ pipe2 plock poll posix_fadvise posix_fallocate posix_spawn posix_spawnp \ + posix_spawn_file_actions_addclosefrom_np \ pread preadv preadv2 pthread_condattr_setclock pthread_init pthread_kill \ pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \ rtpSpawn sched_get_priority_max sched_rr_get_interval sched_setaffinity \ diff --git a/pyconfig.h.in b/pyconfig.h.in index 9c429c03722383..d8a9f68951afbd 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -905,6 +905,10 @@ /* Define to 1 if you have the `posix_spawnp' function. */ #undef HAVE_POSIX_SPAWNP +/* Define to 1 if you have the `posix_spawn_file_actions_addclosefrom_np' + function. */ +#undef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP + /* Define to 1 if you have the `pread' function. */ #undef HAVE_PREAD From f428c4dafbfa2425ea056e7f2ed2ea45fa90be87 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 18 Dec 2023 08:57:45 +0200 Subject: [PATCH 53/62] gh-101100: Fix Sphinx warnings in library/tarfile.rst (#113237) Fix Sphinx warnings in library/tarfile.rst --- Doc/library/tarfile.rst | 67 ++++++++++++++++++++++++++++++++++------- Doc/tools/.nitignore | 1 - 2 files changed, 56 insertions(+), 12 deletions(-) diff --git a/Doc/library/tarfile.rst b/Doc/library/tarfile.rst index 3e5723a66780ca..f4e83d64bb1580 100644 --- a/Doc/library/tarfile.rst +++ b/Doc/library/tarfile.rst @@ -116,7 +116,7 @@ Some facts and figures: ``'filemode|[compression]'``. :func:`tarfile.open` will return a :class:`TarFile` object that processes its data as a stream of blocks. No random seeking will be done on the file. If given, *fileobj* may be any object that has a - :meth:`read` or :meth:`write` method (depending on the *mode*). *bufsize* + :meth:`~io.TextIOBase.read` or :meth:`~io.TextIOBase.write` method (depending on the *mode*). *bufsize* specifies the blocksize and defaults to ``20 * 512`` bytes. Use this variant in combination with e.g. ``sys.stdin``, a socket :term:`file object` or a tape device. However, such a :class:`TarFile` object is limited in that it does @@ -255,6 +255,51 @@ The following constants are available at the module level: The default character encoding: ``'utf-8'`` on Windows, the value returned by :func:`sys.getfilesystemencoding` otherwise. +.. data:: REGTYPE + AREGTYPE + + A regular file :attr:`~TarInfo.type`. + +.. data:: LNKTYPE + + A link (inside tarfile) :attr:`~TarInfo.type`. + +.. data:: SYMTYPE + + A symbolic link :attr:`~TarInfo.type`. + +.. data:: CHRTYPE + + A character special device :attr:`~TarInfo.type`. + +.. data:: BLKTYPE + + A block special device :attr:`~TarInfo.type`. + +.. data:: DIRTYPE + + A directory :attr:`~TarInfo.type`. + +.. data:: FIFOTYPE + + A FIFO special device :attr:`~TarInfo.type`. + +.. data:: CONTTYPE + + A contiguous file :attr:`~TarInfo.type`. + +.. data:: GNUTYPE_LONGNAME + + A GNU tar longname :attr:`~TarInfo.type`. + +.. data:: GNUTYPE_LONGLINK + + A GNU tar longlink :attr:`~TarInfo.type`. + +.. data:: GNUTYPE_SPARSE + + A GNU tar sparse file :attr:`~TarInfo.type`. + Each of the following constants defines a tar archive format that the :mod:`tarfile` module is able to create. See section :ref:`tar-formats` for @@ -325,7 +370,7 @@ be finalized; only the internally used file object will be closed. See the *name* is the pathname of the archive. *name* may be a :term:`path-like object`. It can be omitted if *fileobj* is given. - In this case, the file object's :attr:`name` attribute is used if it exists. + In this case, the file object's :attr:`!name` attribute is used if it exists. *mode* is either ``'r'`` to read from an existing archive, ``'a'`` to append data to an existing file, ``'w'`` to create a new file overwriting an existing @@ -359,7 +404,7 @@ be finalized; only the internally used file object will be closed. See the messages). The messages are written to ``sys.stderr``. *errorlevel* controls how extraction errors are handled, - see :attr:`the corresponding attribute <~TarFile.errorlevel>`. + see :attr:`the corresponding attribute `. The *encoding* and *errors* arguments define the character encoding to be used for reading or writing the archive and how conversion errors are going @@ -645,8 +690,8 @@ It does *not* contain the file's data itself. :meth:`~TarFile.getmember`, :meth:`~TarFile.getmembers` and :meth:`~TarFile.gettarinfo`. -Modifying the objects returned by :meth:`~!TarFile.getmember` or -:meth:`~!TarFile.getmembers` will affect all subsequent +Modifying the objects returned by :meth:`~TarFile.getmember` or +:meth:`~TarFile.getmembers` will affect all subsequent operations on the archive. For cases where this is unwanted, you can use :mod:`copy.copy() ` or call the :meth:`~TarInfo.replace` method to create a modified copy in one step. @@ -795,8 +840,8 @@ A ``TarInfo`` object has the following public data attributes: A dictionary containing key-value pairs of an associated pax extended header. -.. method:: TarInfo.replace(name=..., mtime=..., mode=..., linkname=..., - uid=..., gid=..., uname=..., gname=..., +.. method:: TarInfo.replace(name=..., mtime=..., mode=..., linkname=..., \ + uid=..., gid=..., uname=..., gname=..., \ deep=True) .. versionadded:: 3.12 @@ -816,7 +861,7 @@ A :class:`TarInfo` object also provides some convenient query methods: .. method:: TarInfo.isfile() - Return :const:`True` if the :class:`Tarinfo` object is a regular file. + Return :const:`True` if the :class:`TarInfo` object is a regular file. .. method:: TarInfo.isreg() @@ -952,7 +997,7 @@ reused in custom filters: path (after following symlinks) would end up outside the destination. This raises :class:`~tarfile.OutsideDestinationError`. - Clear high mode bits (setuid, setgid, sticky) and group/other write bits - (:const:`~stat.S_IWGRP`|:const:`~stat.S_IWOTH`). + (:const:`~stat.S_IWGRP` | :const:`~stat.S_IWOTH`). Return the modified ``TarInfo`` member. @@ -977,9 +1022,9 @@ reused in custom filters: - For regular files, including hard links: - Set the owner read and write permissions - (:const:`~stat.S_IRUSR`|:const:`~stat.S_IWUSR`). + (:const:`~stat.S_IRUSR` | :const:`~stat.S_IWUSR`). - Remove the group & other executable permission - (:const:`~stat.S_IXGRP`|:const:`~stat.S_IXOTH`) + (:const:`~stat.S_IXGRP` | :const:`~stat.S_IXOTH`) if the owner doesn’t have it (:const:`~stat.S_IXUSR`). - For other files (directories), set ``mode`` to ``None``, so diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 23d4d3e522b455..8b0fc13832d51a 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -86,7 +86,6 @@ Doc/library/ssl.rst Doc/library/stdtypes.rst Doc/library/string.rst Doc/library/subprocess.rst -Doc/library/tarfile.rst Doc/library/termios.rst Doc/library/test.rst Doc/library/tkinter.rst From 4a24bf9a13a7cf055113c04bde0874186722c62c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Mon, 18 Dec 2023 11:21:46 +0100 Subject: [PATCH 54/62] gh-113246: Updated bundled pip to 23.3.2 (gh-113249) Updated bundled pip to 23.3.2 --- Lib/ensurepip/__init__.py | 2 +- ...ne-any.whl => pip-23.3.2-py3-none-any.whl} | Bin 2107242 -> 2109393 bytes ...-12-18-09-47-54.gh-issue-113246.em930H.rst | 1 + Misc/sbom.spdx.json | 10 +++++----- Tools/build/generate_sbom.py | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) rename Lib/ensurepip/_bundled/{pip-23.3.1-py3-none-any.whl => pip-23.3.2-py3-none-any.whl} (94%) create mode 100644 Misc/NEWS.d/next/Library/2023-12-18-09-47-54.gh-issue-113246.em930H.rst diff --git a/Lib/ensurepip/__init__.py b/Lib/ensurepip/__init__.py index 21e4ad99a39628..a09bf3201e1fb7 100644 --- a/Lib/ensurepip/__init__.py +++ b/Lib/ensurepip/__init__.py @@ -10,7 +10,7 @@ __all__ = ["version", "bootstrap"] _PACKAGE_NAMES = ('pip',) -_PIP_VERSION = "23.3.1" +_PIP_VERSION = "23.3.2" _PROJECTS = [ ("pip", _PIP_VERSION, "py3"), ] diff --git a/Lib/ensurepip/_bundled/pip-23.3.1-py3-none-any.whl b/Lib/ensurepip/_bundled/pip-23.3.2-py3-none-any.whl similarity index 94% rename from Lib/ensurepip/_bundled/pip-23.3.1-py3-none-any.whl rename to Lib/ensurepip/_bundled/pip-23.3.2-py3-none-any.whl index a4faf716b663e61faf22bb58fa57d3b2b0e00299..ae78b8a6ce073797af05cd7a833ff23d14cff185 100644 GIT binary patch delta 62513 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
@A~_VFkV(@xt`85eltqNKG{^Nq0J4e;H77&X41v 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 Date: Mon, 18 Dec 2023 11:14:40 +0000 Subject: [PATCH 55/62] GH-111485: Test the new cases generator (GH-113252) --- Lib/test/test_generated_cases.py | 114 ++++++++++++--------- Python/executor_cases.c.h | 26 ++--- Python/generated_cases.c.h | 30 +++--- Tools/cases_generator/analyzer.py | 3 +- Tools/cases_generator/cwriter.py | 15 +-- Tools/cases_generator/generators_common.py | 6 +- Tools/cases_generator/stack.py | 2 +- Tools/cases_generator/tier1_generator.py | 8 ++ 8 files changed, 116 insertions(+), 88 deletions(-) diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index de96a8764594ba..74cebbe469d794 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -32,6 +32,7 @@ def skip_if_different_mount_drives(): import analysis import formatting from parsing import StackEffect + import tier1_generator def handle_stderr(): @@ -108,13 +109,12 @@ def run_cases_test(self, input: str, expected: str): temp_input.write(analysis.END_MARKER) temp_input.flush() - a = generate_cases.Generator([self.temp_input_filename]) with handle_stderr(): - a.parse() - a.analyze() - if a.errors: - raise RuntimeError(f"Found {a.errors} errors") - a.write_instructions(self.temp_output_filename, False) + tier1_generator.generate_tier1_from_files( + [self.temp_input_filename], + self.temp_output_filename, + False + ) with open(self.temp_output_filename) as temp_output: lines = temp_output.readlines() @@ -163,7 +163,7 @@ def test_inst_one_pop(self): PyObject *value; value = stack_pointer[-1]; spam(); - STACK_SHRINK(1); + stack_pointer += -1; DISPATCH(); } """ @@ -182,8 +182,8 @@ def test_inst_one_push(self): INSTRUCTION_STATS(OP); PyObject *res; spam(); - STACK_GROW(1); - stack_pointer[-1] = res; + stack_pointer[0] = res; + stack_pointer += 1; DISPATCH(); } """ @@ -227,8 +227,8 @@ def test_binary_op(self): right = stack_pointer[-1]; left = stack_pointer[-2]; spam(); - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; DISPATCH(); } """ @@ -273,7 +273,6 @@ def test_predictions_and_eval_breaker(self): next_instr += 1; INSTRUCTION_STATS(OP1); PREDICTED(OP1); - static_assert(INLINE_CACHE_ENTRIES_OP1 == 0, "incorrect cache size"); PyObject *arg; PyObject *rest; arg = stack_pointer[-1]; @@ -285,6 +284,7 @@ def test_predictions_and_eval_breaker(self): frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(OP3); + static_assert(INLINE_CACHE_ENTRIES_OP1 == 0, "incorrect cache size"); PyObject *arg; PyObject *res; arg = stack_pointer[-1]; @@ -325,6 +325,7 @@ def test_error_if_plain_with_comment(self): next_instr += 1; INSTRUCTION_STATS(OP); if (cond) goto label; + // Comment is ok DISPATCH(); } """ @@ -347,8 +348,8 @@ def test_error_if_pop(self): right = stack_pointer[-1]; left = stack_pointer[-2]; if (cond) goto pop_2_label; - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; DISPATCH(); } """ @@ -368,7 +369,7 @@ def test_cache_effect(self): value = stack_pointer[-1]; uint16_t counter = read_u16(&this_instr[1].cache); uint32_t extra = read_u32(&this_instr[2].cache); - STACK_SHRINK(1); + stack_pointer += -1; DISPATCH(); } """ @@ -411,26 +412,26 @@ def test_macro_instruction(self): INSTRUCTION_STATS(OP); PREDICTED(OP); _Py_CODEUNIT *this_instr = next_instr - 6; - static_assert(INLINE_CACHE_ENTRIES_OP == 5, "incorrect cache size"); PyObject *right; PyObject *left; PyObject *arg2; PyObject *res; - // OP1 + // _OP1 right = stack_pointer[-1]; left = stack_pointer[-2]; { uint16_t counter = read_u16(&this_instr[1].cache); op1(left, right); } + /* Skip 2 cache entries */ // OP2 arg2 = stack_pointer[-3]; { uint32_t extra = read_u32(&this_instr[4].cache); res = op2(arg2, left, right); } - STACK_SHRINK(2); - stack_pointer[-1] = res; + stack_pointer[-3] = res; + stack_pointer += -2; DISPATCH(); } @@ -451,6 +452,7 @@ def test_macro_instruction(self): frame->instr_ptr = next_instr; next_instr += 6; INSTRUCTION_STATS(OP3); + static_assert(INLINE_CACHE_ENTRIES_OP == 5, "incorrect cache size"); PyObject *right; PyObject *left; PyObject *arg2; @@ -459,8 +461,24 @@ def test_macro_instruction(self): left = stack_pointer[-2]; arg2 = stack_pointer[-3]; res = op3(arg2, left, right); - STACK_SHRINK(2); - stack_pointer[-1] = res; + stack_pointer[-3] = res; + stack_pointer += -2; + DISPATCH(); + } + """ + self.run_cases_test(input, output) + def test_unused_caches(self): + input = """ + inst(OP, (unused/1, unused/2 --)) { + body(); + } + """ + output = """ + TARGET(OP) { + frame->instr_ptr = next_instr; + next_instr += 4; + INSTRUCTION_STATS(OP); + body(); DISPATCH(); } """ @@ -519,11 +537,10 @@ def test_array_input(self): PyObject **values; PyObject *below; above = stack_pointer[-1]; - values = stack_pointer - 1 - oparg*2; + values = &stack_pointer[-1 - oparg*2]; below = stack_pointer[-2 - oparg*2]; spam(); - STACK_SHRINK(oparg*2); - STACK_SHRINK(2); + stack_pointer += -2 - oparg*2; DISPATCH(); } """ @@ -543,11 +560,11 @@ def test_array_output(self): PyObject *below; PyObject **values; PyObject *above; - values = stack_pointer - 1; + values = &stack_pointer[-1]; spam(values, oparg); - STACK_GROW(oparg*3); - stack_pointer[-2 - oparg*3] = below; - stack_pointer[-1] = above; + stack_pointer[-2] = below; + stack_pointer[-1 + oparg*3] = above; + stack_pointer += oparg*3; DISPATCH(); } """ @@ -566,10 +583,10 @@ def test_array_input_output(self): INSTRUCTION_STATS(OP); PyObject **values; PyObject *above; - values = stack_pointer - oparg; + values = &stack_pointer[-oparg]; spam(values, oparg); - STACK_GROW(1); - stack_pointer[-1] = above; + stack_pointer[0] = above; + stack_pointer += 1; DISPATCH(); } """ @@ -588,11 +605,10 @@ def test_array_error_if(self): INSTRUCTION_STATS(OP); PyObject **values; PyObject *extra; - values = stack_pointer - oparg; + values = &stack_pointer[-oparg]; extra = stack_pointer[-1 - oparg]; - if (oparg == 0) { STACK_SHRINK(oparg); goto pop_1_somewhere; } - STACK_SHRINK(oparg); - STACK_SHRINK(1); + if (oparg == 0) { stack_pointer += -1 - oparg; goto somewhere; } + stack_pointer += -1 - oparg; DISPATCH(); } """ @@ -616,14 +632,13 @@ def test_cond_effect(self): PyObject *output = NULL; PyObject *zz; cc = stack_pointer[-1]; - if ((oparg & 1) == 1) { input = stack_pointer[-1 - ((oparg & 1) == 1 ? 1 : 0)]; } - aa = stack_pointer[-2 - ((oparg & 1) == 1 ? 1 : 0)]; + if ((oparg & 1) == 1) { input = stack_pointer[-1 - ((((oparg & 1) == 1) ? 1 : 0))]; } + aa = stack_pointer[-2 - ((((oparg & 1) == 1) ? 1 : 0))]; output = spam(oparg, input); - STACK_SHRINK((((oparg & 1) == 1) ? 1 : 0)); - STACK_GROW(((oparg & 2) ? 1 : 0)); - stack_pointer[-2 - (oparg & 2 ? 1 : 0)] = xx; - if (oparg & 2) { stack_pointer[-1 - (oparg & 2 ? 1 : 0)] = output; } - stack_pointer[-1] = zz; + stack_pointer[-2 - ((((oparg & 1) == 1) ? 1 : 0))] = xx; + if (oparg & 2) stack_pointer[-1 - ((((oparg & 1) == 1) ? 1 : 0))] = output; + stack_pointer[-1 - ((((oparg & 1) == 1) ? 1 : 0)) + (((oparg & 2) ? 1 : 0))] = zz; + stack_pointer += -((((oparg & 1) == 1) ? 1 : 0)) + (((oparg & 2) ? 1 : 0)); DISPATCH(); } """ @@ -661,11 +676,10 @@ def test_macro_cond_effect(self): { # Body of B } - STACK_SHRINK(1); - STACK_GROW((oparg ? 1 : 0)); - stack_pointer[-2 - (oparg ? 1 : 0)] = deep; - if (oparg) { stack_pointer[-1 - (oparg ? 1 : 0)] = extra; } - stack_pointer[-1] = res; + stack_pointer[-3] = deep; + if (oparg) stack_pointer[-2] = extra; + stack_pointer[-2 + (((oparg) ? 1 : 0))] = res; + stack_pointer += -1 + (((oparg) ? 1 : 0)); DISPATCH(); } """ @@ -696,9 +710,9 @@ def test_macro_push_push(self): { val2 = spam(); } - STACK_GROW(2); - stack_pointer[-2] = val1; - stack_pointer[-1] = val2; + stack_pointer[0] = val1; + stack_pointer[1] = val2; + stack_pointer += 2; DISPATCH(); } """ diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 7cb60cbc1dd3ff..7cc29c8e644d8d 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -1126,7 +1126,7 @@ } null = NULL; stack_pointer[0] = res; - if (oparg & 1) stack_pointer[1] = null; + if (oparg & 1) stack_pointer[1] = null; stack_pointer += 1 + ((oparg & 1)); break; } @@ -1162,7 +1162,7 @@ STAT_INC(LOAD_GLOBAL, hit); null = NULL; stack_pointer[0] = res; - if (oparg & 1) stack_pointer[1] = null; + if (oparg & 1) stack_pointer[1] = null; stack_pointer += 1 + ((oparg & 1)); break; } @@ -1180,7 +1180,7 @@ STAT_INC(LOAD_GLOBAL, hit); null = NULL; stack_pointer[0] = res; - if (oparg & 1) stack_pointer[1] = null; + if (oparg & 1) stack_pointer[1] = null; stack_pointer += 1 + ((oparg & 1)); break; } @@ -1612,7 +1612,7 @@ if (attr == NULL) goto pop_1_error_tier_two; } stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = self_or_null; + if (oparg & 1) stack_pointer[0] = self_or_null; stack_pointer += ((oparg & 1)); break; } @@ -1652,7 +1652,7 @@ null = NULL; Py_DECREF(owner); stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = null; + if (oparg & 1) stack_pointer[0] = null; stack_pointer += ((oparg & 1)); break; } @@ -1686,7 +1686,7 @@ null = NULL; Py_DECREF(owner); stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = null; + if (oparg & 1) stack_pointer[0] = null; stack_pointer += ((oparg & 1)); break; } @@ -1730,7 +1730,7 @@ null = NULL; Py_DECREF(owner); stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = null; + if (oparg & 1) stack_pointer[0] = null; stack_pointer += ((oparg & 1)); break; } @@ -1750,7 +1750,7 @@ null = NULL; Py_DECREF(owner); stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = null; + if (oparg & 1) stack_pointer[0] = null; stack_pointer += ((oparg & 1)); break; } @@ -1778,7 +1778,7 @@ null = NULL; Py_DECREF(owner); stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = null; + if (oparg & 1) stack_pointer[0] = null; stack_pointer += ((oparg & 1)); break; } @@ -2467,7 +2467,7 @@ assert(_PyType_HasFeature(Py_TYPE(attr), Py_TPFLAGS_METHOD_DESCRIPTOR)); self = owner; stack_pointer[-1] = attr; - if (1) stack_pointer[0] = self; + if (1) stack_pointer[0] = self; stack_pointer += (((1) ? 1 : 0)); break; } @@ -2487,7 +2487,7 @@ attr = Py_NewRef(descr); self = owner; stack_pointer[-1] = attr; - if (1) stack_pointer[0] = self; + if (1) stack_pointer[0] = self; stack_pointer += (((1) ? 1 : 0)); break; } @@ -2550,7 +2550,7 @@ attr = Py_NewRef(descr); self = owner; stack_pointer[-1] = attr; - if (1) stack_pointer[0] = self; + if (1) stack_pointer[0] = self; stack_pointer += (((1) ? 1 : 0)); break; } @@ -3199,7 +3199,7 @@ PyObject *start; PyObject *slice; oparg = CURRENT_OPARG(); - if (oparg == 3) { step = stack_pointer[-(((oparg == 3) ? 1 : 0))]; } + if (oparg == 3) { step = stack_pointer[-(((oparg == 3) ? 1 : 0))]; } stop = stack_pointer[-1 - (((oparg == 3) ? 1 : 0))]; start = stack_pointer[-2 - (((oparg == 3) ? 1 : 0))]; slice = PySlice_New(start, stop, step); diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 24f26722d7a745..b202d141b36f4f 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -678,7 +678,7 @@ PyObject *stop; PyObject *start; PyObject *slice; - if (oparg == 3) { step = stack_pointer[-(((oparg == 3) ? 1 : 0))]; } + if (oparg == 3) { step = stack_pointer[-(((oparg == 3) ? 1 : 0))]; } stop = stack_pointer[-1 - (((oparg == 3) ? 1 : 0))]; start = stack_pointer[-2 - (((oparg == 3) ? 1 : 0))]; slice = PySlice_New(start, stop, step); @@ -1161,7 +1161,7 @@ PyObject *callargs; PyObject *func; PyObject *result; - if (oparg & 1) { kwargs = stack_pointer[-((oparg & 1))]; } + if (oparg & 1) { kwargs = stack_pointer[-((oparg & 1))]; } callargs = stack_pointer[-1 - ((oparg & 1))]; func = stack_pointer[-3 - ((oparg & 1))]; // DICT_MERGE is called before this opcode if there are kwargs. @@ -3385,7 +3385,7 @@ } } stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = self_or_null; + if (oparg & 1) stack_pointer[0] = self_or_null; stack_pointer += ((oparg & 1)); DISPATCH(); } @@ -3418,7 +3418,7 @@ Py_DECREF(owner); } stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = null; + if (oparg & 1) stack_pointer[0] = null; stack_pointer += ((oparg & 1)); DISPATCH(); } @@ -3494,7 +3494,7 @@ } /* Skip 5 cache entries */ stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = null; + if (oparg & 1) stack_pointer[0] = null; stack_pointer += ((oparg & 1)); DISPATCH(); } @@ -3536,7 +3536,7 @@ self = owner; } stack_pointer[-1] = attr; - if (1) stack_pointer[0] = self; + if (1) stack_pointer[0] = self; stack_pointer += (((1) ? 1 : 0)); DISPATCH(); } @@ -3571,7 +3571,7 @@ self = owner; } stack_pointer[-1] = attr; - if (1) stack_pointer[0] = self; + if (1) stack_pointer[0] = self; stack_pointer += (((1) ? 1 : 0)); DISPATCH(); } @@ -3618,7 +3618,7 @@ self = owner; } stack_pointer[-1] = attr; - if (1) stack_pointer[0] = self; + if (1) stack_pointer[0] = self; stack_pointer += (((1) ? 1 : 0)); DISPATCH(); } @@ -3657,7 +3657,7 @@ } /* Skip 5 cache entries */ stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = null; + if (oparg & 1) stack_pointer[0] = null; stack_pointer += ((oparg & 1)); DISPATCH(); } @@ -3799,7 +3799,7 @@ } /* Skip 5 cache entries */ stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = null; + if (oparg & 1) stack_pointer[0] = null; stack_pointer += ((oparg & 1)); DISPATCH(); } @@ -3855,7 +3855,7 @@ } /* Skip 5 cache entries */ stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = null; + if (oparg & 1) stack_pointer[0] = null; stack_pointer += ((oparg & 1)); DISPATCH(); } @@ -4083,7 +4083,7 @@ null = NULL; } stack_pointer[0] = res; - if (oparg & 1) stack_pointer[1] = null; + if (oparg & 1) stack_pointer[1] = null; stack_pointer += 1 + ((oparg & 1)); DISPATCH(); } @@ -4124,7 +4124,7 @@ null = NULL; } stack_pointer[0] = res; - if (oparg & 1) stack_pointer[1] = null; + if (oparg & 1) stack_pointer[1] = null; stack_pointer += 1 + ((oparg & 1)); DISPATCH(); } @@ -4158,7 +4158,7 @@ null = NULL; } stack_pointer[0] = res; - if (oparg & 1) stack_pointer[1] = null; + if (oparg & 1) stack_pointer[1] = null; stack_pointer += 1 + ((oparg & 1)); DISPATCH(); } @@ -4286,7 +4286,7 @@ null = NULL; } stack_pointer[-3] = attr; - if (oparg & 1) stack_pointer[-2] = null; + if (oparg & 1) stack_pointer[-2] = null; stack_pointer += -2 + ((oparg & 1)); DISPATCH(); } diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index bcc13538e51d9b..2147f6f25b8a6f 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -444,7 +444,8 @@ def analyze_forest(forest: list[parser.AstNode]) -> Analysis: if target.text in instructions: instructions[target.text].is_target = True # Hack - instructions["BINARY_OP_INPLACE_ADD_UNICODE"].family = families["BINARY_OP"] + if "BINARY_OP_INPLACE_ADD_UNICODE" in instructions: + instructions["BINARY_OP_INPLACE_ADD_UNICODE"].family = families["BINARY_OP"] return Analysis(instructions, uops, families, pseudos) diff --git a/Tools/cases_generator/cwriter.py b/Tools/cases_generator/cwriter.py index 34e39855a9b40a..67b1c9a169024c 100644 --- a/Tools/cases_generator/cwriter.py +++ b/Tools/cases_generator/cwriter.py @@ -38,7 +38,8 @@ def maybe_dedent(self, txt: str) -> None: parens = txt.count("(") - txt.count(")") if parens < 0: self.indents.pop() - elif "}" in txt or is_label(txt): + braces = txt.count("{") - txt.count("}") + if braces < 0 or is_label(txt): self.indents.pop() def maybe_indent(self, txt: str) -> None: @@ -50,11 +51,13 @@ def maybe_indent(self, txt: str) -> None: self.indents.append(offset) if is_label(txt): self.indents.append(self.indents[-1] + 4) - elif "{" in txt: - if 'extern "C"' in txt: - self.indents.append(self.indents[-1]) - else: - self.indents.append(self.indents[-1] + 4) + else: + braces = txt.count("{") - txt.count("}") + if braces > 0: + if 'extern "C"' in txt: + self.indents.append(self.indents[-1]) + else: + self.indents.append(self.indents[-1] + 4) def emit_text(self, txt: str) -> None: self.out.write(txt) diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py index e0674a7343498d..1b565bff2c56f6 100644 --- a/Tools/cases_generator/generators_common.py +++ b/Tools/cases_generator/generators_common.py @@ -22,8 +22,10 @@ def root_relative_path(filename: str) -> str: - return Path(filename).absolute().relative_to(ROOT).as_posix() - + try: + return Path(filename).absolute().relative_to(ROOT).as_posix() + except ValueError: + return filename def write_header(generator: str, sources: list[str], outfile: TextIO) -> None: outfile.write( diff --git a/Tools/cases_generator/stack.py b/Tools/cases_generator/stack.py index c36a56ebf2d381..0b31ce4090f552 100644 --- a/Tools/cases_generator/stack.py +++ b/Tools/cases_generator/stack.py @@ -148,7 +148,7 @@ def flush(self, out: CWriter) -> None: cast = "(PyObject *)" if var.type else "" if var.name != "unused" and not var.is_array(): if var.condition: - out.emit(f" if ({var.condition}) ") + out.emit(f"if ({var.condition}) ") out.emit( f"stack_pointer[{self.base_offset.to_c()}] = {cast}{var.name};\n" ) diff --git a/Tools/cases_generator/tier1_generator.py b/Tools/cases_generator/tier1_generator.py index 11885dca6fe1a2..bcfd2d88ecd734 100644 --- a/Tools/cases_generator/tier1_generator.py +++ b/Tools/cases_generator/tier1_generator.py @@ -181,6 +181,14 @@ def generate_tier1( "input", nargs=argparse.REMAINDER, help="Instruction definition file(s)" ) + +def generate_tier1_from_files( + filenames: list[str], outfilename: str, lines: bool +) -> None: + data = analyze_files(filenames) + with open(outfilename, "w") as outfile: + generate_tier1(filenames, data, outfile, lines) + if __name__ == "__main__": args = arg_parser.parse_args() if len(args.input) == 0: From 70d378cdaa99f995bdce278439ef7c4defe4f805 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 18 Dec 2023 13:16:45 +0000 Subject: [PATCH 56/62] GH-111485: Break up instructions with unused cache entries into component micro-ops (GH-113169) --- Lib/test/test_generated_cases.py | 4 ++ Python/generated_cases.c.h | 71 ++++++++++++++++++++++++ Tools/cases_generator/analyzer.py | 31 ++++++++--- Tools/cases_generator/parser.py | 1 + Tools/cases_generator/tier1_generator.py | 3 +- 5 files changed, 102 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index 74cebbe469d794..3541a4e70b9a56 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -457,6 +457,7 @@ def test_macro_instruction(self): PyObject *left; PyObject *arg2; PyObject *res; + /* Skip 5 cache entries */ right = stack_pointer[-1]; left = stack_pointer[-2]; arg2 = stack_pointer[-3]; @@ -467,6 +468,7 @@ def test_macro_instruction(self): } """ self.run_cases_test(input, output) + def test_unused_caches(self): input = """ inst(OP, (unused/1, unused/2 --)) { @@ -478,6 +480,8 @@ def test_unused_caches(self): frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(OP); + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ body(); DISPATCH(); } diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index b202d141b36f4f..a274427a699a43 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -452,6 +452,7 @@ PyObject *sub; PyObject *dict; PyObject *res; + /* Skip 1 cache entry */ sub = stack_pointer[-1]; dict = stack_pointer[-2]; DEOPT_IF(!PyDict_CheckExact(dict), BINARY_SUBSCR); @@ -476,6 +477,7 @@ static_assert(INLINE_CACHE_ENTRIES_BINARY_SUBSCR == 1, "incorrect cache size"); PyObject *sub; PyObject *container; + /* Skip 1 cache entry */ sub = stack_pointer[-1]; container = stack_pointer[-2]; DEOPT_IF(tstate->interp->eval_frame, BINARY_SUBSCR); @@ -509,6 +511,7 @@ PyObject *sub; PyObject *list; PyObject *res; + /* Skip 1 cache entry */ sub = stack_pointer[-1]; list = stack_pointer[-2]; DEOPT_IF(!PyLong_CheckExact(sub), BINARY_SUBSCR); @@ -536,6 +539,7 @@ PyObject *sub; PyObject *str; PyObject *res; + /* Skip 1 cache entry */ sub = stack_pointer[-1]; str = stack_pointer[-2]; DEOPT_IF(!PyLong_CheckExact(sub), BINARY_SUBSCR); @@ -563,6 +567,7 @@ PyObject *sub; PyObject *tuple; PyObject *res; + /* Skip 1 cache entry */ sub = stack_pointer[-1]; tuple = stack_pointer[-2]; DEOPT_IF(!PyLong_CheckExact(sub), BINARY_SUBSCR); @@ -840,6 +845,8 @@ PyObject **args; PyObject *null; PyObject *callable; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1001,6 +1008,8 @@ PyObject *self_or_null; PyObject *callable; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1035,6 +1044,8 @@ PyObject *self_or_null; PyObject *callable; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1080,6 +1091,8 @@ PyObject *self_or_null; PyObject *callable; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1119,6 +1132,8 @@ PyObject *self_or_null; PyObject *callable; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1279,6 +1294,8 @@ PyObject *self_or_null; PyObject *callable; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1408,6 +1425,8 @@ PyObject *self_or_null; PyObject *callable; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1444,6 +1463,8 @@ PyObject **args; PyObject *self; PyObject *callable; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; self = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1475,6 +1496,8 @@ PyObject *self_or_null; PyObject *callable; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1517,6 +1540,8 @@ PyObject *self_or_null; PyObject *callable; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1559,6 +1584,8 @@ PyObject *self_or_null; PyObject *callable; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1603,6 +1630,8 @@ PyObject *self_or_null; PyObject *callable; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1728,6 +1757,7 @@ PyObject **args; PyObject *self_or_null; PyObject *callable; + /* Skip 1 cache entry */ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1774,6 +1804,8 @@ PyObject *null; PyObject *callable; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1801,6 +1833,8 @@ PyObject *null; PyObject *callable; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1828,6 +1862,8 @@ PyObject *null; PyObject *callable; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1982,6 +2018,7 @@ PyObject *right; PyObject *left; PyObject *res; + /* Skip 1 cache entry */ right = stack_pointer[-1]; left = stack_pointer[-2]; DEOPT_IF(!PyFloat_CheckExact(left), COMPARE_OP); @@ -2008,6 +2045,7 @@ PyObject *right; PyObject *left; PyObject *res; + /* Skip 1 cache entry */ right = stack_pointer[-1]; left = stack_pointer[-2]; DEOPT_IF(!PyLong_CheckExact(left), COMPARE_OP); @@ -2038,6 +2076,7 @@ PyObject *right; PyObject *left; PyObject *res; + /* Skip 1 cache entry */ right = stack_pointer[-1]; left = stack_pointer[-2]; DEOPT_IF(!PyUnicode_CheckExact(left), COMPARE_OP); @@ -2469,6 +2508,7 @@ INSTRUCTION_STATS(FOR_ITER_GEN); static_assert(INLINE_CACHE_ENTRIES_FOR_ITER == 1, "incorrect cache size"); PyObject *iter; + /* Skip 1 cache entry */ iter = stack_pointer[-1]; DEOPT_IF(tstate->interp->eval_frame, FOR_ITER); PyGenObject *gen = (PyGenObject *)iter; @@ -2843,6 +2883,7 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(INSTRUMENTED_CALL); + /* Skip 3 cache entries */ int is_meth = PEEK(oparg + 1) != NULL; int total_args = oparg + is_meth; PyObject *function = PEEK(oparg + 2); @@ -2929,6 +2970,7 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(INSTRUMENTED_FOR_ITER); + /* Skip 1 cache entry */ _Py_CODEUNIT *target; PyObject *iter = TOP(); PyObject *next = (*Py_TYPE(iter)->tp_iternext)(iter); @@ -2976,6 +3018,7 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(INSTRUMENTED_JUMP_BACKWARD); + /* Skip 1 cache entry */ CHECK_EVAL_BREAKER(); INSTRUMENTED_JUMP(this_instr, next_instr - oparg, PY_MONITORING_EVENT_JUMP); DISPATCH(); @@ -2993,6 +3036,7 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(INSTRUMENTED_LOAD_SUPER_ATTR); + /* Skip 1 cache entry */ // cancel out the decrement that will happen in LOAD_SUPER_ATTR; we // don't want to specialize instrumented instructions INCREMENT_ADAPTIVE_COUNTER(this_instr[1].cache); @@ -3003,6 +3047,7 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(INSTRUMENTED_POP_JUMP_IF_FALSE); + /* Skip 1 cache entry */ PyObject *cond = POP(); assert(PyBool_Check(cond)); int flag = Py_IsFalse(cond); @@ -3018,6 +3063,7 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(INSTRUMENTED_POP_JUMP_IF_NONE); + /* Skip 1 cache entry */ PyObject *value = POP(); int flag = Py_IsNone(value); int offset; @@ -3039,6 +3085,7 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(INSTRUMENTED_POP_JUMP_IF_NOT_NONE); + /* Skip 1 cache entry */ PyObject *value = POP(); int offset; int nflag = Py_IsNone(value); @@ -3060,6 +3107,7 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(INSTRUMENTED_POP_JUMP_IF_TRUE); + /* Skip 1 cache entry */ PyObject *cond = POP(); assert(PyBool_Check(cond)); int flag = Py_IsTrue(cond); @@ -3216,6 +3264,7 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(JUMP_BACKWARD); + /* Skip 1 cache entry */ CHECK_EVAL_BREAKER(); assert(oparg <= INSTR_OFFSET()); JUMPBY(-oparg); @@ -3429,6 +3478,7 @@ INSTRUCTION_STATS(LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN); static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); PyObject *owner; + /* Skip 1 cache entry */ owner = stack_pointer[-1]; uint32_t type_version = read_u32(&this_instr[2].cache); uint32_t func_version = read_u32(&this_instr[4].cache); @@ -3743,6 +3793,7 @@ INSTRUCTION_STATS(LOAD_ATTR_PROPERTY); static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); PyObject *owner; + /* Skip 1 cache entry */ owner = stack_pointer[-1]; uint32_t type_version = read_u32(&this_instr[2].cache); uint32_t func_version = read_u32(&this_instr[4].cache); @@ -4300,6 +4351,7 @@ PyObject *class; PyObject *global_super; PyObject *attr; + /* Skip 1 cache entry */ self = stack_pointer[-1]; class = stack_pointer[-2]; global_super = stack_pointer[-3]; @@ -4328,6 +4380,7 @@ PyObject *global_super; PyObject *attr; PyObject *self_or_null; + /* Skip 1 cache entry */ self = stack_pointer[-1]; class = stack_pointer[-2]; global_super = stack_pointer[-3]; @@ -4927,6 +4980,7 @@ static_assert(INLINE_CACHE_ENTRIES_SEND == 1, "incorrect cache size"); PyObject *v; PyObject *receiver; + /* Skip 1 cache entry */ v = stack_pointer[-1]; receiver = stack_pointer[-2]; DEOPT_IF(tstate->interp->eval_frame, SEND); @@ -5157,6 +5211,7 @@ static_assert(INLINE_CACHE_ENTRIES_STORE_ATTR == 4, "incorrect cache size"); PyObject *owner; PyObject *value; + /* Skip 1 cache entry */ owner = stack_pointer[-1]; value = stack_pointer[-2]; uint32_t type_version = read_u32(&this_instr[2].cache); @@ -5374,6 +5429,7 @@ PyObject *sub; PyObject *dict; PyObject *value; + /* Skip 1 cache entry */ sub = stack_pointer[-1]; dict = stack_pointer[-2]; value = stack_pointer[-3]; @@ -5394,6 +5450,7 @@ PyObject *sub; PyObject *list; PyObject *value; + /* Skip 1 cache entry */ sub = stack_pointer[-1]; list = stack_pointer[-2]; value = stack_pointer[-3]; @@ -5470,6 +5527,7 @@ static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value; PyObject *res; + /* Skip 1 cache entry */ value = stack_pointer[-1]; uint32_t version = read_u32(&this_instr[2].cache); // This one is a bit weird, because we expect *some* failures: @@ -5488,6 +5546,8 @@ INSTRUCTION_STATS(TO_BOOL_BOOL); static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ value = stack_pointer[-1]; DEOPT_IF(!PyBool_Check(value), TO_BOOL); STAT_INC(TO_BOOL, hit); @@ -5501,6 +5561,8 @@ static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ value = stack_pointer[-1]; DEOPT_IF(!PyLong_CheckExact(value), TO_BOOL); STAT_INC(TO_BOOL, hit); @@ -5523,6 +5585,8 @@ static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ value = stack_pointer[-1]; DEOPT_IF(!PyList_CheckExact(value), TO_BOOL); STAT_INC(TO_BOOL, hit); @@ -5539,6 +5603,8 @@ static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ value = stack_pointer[-1]; // This one is a bit weird, because we expect *some* failures: DEOPT_IF(!Py_IsNone(value), TO_BOOL); @@ -5555,6 +5621,8 @@ static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ value = stack_pointer[-1]; DEOPT_IF(!PyUnicode_CheckExact(value), TO_BOOL); STAT_INC(TO_BOOL, hit); @@ -5669,6 +5737,7 @@ static_assert(INLINE_CACHE_ENTRIES_UNPACK_SEQUENCE == 1, "incorrect cache size"); PyObject *seq; PyObject **values; + /* Skip 1 cache entry */ seq = stack_pointer[-1]; values = &stack_pointer[-1]; DEOPT_IF(!PyList_CheckExact(seq), UNPACK_SEQUENCE); @@ -5690,6 +5759,7 @@ static_assert(INLINE_CACHE_ENTRIES_UNPACK_SEQUENCE == 1, "incorrect cache size"); PyObject *seq; PyObject **values; + /* Skip 1 cache entry */ seq = stack_pointer[-1]; values = &stack_pointer[-1]; DEOPT_IF(!PyTuple_CheckExact(seq), UNPACK_SEQUENCE); @@ -5711,6 +5781,7 @@ static_assert(INLINE_CACHE_ENTRIES_UNPACK_SEQUENCE == 1, "incorrect cache size"); PyObject *seq; PyObject **values; + /* Skip 1 cache entry */ seq = stack_pointer[-1]; values = &stack_pointer[-1]; DEOPT_IF(!PyTuple_CheckExact(seq), UNPACK_SEQUENCE); diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index 2147f6f25b8a6f..e077eb0a8ed203 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -234,9 +234,9 @@ def analyze_stack(op: parser.InstDef) -> StackEffect: return StackEffect(inputs, outputs) -def analyze_caches(op: parser.InstDef) -> list[CacheEntry]: +def analyze_caches(inputs: list[parser.InputEffect]) -> list[CacheEntry]: caches: list[parser.CacheEffect] = [ - i for i in op.inputs if isinstance(i, parser.CacheEffect) + i for i in inputs if isinstance(i, parser.CacheEffect) ] return [CacheEntry(i.name, int(i.size)) for i in caches] @@ -314,13 +314,13 @@ def compute_properties(op: parser.InstDef) -> Properties: ) -def make_uop(name: str, op: parser.InstDef) -> Uop: +def make_uop(name: str, op: parser.InstDef, inputs: list[parser.InputEffect]) -> Uop: return Uop( name=name, context=op.context, annotations=op.annotations, stack=analyze_stack(op), - caches=analyze_caches(op), + caches=analyze_caches(inputs), body=op.block.tokens, properties=compute_properties(op), ) @@ -333,7 +333,7 @@ def add_op(op: parser.InstDef, uops: dict[str, Uop]) -> None: raise override_error( op.name, op.context, uops[op.name].context, op.tokens[0] ) - uops[op.name] = make_uop(op.name, op) + uops[op.name] = make_uop(op.name, op, op.inputs) def add_instruction( @@ -347,10 +347,27 @@ def desugar_inst( ) -> None: assert inst.kind == "inst" name = inst.name - uop = make_uop("_" + inst.name, inst) + op_inputs: list[parser.InputEffect] = [] + parts: list[Part] = [] + uop_index = -1 + # Move unused cache entries to the Instruction, removing them from the Uop. + for input in inst.inputs: + if isinstance(input, parser.CacheEffect) and input.name == "unused": + parts.append(Skip(input.size)) + else: + op_inputs.append(input) + if uop_index < 0: + uop_index = len(parts) + # Place holder for the uop. + parts.append(Skip(0)) + uop = make_uop("_" + inst.name, inst, op_inputs) uop.implicitly_created = True uops[inst.name] = uop - add_instruction(name, [uop], instructions) + if uop_index < 0: + parts.append(uop) + else: + parts[uop_index] = uop + add_instruction(name, parts, instructions) def add_macro( diff --git a/Tools/cases_generator/parser.py b/Tools/cases_generator/parser.py index 12173a61199700..fe4e8e476eadee 100644 --- a/Tools/cases_generator/parser.py +++ b/Tools/cases_generator/parser.py @@ -7,6 +7,7 @@ Context, CacheEffect, StackEffect, + InputEffect, OpName, AstNode, ) diff --git a/Tools/cases_generator/tier1_generator.py b/Tools/cases_generator/tier1_generator.py index bcfd2d88ecd734..49cede978d821a 100644 --- a/Tools/cases_generator/tier1_generator.py +++ b/Tools/cases_generator/tier1_generator.py @@ -151,7 +151,8 @@ def generate_tier1( stack = Stack() for part in inst.parts: # Only emit braces if more than one uop - offset = write_uop(part, out, offset, stack, inst, len(inst.parts) > 1) + insert_braces = len([p for p in inst.parts if isinstance(p, Uop)]) > 1 + offset = write_uop(part, out, offset, stack, inst, insert_braces) out.start_line() if not inst.parts[-1].properties.always_exits: stack.flush(out) From 59f0766ae5aef8bd393a53ab9234371b7d165ec3 Mon Sep 17 00:00:00 2001 From: dreamflow Date: Mon, 18 Dec 2023 17:14:15 +0100 Subject: [PATCH 57/62] gh-108113: [docs] mention PyCF_OPTIMIZED_AST in ast Compiler Flags (#113241) --- Doc/library/ast.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst index 4ebbe0e5471c88..9997edd0fb5fc0 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -2457,6 +2457,13 @@ effects on the compilation of a program: Generates and returns an abstract syntax tree instead of returning a compiled code object. +.. data:: PyCF_OPTIMIZED_AST + + The returned AST is optimized according to the *optimize* argument + in :func:`compile` or :func:`ast.parse`. + + .. versionadded:: 3.13 + .. data:: PyCF_TYPE_COMMENTS Enables support for :pep:`484` and :pep:`526` style type comments From d00dbf541525fcb36c9c6ebb7b41a5637c5aa6c0 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Mon, 18 Dec 2023 16:54:49 +0000 Subject: [PATCH 58/62] gh-112535: Implement fallback implementation of _Py_ThreadId() (gh-113185) --------- Co-authored-by: Sam Gross --- Include/object.h | 6 +++++- Python/pystate.c | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Include/object.h b/Include/object.h index d22e5c2b8be2a9..48f1ddf7510887 100644 --- a/Include/object.h +++ b/Include/object.h @@ -239,6 +239,8 @@ PyAPI_FUNC(int) Py_Is(PyObject *x, PyObject *y); #define Py_Is(x, y) ((x) == (y)) #if defined(Py_GIL_DISABLED) && !defined(Py_LIMITED_API) +PyAPI_FUNC(uintptr_t) _Py_GetThreadLocal_Addr(void); + static inline uintptr_t _Py_ThreadId(void) { @@ -291,7 +293,9 @@ _Py_ThreadId(void) __asm__ ("mv %0, tp" : "=r" (tid)); #endif #else - # error "define _Py_ThreadId for this platform" + // Fallback to a portable implementation if we do not have a faster + // platform-specific implementation. + tid = _Py_GetThreadLocal_Addr(); #endif return tid; } diff --git a/Python/pystate.c b/Python/pystate.c index e18eb0186d0010..632a119ea6d4f8 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1951,6 +1951,20 @@ _PyThreadState_Bind(PyThreadState *tstate) } } +#if defined(Py_GIL_DISABLED) && !defined(Py_LIMITED_API) +uintptr_t +_Py_GetThreadLocal_Addr(void) +{ +#ifdef HAVE_THREAD_LOCAL + // gh-112535: Use the address of the thread-local PyThreadState variable as + // a unique identifier for the current thread. Each thread has a unique + // _Py_tss_tstate variable with a unique address. + return (uintptr_t)&_Py_tss_tstate; +#else +# error "no supported thread-local variable storage classifier" +#endif +} +#endif /***********************************/ /* routines for advanced debuggers */ From 2feec0fc7fd0b9caae7ab2e26e69311d3ed93e77 Mon Sep 17 00:00:00 2001 From: Itamar Oren Date: Mon, 18 Dec 2023 09:04:40 -0800 Subject: [PATCH 59/62] gh-113039: Avoid using leading dots in the include path for frozen getpath.py (GH-113022) --- Modules/getpath.c | 2 +- PCbuild/pythoncore.vcxproj | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/getpath.c b/Modules/getpath.c index afa9273ebc59b7..a3c8fc269d1c3c 100644 --- a/Modules/getpath.c +++ b/Modules/getpath.c @@ -22,7 +22,7 @@ #endif /* Reference the precompiled getpath.py */ -#include "../Python/frozen_modules/getpath.h" +#include "Python/frozen_modules/getpath.h" #if (!defined(PREFIX) || !defined(EXEC_PREFIX) \ || !defined(VERSION) || !defined(VPATH) \ diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 90aa8cf28f8c5d..c90ad1a3592f67 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -120,6 +120,7 @@ PLATLIBDIR="DLLs"; %(PreprocessorDefinitions) + $(PySourcePath);%(AdditionalIncludeDirectories) From 41336a72b90634d5ac74a57b6826e4dd6fe78eac Mon Sep 17 00:00:00 2001 From: Illia Volochii Date: Mon, 18 Dec 2023 22:17:16 +0200 Subject: [PATCH 60/62] gh-113199: Make read1() and readline() of HTTPResponse close IO after reading all data (GH-113200) --- Lib/http/client.py | 4 ++++ Lib/test/test_httplib.py | 16 ++++++++++++++-- ...023-12-16-01-10-47.gh-issue-113199.oDjnjL.rst | 3 +++ 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-16-01-10-47.gh-issue-113199.oDjnjL.rst diff --git a/Lib/http/client.py b/Lib/http/client.py index 7bb5d824bb6da4..5eebfccafbca59 100644 --- a/Lib/http/client.py +++ b/Lib/http/client.py @@ -665,6 +665,8 @@ def read1(self, n=-1): self._close_conn() elif self.length is not None: self.length -= len(result) + if not self.length: + self._close_conn() return result def peek(self, n=-1): @@ -689,6 +691,8 @@ def readline(self, limit=-1): self._close_conn() elif self.length is not None: self.length -= len(result) + if not self.length: + self._close_conn() return result def _read1_chunked(self, n): diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py index caa4c76a913a01..089bf5be40a0e2 100644 --- a/Lib/test/test_httplib.py +++ b/Lib/test/test_httplib.py @@ -1546,11 +1546,14 @@ def test_readline(self): resp = self.resp self._verify_readline(self.resp.readline, self.lines_expected) - def _verify_readline(self, readline, expected): + def test_readline_without_limit(self): + self._verify_readline(self.resp.readline, self.lines_expected, limit=-1) + + def _verify_readline(self, readline, expected, limit=5): all = [] while True: # short readlines - line = readline(5) + line = readline(limit) if line and line != b"foo": if len(line) < 5: self.assertTrue(line.endswith(b"\n")) @@ -1558,6 +1561,7 @@ def _verify_readline(self, readline, expected): if not line: break self.assertEqual(b"".join(all), expected) + self.assertTrue(self.resp.isclosed()) def test_read1(self): resp = self.resp @@ -1577,6 +1581,7 @@ def test_read1_unbounded(self): break all.append(data) self.assertEqual(b"".join(all), self.lines_expected) + self.assertTrue(resp.isclosed()) def test_read1_bounded(self): resp = self.resp @@ -1588,15 +1593,22 @@ def test_read1_bounded(self): self.assertLessEqual(len(data), 10) all.append(data) self.assertEqual(b"".join(all), self.lines_expected) + self.assertTrue(resp.isclosed()) def test_read1_0(self): self.assertEqual(self.resp.read1(0), b"") + self.assertFalse(self.resp.isclosed()) def test_peek_0(self): p = self.resp.peek(0) self.assertLessEqual(0, len(p)) +class ExtendedReadTestContentLengthKnown(ExtendedReadTest): + _header, _body = ExtendedReadTest.lines.split('\r\n\r\n', 1) + lines = _header + f'\r\nContent-Length: {len(_body)}\r\n\r\n' + _body + + class ExtendedReadTestChunked(ExtendedReadTest): """ Test peek(), read1(), readline() in chunked mode diff --git a/Misc/NEWS.d/next/Library/2023-12-16-01-10-47.gh-issue-113199.oDjnjL.rst b/Misc/NEWS.d/next/Library/2023-12-16-01-10-47.gh-issue-113199.oDjnjL.rst new file mode 100644 index 00000000000000..d8e0b1731d1e3b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-16-01-10-47.gh-issue-113199.oDjnjL.rst @@ -0,0 +1,3 @@ +Make ``http.client.HTTPResponse.read1`` and +``http.client.HTTPResponse.readline`` close IO after reading all data when +content length is known. Patch by Illia Volochii. From 4cfce3a4da7ca9513e7f2c8ec94d50f8bddfa41b Mon Sep 17 00:00:00 2001 From: Ronald Oussoren Date: Tue, 19 Dec 2023 00:41:41 +0100 Subject: [PATCH 61/62] gh-108269: Add CFBundleAllowMixedLocalizations to Info.plist on macOS (GH-113213) Adding this key with a value of true enables detecting the users prefered language in libraries accessing system APIs for this. --- Mac/IDLE/IDLE.app/Contents/Info.plist | 2 ++ Mac/Resources/app/Info.plist.in | 2 ++ Mac/Resources/framework/Info.plist.in | 2 ++ .../next/macOS/2023-12-16-11-45-32.gh-issue-108269.wVgCHF.rst | 4 ++++ 4 files changed, 10 insertions(+) create mode 100644 Misc/NEWS.d/next/macOS/2023-12-16-11-45-32.gh-issue-108269.wVgCHF.rst diff --git a/Mac/IDLE/IDLE.app/Contents/Info.plist b/Mac/IDLE/IDLE.app/Contents/Info.plist index fea06d46324999..20b97b67f41d1a 100644 --- a/Mac/IDLE/IDLE.app/Contents/Info.plist +++ b/Mac/IDLE/IDLE.app/Contents/Info.plist @@ -56,5 +56,7 @@ %version% NSHighResolutionCapable + CFBundleAllowMixedLocalizations + diff --git a/Mac/Resources/app/Info.plist.in b/Mac/Resources/app/Info.plist.in index 4ec828ff176e7b..8362b19b361b62 100644 --- a/Mac/Resources/app/Info.plist.in +++ b/Mac/Resources/app/Info.plist.in @@ -58,5 +58,7 @@ (c) 2001-2023 Python Software Foundation. NSHighResolutionCapable + CFBundleAllowMixedLocalizations + diff --git a/Mac/Resources/framework/Info.plist.in b/Mac/Resources/framework/Info.plist.in index e131c205ef0b28..238441ce2c76c7 100644 --- a/Mac/Resources/framework/Info.plist.in +++ b/Mac/Resources/framework/Info.plist.in @@ -24,5 +24,7 @@ ???? CFBundleVersion %VERSION% + CFBundleAllowMixedLocalizations + diff --git a/Misc/NEWS.d/next/macOS/2023-12-16-11-45-32.gh-issue-108269.wVgCHF.rst b/Misc/NEWS.d/next/macOS/2023-12-16-11-45-32.gh-issue-108269.wVgCHF.rst new file mode 100644 index 00000000000000..85598454abcaad --- /dev/null +++ b/Misc/NEWS.d/next/macOS/2023-12-16-11-45-32.gh-issue-108269.wVgCHF.rst @@ -0,0 +1,4 @@ +Set ``CFBundleAllowMixedLocalizations`` to true in the Info.plist for the +framework, embedded Python.app and IDLE.app with framework installs on +macOS. This allows applications to pick up the user's preferred locale when +that's different from english. From 893c9ccf48eacb02fa6ae93632f2d0cb6778dbb6 Mon Sep 17 00:00:00 2001 From: Ronald Oussoren Date: Tue, 19 Dec 2023 00:51:58 +0100 Subject: [PATCH 62/62] gh-102362: Fix macOS version number in result of sysconfig.get_platform() (GH-112942) Change _osx_support.get_platform_osx() to make sure that the version number in the result includes at least a major and minor version (e.g. 14.2) even if MACOSX_DEPLOYMENT_TARGET is set to just a major version (e.g. 14). This matches the versions expected by pip when selecting appropriate wheels for installation. --- Lib/_osx_support.py | 5 +++++ .../macOS/2023-12-10-20-30-06.gh-issue-102362.y8svbF.rst | 3 +++ 2 files changed, 8 insertions(+) create mode 100644 Misc/NEWS.d/next/macOS/2023-12-10-20-30-06.gh-issue-102362.y8svbF.rst diff --git a/Lib/_osx_support.py b/Lib/_osx_support.py index aa66c8b9f4189f..0cb064fcd791be 100644 --- a/Lib/_osx_support.py +++ b/Lib/_osx_support.py @@ -507,6 +507,11 @@ def get_platform_osx(_config_vars, osname, release, machine): # MACOSX_DEPLOYMENT_TARGET. macver = _config_vars.get('MACOSX_DEPLOYMENT_TARGET', '') + if macver and '.' not in macver: + # Ensure that the version includes at least a major + # and minor version, even if MACOSX_DEPLOYMENT_TARGET + # is set to a single-label version like "14". + macver += '.0' macrelease = _get_system_version() or macver macver = macver or macrelease diff --git a/Misc/NEWS.d/next/macOS/2023-12-10-20-30-06.gh-issue-102362.y8svbF.rst b/Misc/NEWS.d/next/macOS/2023-12-10-20-30-06.gh-issue-102362.y8svbF.rst new file mode 100644 index 00000000000000..55c5ac01434660 --- /dev/null +++ b/Misc/NEWS.d/next/macOS/2023-12-10-20-30-06.gh-issue-102362.y8svbF.rst @@ -0,0 +1,3 @@ +Make sure the result of :func:`sysconfig.get_plaform` includes at least a +major and minor versions, even if ``MACOSX_DEPLOYMENT_TARGET`` is set to +only a major version during build to match the format expected by pip.