Skip to content

Commit

Permalink
gh-126072: do not add None to co_consts if there is no docstring (G…
Browse files Browse the repository at this point in the history
  • Loading branch information
xuantengh authored Oct 30, 2024
1 parent 2ab377a commit 35df4eb
Show file tree
Hide file tree
Showing 16 changed files with 148 additions and 58 deletions.
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

0 comments on commit 35df4eb

Please sign in to comment.