Skip to content

Commit 35df4eb

Browse files
authored
gh-126072: do not add None to co_consts if there is no docstring (GH-126101)
1 parent 2ab377a commit 35df4eb

File tree

16 files changed

+148
-58
lines changed

16 files changed

+148
-58
lines changed

Doc/library/inspect.rst

+8
Original file line numberDiff line numberDiff line change
@@ -1700,6 +1700,14 @@ which is a bitmap of the following flags:
17001700

17011701
.. versionadded:: 3.6
17021702

1703+
.. data:: CO_HAS_DOCSTRING
1704+
1705+
The flag is set when there is a docstring for the code object in
1706+
the source code. If set, it will be the first item in
1707+
:attr:`~codeobject.co_consts`.
1708+
1709+
.. versionadded:: 3.14
1710+
17031711
.. note::
17041712
The flags are specific to CPython, and may not be defined in other
17051713
Python implementations. Furthermore, the flags are an implementation

Doc/reference/datamodel.rst

+3-3
Original file line numberDiff line numberDiff line change
@@ -1536,9 +1536,9 @@ Other bits in :attr:`~codeobject.co_flags` are reserved for internal use.
15361536

15371537
.. index:: single: documentation string
15381538

1539-
If a code object represents a function, the first item in
1540-
:attr:`~codeobject.co_consts` is
1541-
the documentation string of the function, or ``None`` if undefined.
1539+
If a code object represents a function and has a docstring,
1540+
the first item in :attr:`~codeobject.co_consts` is
1541+
the docstring of the function.
15421542

15431543
Methods on code objects
15441544
~~~~~~~~~~~~~~~~~~~~~~~

Include/cpython/code.h

+5
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,11 @@ struct PyCodeObject _PyCode_DEF(1);
174174

175175
#define CO_NO_MONITORING_EVENTS 0x2000000
176176

177+
/* Whether the code object has a docstring,
178+
If so, it will be the first item in co_consts
179+
*/
180+
#define CO_HAS_DOCSTRING 0x4000000
181+
177182
/* This should be defined if a future statement modifies the syntax.
178183
For example, when a keyword is added.
179184
*/

Include/internal/pycore_symtable.h

