Skip to content

gh-126072: do not add None to co_consts if no docstring #126101

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 26 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Doc/library/inspect.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions Doc/reference/datamodel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
5 changes: 5 additions & 0 deletions Include/cpython/code.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_symtable.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
21 changes: 11 additions & 10 deletions Lib/dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 2 additions & 0 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"CO_OPTIMIZED",
"CO_VARARGS",
"CO_VARKEYWORDS",
"CO_HAS_DOCSTRING",
"ClassFoundException",
"ClosureVars",
"EndOfBlock",
Expand Down Expand Up @@ -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)
Expand Down
59 changes: 57 additions & 2 deletions Lib/test/test_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
freevars: ()
nlocals: 2
flags: 3
consts: ('None', '<code object g>')
consts: ('<code object g>',)

>>> dump(f(4).__code__)
name: g
Expand Down Expand Up @@ -86,7 +86,7 @@
cellvars: ()
freevars: ()
nlocals: 0
flags: 3
flags: 67108867
consts: ("'doc string'", 'None')

>>> def keywordonly_args(a,b,*,k1):
Expand Down Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions Lib/test/test_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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
Expand Down Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_compiler_assemble.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
Loading
Loading