diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 892f5ba9a7624e..ca5dac87aff2b4 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -1700,6 +1700,14 @@ which is a bitmap of the following flags: .. versionadded:: 3.6 +.. data:: CO_HAS_DOCSTRING + + The flag is set when there is a docstring for the code object in + the source code. If set, it will be the first item in + :attr:`~codeobject.co_consts`. + + .. versionadded:: 3.14 + .. note:: The flags are specific to CPython, and may not be defined in other Python implementations. Furthermore, the flags are an implementation diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index e5f2dcd5bc9523..aceb95f2758e38 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1536,9 +1536,9 @@ Other bits in :attr:`~codeobject.co_flags` are reserved for internal use. .. index:: single: documentation string -If a code object represents a function, the first item in -:attr:`~codeobject.co_consts` is -the documentation string of the function, or ``None`` if undefined. +If a code object represents a function and has a docstring, +the first item in :attr:`~codeobject.co_consts` is +the docstring of the function. Methods on code objects ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Include/cpython/code.h b/Include/cpython/code.h index af9149b9c38e62..2561b2b88baacc 100644 --- a/Include/cpython/code.h +++ b/Include/cpython/code.h @@ -174,6 +174,11 @@ struct PyCodeObject _PyCode_DEF(1); #define CO_NO_MONITORING_EVENTS 0x2000000 +/* Whether the code object has a docstring, + If so, it will be the first item in co_consts +*/ +#define CO_HAS_DOCSTRING 0x4000000 + /* This should be defined if a future statement modifies the syntax. For example, when a keyword is added. */ diff --git a/Include/internal/pycore_symtable.h b/Include/internal/pycore_symtable.h index 7e3d45adcecc1a..91dac767d5885b 100644 --- a/Include/internal/pycore_symtable.h +++ b/Include/internal/pycore_symtable.h @@ -123,6 +123,7 @@ typedef struct _symtable_entry { unsigned ste_comp_iter_target : 1; /* true if visiting comprehension target */ unsigned ste_can_see_class_scope : 1; /* true if this block can see names bound in an enclosing class scope */ + unsigned ste_has_docstring : 1; /* true if docstring present */ int ste_comp_iter_expr; /* non-zero if visiting a comprehension range expression */ _Py_SourceLocation ste_loc; /* source location of block */ struct _symtable_entry *ste_annotation_block; /* symbol table entry for this entry's annotations */ diff --git a/Lib/dis.py b/Lib/dis.py index c28fa6b3dc43be..1718e39cceb638 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -151,16 +151,17 @@ def distb(tb=None, *, file=None, show_caches=False, adaptive=False, show_offsets # list of CO_* constants. It is also used by pretty_flags to # turn the co_flags field into a human readable list. COMPILER_FLAG_NAMES = { - 1: "OPTIMIZED", - 2: "NEWLOCALS", - 4: "VARARGS", - 8: "VARKEYWORDS", - 16: "NESTED", - 32: "GENERATOR", - 64: "NOFREE", - 128: "COROUTINE", - 256: "ITERABLE_COROUTINE", - 512: "ASYNC_GENERATOR", + 1: "OPTIMIZED", + 2: "NEWLOCALS", + 4: "VARARGS", + 8: "VARKEYWORDS", + 16: "NESTED", + 32: "GENERATOR", + 64: "NOFREE", + 128: "COROUTINE", + 256: "ITERABLE_COROUTINE", + 512: "ASYNC_GENERATOR", + 0x4000000: "HAS_DOCSTRING", } def pretty_flags(flags): diff --git a/Lib/inspect.py b/Lib/inspect.py index ea0d992436eb17..08718d82e91582 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -56,6 +56,7 @@ "CO_OPTIMIZED", "CO_VARARGS", "CO_VARKEYWORDS", + "CO_HAS_DOCSTRING", "ClassFoundException", "ClosureVars", "EndOfBlock", @@ -409,6 +410,7 @@ def iscode(object): co_flags bitmap: 1=optimized | 2=newlocals | 4=*arg | 8=**arg | 16=nested | 32=generator | 64=nofree | 128=coroutine | 256=iterable_coroutine | 512=async_generator + | 0x4000000=has_docstring co_freevars tuple of names of free variables co_posonlyargcount number of positional only arguments co_kwonlyargcount number of keyword only arguments (not including ** arg) diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py index dcdd15a43e66e8..93c65a82508dcb 100644 --- a/Lib/test/test_code.py +++ b/Lib/test/test_code.py @@ -17,7 +17,7 @@ freevars: () nlocals: 2 flags: 3 -consts: ('None', '') +consts: ('',) >>> dump(f(4).__code__) name: g @@ -86,7 +86,7 @@ cellvars: () freevars: () nlocals: 0 -flags: 3 +flags: 67108867 consts: ("'doc string'", 'None') >>> def keywordonly_args(a,b,*,k1): @@ -123,6 +123,61 @@ flags: 3 consts: ('None',) +>>> def has_docstring(x: str): +... 'This is a one-line doc string' +... x += x +... x += "hello world" +... # co_flags should be 0x4000003 = 67108867 +... return x + +>>> dump(has_docstring.__code__) +name: has_docstring +argcount: 1 +posonlyargcount: 0 +kwonlyargcount: 0 +names: () +varnames: ('x',) +cellvars: () +freevars: () +nlocals: 1 +flags: 67108867 +consts: ("'This is a one-line doc string'", "'hello world'") + +>>> async def async_func_docstring(x: str, y: str): +... "This is a docstring from async function" +... import asyncio +... await asyncio.sleep(1) +... # co_flags should be 0x4000083 = 67108995 +... return x + y + +>>> dump(async_func_docstring.__code__) +name: async_func_docstring +argcount: 2 +posonlyargcount: 0 +kwonlyargcount: 0 +names: ('asyncio', 'sleep') +varnames: ('x', 'y', 'asyncio') +cellvars: () +freevars: () +nlocals: 3 +flags: 67108995 +consts: ("'This is a docstring from async function'", 'None') + +>>> def no_docstring(x, y, z): +... return x + "hello" + y + z + "world" + +>>> dump(no_docstring.__code__) +name: no_docstring +argcount: 3 +posonlyargcount: 0 +kwonlyargcount: 0 +names: () +varnames: ('x', 'y', 'z') +cellvars: () +freevars: () +nlocals: 3 +flags: 3 +consts: ("'hello'", "'world'") """ import copy diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 958170f675e1d9..85ae71c1f77b28 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -834,7 +834,7 @@ def f(): return "unused" self.assertEqual(f.__code__.co_consts, - (None, "used")) + (True, "used")) @support.cpython_only def test_remove_unused_consts_extended_args(self): @@ -852,9 +852,9 @@ def test_remove_unused_consts_extended_args(self): eval(compile(code, "file.py", "exec"), g) exec(code, g) f = g['f'] - expected = tuple([None, ''] + [f't{i}' for i in range(N)]) + expected = tuple([''] + [f't{i}' for i in range(N)]) self.assertEqual(f.__code__.co_consts, expected) - expected = "".join(expected[2:]) + expected = "".join(expected[1:]) self.assertEqual(expected, f()) # Stripping unused constants is not a strict requirement for the @@ -1244,7 +1244,7 @@ def return_genexp(): y) genexp_lines = [0, 4, 2, 0, 4] - genexp_code = return_genexp.__code__.co_consts[1] + genexp_code = return_genexp.__code__.co_consts[0] code_lines = self.get_code_lines(genexp_code) self.assertEqual(genexp_lines, code_lines) diff --git a/Lib/test/test_compiler_assemble.py b/Lib/test/test_compiler_assemble.py index 625d3c704c3529..c4962e3599986e 100644 --- a/Lib/test/test_compiler_assemble.py +++ b/Lib/test/test_compiler_assemble.py @@ -84,7 +84,7 @@ def inner(): return x return inner() % 2 - inner_code = mod_two.__code__.co_consts[1] + inner_code = mod_two.__code__.co_consts[0] assert isinstance(inner_code, types.CodeType) metadata = { diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index bd1d1735181b7b..3c6570afa50d45 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -197,7 +197,7 @@ def bug1333982(x=[]): %3d RESUME 0 %3d LOAD_COMMON_CONSTANT 0 (AssertionError) - LOAD_CONST 1 ( at 0x..., file "%s", line %d>) + LOAD_CONST 0 ( at 0x..., file "%s", line %d>) MAKE_FUNCTION LOAD_FAST 0 (x) GET_ITER @@ -276,10 +276,10 @@ def wrap_func_w_kwargs(): LOAD_SMALL_INT 1 LOAD_SMALL_INT 2 LOAD_SMALL_INT 5 - LOAD_CONST 1 (('c',)) + LOAD_CONST 0 (('c',)) CALL_KW 3 POP_TOP - LOAD_CONST 0 (None) + LOAD_CONST 1 (None) RETURN_VALUE """ % (wrap_func_w_kwargs.__code__.co_firstlineno, wrap_func_w_kwargs.__code__.co_firstlineno + 1) @@ -500,18 +500,18 @@ def _fstring(a, b, c, d): %3d LOAD_FAST 0 (a) FORMAT_SIMPLE - LOAD_CONST 1 (' ') + LOAD_CONST 0 (' ') LOAD_FAST 1 (b) - LOAD_CONST 2 ('4') + LOAD_CONST 1 ('4') FORMAT_WITH_SPEC - LOAD_CONST 1 (' ') + LOAD_CONST 0 (' ') LOAD_FAST 2 (c) CONVERT_VALUE 2 (repr) FORMAT_SIMPLE - LOAD_CONST 1 (' ') + LOAD_CONST 0 (' ') LOAD_FAST 3 (d) CONVERT_VALUE 2 (repr) - LOAD_CONST 2 ('4') + LOAD_CONST 1 ('4') FORMAT_WITH_SPEC BUILD_STRING 7 RETURN_VALUE @@ -785,7 +785,7 @@ def foo(x): %4d LOAD_FAST 0 (y) BUILD_TUPLE 1 - LOAD_CONST 1 () + LOAD_CONST 0 () MAKE_FUNCTION SET_FUNCTION_ATTRIBUTE 8 (closure) STORE_FAST 1 (foo) @@ -884,7 +884,7 @@ def loop_test(): %3d RESUME_CHECK 0 %3d BUILD_LIST 0 - LOAD_CONST 1 ((1, 2, 3)) + LOAD_CONST 0 ((1, 2, 3)) LIST_EXTEND 1 LOAD_SMALL_INT 3 BINARY_OP 5 (*) @@ -900,7 +900,7 @@ def loop_test(): %3d L2: END_FOR POP_TOP - LOAD_CONST_IMMORTAL 0 (None) + LOAD_CONST_IMMORTAL 1 (None) RETURN_VALUE """ % (loop_test.__code__.co_firstlineno, loop_test.__code__.co_firstlineno + 1, @@ -913,12 +913,12 @@ def extended_arg_quick(): dis_extended_arg_quick_code = """\ %3d RESUME 0 -%3d LOAD_CONST 1 (Ellipsis) +%3d LOAD_CONST 0 (Ellipsis) EXTENDED_ARG 1 UNPACK_EX 256 POP_TOP STORE_FAST 0 (_) - LOAD_CONST 0 (None) + LOAD_CONST 1 (None) RETURN_VALUE """% (extended_arg_quick.__code__.co_firstlineno, extended_arg_quick.__code__.co_firstlineno + 1,) @@ -1465,7 +1465,7 @@ def get_disassembly(self, func, lasti=-1, wrapper=True, **kwargs): Kw-only arguments: 0 Number of locals: 1 Stack size: \\d+ -Flags: OPTIMIZED, NEWLOCALS +Flags: OPTIMIZED, NEWLOCALS, HAS_DOCSTRING Constants: {code_info_consts} Names: @@ -1491,8 +1491,8 @@ def f(c=c): Stack size: \\d+ Flags: OPTIMIZED, NEWLOCALS, VARARGS, VARKEYWORDS, GENERATOR Constants: - 0: None - 1: + 0: + 1: None Variable names: 0: a 1: b @@ -1510,10 +1510,12 @@ def f(c=c): 2: [abedfxyz] 3: [abedfxyz] 4: [abedfxyz] - 5: [abedfxyz]""" + 5: [abedfxyz] + 6: [abedfxyz] + 7: [abedfxyz]""" # NOTE: the order of the cell variables above depends on dictionary order! -co_tricky_nested_f = tricky.__func__.__code__.co_consts[1] +co_tricky_nested_f = tricky.__func__.__code__.co_consts[0] code_info_tricky_nested_f = """\ Filename: (.*) @@ -1677,9 +1679,9 @@ def jumpy(): # End fodder for opinfo generation tests expected_outer_line = 1 _line_offset = outer.__code__.co_firstlineno - 1 -code_object_f = outer.__code__.co_consts[1] +code_object_f = outer.__code__.co_consts[0] expected_f_line = code_object_f.co_firstlineno - _line_offset -code_object_inner = code_object_f.co_consts[1] +code_object_inner = code_object_f.co_consts[0] expected_inner_line = code_object_inner.co_firstlineno - _line_offset expected_jumpy_line = 1 @@ -1735,11 +1737,11 @@ def _prepare_test_cases(): Instruction(opname='MAKE_CELL', opcode=92, arg=0, argval='a', argrepr='a', offset=0, start_offset=0, starts_line=True, line_number=None, label=None, positions=None, cache_info=None), Instruction(opname='MAKE_CELL', opcode=92, arg=1, argval='b', argrepr='b', offset=2, start_offset=2, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), Instruction(opname='RESUME', opcode=149, arg=0, argval=0, argrepr='', offset=4, start_offset=4, starts_line=True, line_number=1, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_CONST', opcode=79, arg=4, argval=(3, 4), argrepr='(3, 4)', offset=6, start_offset=6, starts_line=True, line_number=2, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=79, arg=3, argval=(3, 4), argrepr='(3, 4)', offset=6, start_offset=6, starts_line=True, line_number=2, label=None, positions=None, cache_info=None), Instruction(opname='LOAD_FAST', opcode=81, arg=0, argval='a', argrepr='a', offset=8, start_offset=8, starts_line=False, line_number=2, label=None, positions=None, cache_info=None), Instruction(opname='LOAD_FAST', opcode=81, arg=1, argval='b', argrepr='b', offset=10, start_offset=10, starts_line=False, line_number=2, label=None, positions=None, cache_info=None), Instruction(opname='BUILD_TUPLE', opcode=48, arg=2, argval=2, argrepr='', offset=12, start_offset=12, starts_line=False, line_number=2, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_CONST', opcode=79, arg=1, argval=code_object_f, argrepr=repr(code_object_f), offset=14, start_offset=14, starts_line=False, line_number=2, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=79, arg=0, argval=code_object_f, argrepr=repr(code_object_f), offset=14, start_offset=14, starts_line=False, line_number=2, label=None, positions=None, cache_info=None), Instruction(opname='MAKE_FUNCTION', opcode=23, arg=None, argval=None, argrepr='', offset=16, start_offset=16, starts_line=False, line_number=2, label=None, positions=None, cache_info=None), Instruction(opname='SET_FUNCTION_ATTRIBUTE', opcode=103, arg=8, argval=8, argrepr='closure', offset=18, start_offset=18, starts_line=False, line_number=2, label=None, positions=None, cache_info=None), Instruction(opname='SET_FUNCTION_ATTRIBUTE', opcode=103, arg=1, argval=1, argrepr='defaults', offset=20, start_offset=20, starts_line=False, line_number=2, label=None, positions=None, cache_info=None), @@ -1747,11 +1749,11 @@ def _prepare_test_cases(): Instruction(opname='LOAD_GLOBAL', opcode=87, arg=1, argval='print', argrepr='print + NULL', offset=24, start_offset=24, starts_line=True, line_number=7, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), Instruction(opname='LOAD_DEREF', opcode=80, arg=0, argval='a', argrepr='a', offset=34, start_offset=34, starts_line=False, line_number=7, label=None, positions=None, cache_info=None), Instruction(opname='LOAD_DEREF', opcode=80, arg=1, argval='b', argrepr='b', offset=36, start_offset=36, starts_line=False, line_number=7, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_CONST', opcode=79, arg=2, argval='', argrepr="''", offset=38, start_offset=38, starts_line=False, line_number=7, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=79, arg=1, argval='', argrepr="''", offset=38, start_offset=38, starts_line=False, line_number=7, label=None, positions=None, cache_info=None), Instruction(opname='LOAD_SMALL_INT', opcode=89, arg=1, argval=1, argrepr='', offset=40, start_offset=40, starts_line=False, line_number=7, label=None, positions=None, cache_info=None), Instruction(opname='BUILD_LIST', opcode=43, arg=0, argval=0, argrepr='', offset=42, start_offset=42, starts_line=False, line_number=7, label=None, positions=None, cache_info=None), Instruction(opname='BUILD_MAP', opcode=44, arg=0, argval=0, argrepr='', offset=44, start_offset=44, starts_line=False, line_number=7, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_CONST', opcode=79, arg=3, argval='Hello world!', argrepr="'Hello world!'", offset=46, start_offset=46, starts_line=False, line_number=7, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=79, arg=2, argval='Hello world!', argrepr="'Hello world!'", offset=46, start_offset=46, starts_line=False, line_number=7, label=None, positions=None, cache_info=None), Instruction(opname='CALL', opcode=49, arg=7, argval=7, argrepr='', offset=48, start_offset=48, starts_line=False, line_number=7, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), Instruction(opname='POP_TOP', opcode=29, arg=None, argval=None, argrepr='', offset=56, start_offset=56, starts_line=False, line_number=7, label=None, positions=None, cache_info=None), Instruction(opname='LOAD_FAST', opcode=81, arg=2, argval='f', argrepr='f', offset=58, start_offset=58, starts_line=True, line_number=8, label=None, positions=None, cache_info=None), @@ -1763,13 +1765,13 @@ def _prepare_test_cases(): Instruction(opname='MAKE_CELL', opcode=92, arg=0, argval='c', argrepr='c', offset=2, start_offset=2, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), Instruction(opname='MAKE_CELL', opcode=92, arg=1, argval='d', argrepr='d', offset=4, start_offset=4, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), Instruction(opname='RESUME', opcode=149, arg=0, argval=0, argrepr='', offset=6, start_offset=6, starts_line=True, line_number=2, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_CONST', opcode=79, arg=2, argval=(5, 6), argrepr='(5, 6)', offset=8, start_offset=8, starts_line=True, line_number=3, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=79, arg=1, argval=(5, 6), argrepr='(5, 6)', offset=8, start_offset=8, starts_line=True, line_number=3, label=None, positions=None, cache_info=None), Instruction(opname='LOAD_FAST', opcode=81, arg=3, argval='a', argrepr='a', offset=10, start_offset=10, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), Instruction(opname='LOAD_FAST', opcode=81, arg=4, argval='b', argrepr='b', offset=12, start_offset=12, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), Instruction(opname='LOAD_FAST', opcode=81, arg=0, argval='c', argrepr='c', offset=14, start_offset=14, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), Instruction(opname='LOAD_FAST', opcode=81, arg=1, argval='d', argrepr='d', offset=16, start_offset=16, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), Instruction(opname='BUILD_TUPLE', opcode=48, arg=4, argval=4, argrepr='', offset=18, start_offset=18, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_CONST', opcode=79, arg=1, argval=code_object_inner, argrepr=repr(code_object_inner), offset=20, start_offset=20, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=79, arg=0, argval=code_object_inner, argrepr=repr(code_object_inner), offset=20, start_offset=20, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), Instruction(opname='MAKE_FUNCTION', opcode=23, arg=None, argval=None, argrepr='', offset=22, start_offset=22, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), Instruction(opname='SET_FUNCTION_ATTRIBUTE', opcode=103, arg=8, argval=8, argrepr='closure', offset=24, start_offset=24, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), Instruction(opname='SET_FUNCTION_ATTRIBUTE', opcode=103, arg=1, argval=1, argrepr='defaults', offset=26, start_offset=26, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), @@ -1827,7 +1829,7 @@ def _prepare_test_cases(): Instruction(opname='END_FOR', opcode=9, arg=None, argval=None, argrepr='', offset=88, start_offset=88, starts_line=True, line_number=3, label=4, positions=None, cache_info=None), Instruction(opname='POP_TOP', opcode=29, arg=None, argval=None, argrepr='', offset=90, start_offset=90, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), Instruction(opname='LOAD_GLOBAL', opcode=87, arg=3, argval='print', argrepr='print + NULL', offset=92, start_offset=92, starts_line=True, line_number=10, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - Instruction(opname='LOAD_CONST', opcode=79, arg=1, argval='I can haz else clause?', argrepr="'I can haz else clause?'", offset=102, start_offset=102, starts_line=False, line_number=10, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=79, arg=0, argval='I can haz else clause?', argrepr="'I can haz else clause?'", offset=102, start_offset=102, starts_line=False, line_number=10, label=None, positions=None, cache_info=None), Instruction(opname='CALL', opcode=49, arg=1, argval=1, argrepr='', offset=104, start_offset=104, starts_line=False, line_number=10, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), Instruction(opname='POP_TOP', opcode=29, arg=None, argval=None, argrepr='', offset=112, start_offset=112, starts_line=False, line_number=10, label=None, positions=None, cache_info=None), Instruction(opname='LOAD_FAST_CHECK', opcode=83, arg=0, argval='i', argrepr='i', offset=114, start_offset=114, starts_line=True, line_number=11, label=5, positions=None, cache_info=None), @@ -1853,7 +1855,7 @@ def _prepare_test_cases(): Instruction(opname='JUMP_BACKWARD', opcode=72, arg=39, argval=114, argrepr='to L5', offset=188, start_offset=188, starts_line=False, line_number=16, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), Instruction(opname='JUMP_FORWARD', opcode=74, arg=11, argval=216, argrepr='to L9', offset=192, start_offset=192, starts_line=True, line_number=17, label=7, positions=None, cache_info=None), Instruction(opname='LOAD_GLOBAL', opcode=87, arg=3, argval='print', argrepr='print + NULL', offset=194, start_offset=194, starts_line=True, line_number=19, label=8, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - Instruction(opname='LOAD_CONST', opcode=79, arg=2, argval='Who let lolcatz into this test suite?', argrepr="'Who let lolcatz into this test suite?'", offset=204, start_offset=204, starts_line=False, line_number=19, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=79, arg=1, argval='Who let lolcatz into this test suite?', argrepr="'Who let lolcatz into this test suite?'", offset=204, start_offset=204, starts_line=False, line_number=19, label=None, positions=None, cache_info=None), Instruction(opname='CALL', opcode=49, arg=1, argval=1, argrepr='', offset=206, start_offset=206, starts_line=False, line_number=19, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), Instruction(opname='POP_TOP', opcode=29, arg=None, argval=None, argrepr='', offset=214, start_offset=214, starts_line=False, line_number=19, label=None, positions=None, cache_info=None), Instruction(opname='NOP', opcode=27, arg=None, argval=None, argrepr='', offset=216, start_offset=216, starts_line=True, line_number=20, label=9, positions=None, cache_info=None), @@ -1870,19 +1872,19 @@ def _prepare_test_cases(): Instruction(opname='CALL', opcode=49, arg=0, argval=0, argrepr='', offset=240, start_offset=240, starts_line=False, line_number=25, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), Instruction(opname='STORE_FAST', opcode=107, arg=1, argval='dodgy', argrepr='dodgy', offset=248, start_offset=248, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), Instruction(opname='LOAD_GLOBAL', opcode=87, arg=3, argval='print', argrepr='print + NULL', offset=250, start_offset=250, starts_line=True, line_number=26, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - Instruction(opname='LOAD_CONST', opcode=79, arg=3, argval='Never reach this', argrepr="'Never reach this'", offset=260, start_offset=260, starts_line=False, line_number=26, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=79, arg=2, argval='Never reach this', argrepr="'Never reach this'", offset=260, start_offset=260, starts_line=False, line_number=26, label=None, positions=None, cache_info=None), Instruction(opname='CALL', opcode=49, arg=1, argval=1, argrepr='', offset=262, start_offset=262, starts_line=False, line_number=26, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), Instruction(opname='POP_TOP', opcode=29, arg=None, argval=None, argrepr='', offset=270, start_offset=270, starts_line=False, line_number=26, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_CONST', opcode=79, arg=0, argval=None, argrepr='None', offset=272, start_offset=272, starts_line=True, line_number=25, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_CONST', opcode=79, arg=0, argval=None, argrepr='None', offset=274, start_offset=274, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_CONST', opcode=79, arg=0, argval=None, argrepr='None', offset=276, start_offset=276, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=79, arg=3, argval=None, argrepr='None', offset=272, start_offset=272, starts_line=True, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=79, arg=3, argval=None, argrepr='None', offset=274, start_offset=274, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=79, arg=3, argval=None, argrepr='None', offset=276, start_offset=276, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), Instruction(opname='CALL', opcode=49, arg=3, argval=3, argrepr='', offset=278, start_offset=278, starts_line=False, line_number=25, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), Instruction(opname='POP_TOP', opcode=29, arg=None, argval=None, argrepr='', offset=286, start_offset=286, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), Instruction(opname='LOAD_GLOBAL', opcode=87, arg=3, argval='print', argrepr='print + NULL', offset=288, start_offset=288, starts_line=True, line_number=28, label=10, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), Instruction(opname='LOAD_CONST', opcode=79, arg=5, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=298, start_offset=298, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), Instruction(opname='CALL', opcode=49, arg=1, argval=1, argrepr='', offset=300, start_offset=300, starts_line=False, line_number=28, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), Instruction(opname='POP_TOP', opcode=29, arg=None, argval=None, argrepr='', offset=308, start_offset=308, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_CONST', opcode=79, arg=0, argval=None, argrepr='None', offset=310, start_offset=310, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=79, arg=3, argval=None, argrepr='None', offset=310, start_offset=310, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), Instruction(opname='RETURN_VALUE', opcode=33, arg=None, argval=None, argrepr='', offset=312, start_offset=312, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), Instruction(opname='PUSH_EXC_INFO', opcode=30, arg=None, argval=None, argrepr='', offset=314, start_offset=314, starts_line=True, line_number=25, label=None, positions=None, cache_info=None), Instruction(opname='WITH_EXCEPT_START', opcode=41, arg=None, argval=None, argrepr='', offset=316, start_offset=316, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index a4857dadec2d5c..2250b7e76dac01 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -5373,7 +5373,7 @@ def test_signature_bind_implicit_arg(self): # Issue #19611: getcallargs should work with comprehensions def make_set(): return set(z * z for z in range(5)) - gencomp_code = make_set.__code__.co_consts[1] + gencomp_code = make_set.__code__.co_consts[0] gencomp_func = types.FunctionType(gencomp_code, {}) iterator = iter(range(5)) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-10-29-10-37-39.gh-issue-126072.XLKlxv.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-10-29-10-37-39.gh-issue-126072.XLKlxv.rst new file mode 100644 index 00000000000000..4ad30e9f954ecf --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-10-29-10-37-39.gh-issue-126072.XLKlxv.rst @@ -0,0 +1,3 @@ +Add a new attribute in :attr:`~codeobject.co_flags` to indicate whether the +first item in :attr:`~codeobject.co_consts` is the docstring. If a code +object has no docstring, ``None`` will **NOT** be inserted. diff --git a/Objects/funcobject.c b/Objects/funcobject.c index e72a7d98c0a79e..1f2387f68440aa 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -162,7 +162,8 @@ PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname PyObject *consts = code_obj->co_consts; assert(PyTuple_Check(consts)); PyObject *doc; - if (PyTuple_Size(consts) >= 1) { + if (code_obj->co_flags & CO_HAS_DOCSTRING) { + assert(PyTuple_Size(consts) >= 1); doc = PyTuple_GetItem(consts, 0); if (!PyUnicode_Check(doc)) { doc = Py_None; diff --git a/Python/codegen.c b/Python/codegen.c index 976c94234d6055..d79aee4859e51b 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -1243,10 +1243,10 @@ codegen_function_body(compiler *c, stmt_ty s, int is_async, Py_ssize_t funcflags _PyCompile_ExitScope(c); return ERROR; } + Py_ssize_t idx = _PyCompile_AddConst(c, docstring); + Py_DECREF(docstring); + RETURN_IF_ERROR_IN_SCOPE(c, idx < 0 ? ERROR : SUCCESS); } - Py_ssize_t idx = _PyCompile_AddConst(c, docstring ? docstring : Py_None); - Py_XDECREF(docstring); - RETURN_IF_ERROR_IN_SCOPE(c, idx < 0 ? ERROR : SUCCESS); NEW_JUMP_TARGET_LABEL(c, start); USE_LABEL(c, start); diff --git a/Python/compile.c b/Python/compile.c index d463fcde204a05..4dcb9a1b5acdb3 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1285,6 +1285,8 @@ compute_code_flags(compiler *c) flags |= CO_VARARGS; if (ste->ste_varkeywords) flags |= CO_VARKEYWORDS; + if (ste->ste_has_docstring) + flags |= CO_HAS_DOCSTRING; } if (ste->ste_coroutine && !ste->ste_generator) { diff --git a/Python/symtable.c b/Python/symtable.c index 709918b27afcc8..32d715197c541b 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -136,6 +136,8 @@ ste_new(struct symtable *st, identifier name, _Py_block_ty block, ste->ste_needs_classdict = 0; ste->ste_annotation_block = NULL; + ste->ste_has_docstring = 0; + ste->ste_symbols = PyDict_New(); ste->ste_varnames = PyList_New(0); ste->ste_children = PyList_New(0); @@ -1841,6 +1843,10 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) return 0; } + if (_PyAST_GetDocString(s->v.FunctionDef.body)) { + new_ste->ste_has_docstring = 1; + } + if (!symtable_visit_annotations(st, s, s->v.FunctionDef.args, s->v.FunctionDef.returns, new_ste)) { Py_DECREF(new_ste); @@ -2168,6 +2174,10 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) return 0; } + if (_PyAST_GetDocString(s->v.AsyncFunctionDef.body)) { + new_ste->ste_has_docstring = 1; + } + if (!symtable_visit_annotations(st, s, s->v.AsyncFunctionDef.args, s->v.AsyncFunctionDef.returns, new_ste)) { Py_DECREF(new_ste);