+1
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ typedef struct _symtable_entry {
123123
unsigned ste_comp_iter_target : 1; /* true if visiting comprehension target */
124124
unsigned ste_can_see_class_scope : 1; /* true if this block can see names bound in an
125125
enclosing class scope */
126+
unsigned ste_has_docstring : 1; /* true if docstring present */
126127
int ste_comp_iter_expr; /* non-zero if visiting a comprehension range expression */
127128
_Py_SourceLocation ste_loc; /* source location of block */
128129
struct _symtable_entry *ste_annotation_block; /* symbol table entry for this entry's annotations */

Lib/dis.py

+11-10
Original file line numberDiff line numberDiff line change
@@ -151,16 +151,17 @@ def distb(tb=None, *, file=None, show_caches=False, adaptive=False, show_offsets
151151
# list of CO_* constants. It is also used by pretty_flags to
152152
# turn the co_flags field into a human readable list.
153153
COMPILER_FLAG_NAMES = {
154-
1: "OPTIMIZED",
155-
2: "NEWLOCALS",
156-
4: "VARARGS",
157-
8: "VARKEYWORDS",
158-
16: "NESTED",
159-
32: "GENERATOR",
160-
64: "NOFREE",
161-
128: "COROUTINE",
162-
256: "ITERABLE_COROUTINE",
163-
512: "ASYNC_GENERATOR",
154+
1: "OPTIMIZED",
155+
2: "NEWLOCALS",
156+
4: "VARARGS",
157+
8: "VARKEYWORDS",
158+
16: "NESTED",
159+
32: "GENERATOR",
160+
64: "NOFREE",
161+
128: "COROUTINE",
162+
256: "ITERABLE_COROUTINE",
163+
512: "ASYNC_GENERATOR",
164+
0x4000000: "HAS_DOCSTRING",
164165
}
165166

166167
def pretty_flags(flags):

Lib/inspect.py

+2
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"CO_OPTIMIZED",
5757
"CO_VARARGS",
5858
"CO_VARKEYWORDS",
59+
"CO_HAS_DOCSTRING",
5960
"ClassFoundException",
6061
"ClosureVars",
6162
"EndOfBlock",
@@ -409,6 +410,7 @@ def iscode(object):
409410
co_flags bitmap: 1=optimized | 2=newlocals | 4=*arg | 8=**arg
410411
| 16=nested | 32=generator | 64=nofree | 128=coroutine
411412
| 256=iterable_coroutine | 512=async_generator
413+
| 0x4000000=has_docstring
412414
co_freevars tuple of names of free variables
413415
co_posonlyargcount number of positional only arguments
414416
co_kwonlyargcount number of keyword only arguments (not including ** arg)

Lib/test/test_code.py

+57-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
freevars: ()
1818
nlocals: 2
1919
flags: 3
20-
consts: ('None', '<code object g>')
20+
consts: ('<code object g>',)
2121
2222
>>> dump(f(4).__code__)
2323
name: g
@@ -86,7 +86,7 @@
8686
cellvars: ()
8787
freevars: ()
8888
nlocals: 0
89-
flags: 3
89+
flags: 67108867
9090
consts: ("'doc string'", 'None')
9191
9292
>>> def keywordonly_args(a,b,*,k1):
@@ -123,6 +123,61 @@
123123
flags: 3
124124
consts: ('None',)
125125
126+
>>> def has_docstring(x: str):
127+
... 'This is a one-line doc string'
128+
... x += x
129+
... x += "hello world"
130+
... # co_flags should be 0x4000003 = 67108867
131+
... return x
132+
133+
>>> dump(has_docstring.__code__)
134+
name: has_docstring
135+
argcount: 1
136+
posonlyargcount: 0
137+
kwonlyargcount: 0
138+
names: ()
139+
varnames: ('x',)
140+
cellvars: ()
141+
freevars: ()
142+
nlocals: 1
143+
flags: 67108867
144+
consts: ("'This is a one-line doc string'", "'hello world'")
145+
146+
>>> async def async_func_docstring(x: str, y: str):
147+
... "This is a docstring from async function"
148+
... import asyncio
149+
... await asyncio.sleep(1)
150+
... # co_flags should be 0x4000083 = 67108995
151+
... return x + y
152+
153+
>>> dump(async_func_docstring.__code__)
154+
name: async_func_docstring
155+
argcount: 2
156+
posonlyargcount: 0
157+
kwonlyargcount: 0
158+
names: ('asyncio', 'sleep')
159+
varnames: ('x', 'y', 'asyncio')
160+
cellvars: ()
161+
freevars: ()
162+
nlocals: 3
163+
flags: 67108995
164+
consts: ("'This is a docstring from async function'", 'None')
165+
166+
>>> def no_docstring(x, y, z):
167+
... return x + "hello" + y + z + "world"
168+
169+
>>> dump(no_docstring.__code__)
170+
name: no_docstring
171+
argcount: 3
172+
posonlyargcount: 0
173+
kwonlyargcount: 0
174+
names: ()
175+
varnames: ('x', 'y', 'z')
176+
cellvars: ()
177+
freevars: ()
178+
nlocals: 3
179+
flags: 3
180+
consts: ("'hello'", "'world'")
126181
"""
127182

128183
import copy

Lib/test/test_compile.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -834,7 +834,7 @@ def f():
834834
return "unused"
835835

836836
self.assertEqual(f.__code__.co_consts,
837-
(None, "used"))
837+
(True, "used"))
838838

839839
@support.cpython_only
840840
def test_remove_unused_consts_extended_args(self):
@@ -852,9 +852,9 @@ def test_remove_unused_consts_extended_args(self):
852852
eval(compile(code, "file.py", "exec"), g)
853853
exec(code, g)
854854
f = g['f']
855-
expected = tuple([None, ''] + [f't{i}' for i in range(N)])
855+
expected = tuple([''] + [f't{i}' for i in range(N)])
856856
self.assertEqual(f.__code__.co_consts, expected)
857-
expected = "".join(expected[2:])
857+
expected = "".join(expected[1:])
858858
self.assertEqual(expected, f())
859859

860860
# Stripping unused constants is not a strict requirement for the
@@ -1244,7 +1244,7 @@ def return_genexp():
12441244
y)
12451245
genexp_lines = [0, 4, 2, 0, 4]
12461246

1247-
genexp_code = return_genexp.__code__.co_consts[1]
1247+
genexp_code = return_genexp.__code__.co_consts[0]
12481248
code_lines = self.get_code_lines(genexp_code)
12491249
self.assertEqual(genexp_lines, code_lines)
12501250

Lib/test/test_compiler_assemble.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def inner():
8484
return x
8585
return inner() % 2
8686

87-
inner_code = mod_two.__code__.co_consts[1]
87+
inner_code = mod_two.__code__.co_consts[0]
8888
assert isinstance(inner_code, types.CodeType)
8989

9090
metadata = {

0 commit comments

Comments
 (0)