Skip to content

Commit 255762c

Browse files
authored
gh-127274: Defer nested methods (#128012)
Methods (functions defined in class scope) are likely to be cleaned up by the GC anyway. Add a new code flag, `CO_METHOD`, that is set for functions defined in a class scope. Use that when deciding to defer functions.
1 parent e163e8d commit 255762c

File tree

11 files changed

+37
-14
lines changed

11 files changed

+37
-14
lines changed

Doc/library/inspect.rst

+7
Original file line numberDiff line numberDiff line change
@@ -1708,6 +1708,13 @@ which is a bitmap of the following flags:
17081708

17091709
.. versionadded:: 3.14
17101710

1711+
.. data:: CO_METHOD
1712+
1713+
The flag is set when the code object is a function defined in class
1714+
scope.
1715+
1716+
.. versionadded:: 3.14
1717+
17111718
.. note::
17121719
The flags are specific to CPython, and may not be defined in other
17131720
Python implementations. Furthermore, the flags are an implementation

Include/cpython/code.h

+3
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,9 @@ struct PyCodeObject _PyCode_DEF(1);
199199
*/
200200
#define CO_HAS_DOCSTRING 0x4000000
201201

202+
/* A function defined in class scope */
203+
#define CO_METHOD 0x8000000
204+
202205
/* This should be defined if a future statement modifies the syntax.
203206
For example, when a keyword is added.
204207
*/

Include/internal/pycore_symtable.h

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

Lib/dis.py

+1
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ def distb(tb=None, *, file=None, show_caches=False, adaptive=False, show_offsets
162162
256: "ITERABLE_COROUTINE",
163163
512: "ASYNC_GENERATOR",
164164
0x4000000: "HAS_DOCSTRING",
165+
0x8000000: "METHOD",
165166
}
166167

167168
def pretty_flags(flags):

Lib/inspect.py

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"CO_VARARGS",
5858
"CO_VARKEYWORDS",
5959
"CO_HAS_DOCSTRING",
60+
"CO_METHOD",
6061
"ClassFoundException",
6162
"ClosureVars",
6263
"EndOfBlock",

Lib/test/test_monitoring.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -850,12 +850,6 @@ def __init__(self, events):
850850
def __call__(self, code, offset, val):
851851
self.events.append(("return", code.co_name, val))
852852

853-
# gh-127274: CALL_ALLOC_AND_ENTER_INIT will only cache __init__ methods that
854-
# are deferred. We only defer functions defined at the top-level.
855-
class ValueErrorRaiser:
856-
def __init__(self):
857-
raise ValueError()
858-
859853

860854
class ExceptionMonitoringTest(CheckEvents):
861855

@@ -1054,6 +1048,9 @@ def func():
10541048

10551049
@requires_specialization_ft
10561050
def test_no_unwind_for_shim_frame(self):
1051+
class ValueErrorRaiser:
1052+
def __init__(self):
1053+
raise ValueError()
10571054

10581055
def f():
10591056
try:

Lib/test/test_opcache.py

+4-7
Original file line numberDiff line numberDiff line change
@@ -493,13 +493,6 @@ def f():
493493
self.assertFalse(f())
494494

495495

496-
# gh-127274: CALL_ALLOC_AND_ENTER_INIT will only cache __init__ methods that
497-
# are deferred. We only defer functions defined at the top-level.
498-
class MyClass:
499-
def __init__(self):
500-
pass
501-
502-
503496
class InitTakesArg:
504497
def __init__(self, arg):
505498
self.arg = arg
@@ -536,6 +529,10 @@ def f(x, y):
536529
@disabling_optimizer
537530
@requires_specialization_ft
538531
def test_assign_init_code(self):
532+
class MyClass:
533+
def __init__(self):
534+
pass
535+
539536
def instantiate():
540537
return MyClass()
541538

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add a new flag, ``CO_METHOD``, to :attr:`~codeobject.co_flags` that
2+
indicates whether the code object belongs to a function defined in class
3+
scope.

Objects/funcobject.c

+5-1
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,14 @@ PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname
210210
op->func_typeparams = NULL;
211211
op->vectorcall = _PyFunction_Vectorcall;
212212
op->func_version = FUNC_VERSION_UNSET;
213-
if ((code_obj->co_flags & CO_NESTED) == 0) {
213+
if (((code_obj->co_flags & CO_NESTED) == 0) ||
214+
(code_obj->co_flags & CO_METHOD)) {
214215
// Use deferred reference counting for top-level functions, but not
215216
// nested functions because they are more likely to capture variables,
216217
// which makes prompt deallocation more important.
218+
//
219+
// Nested methods (functions defined in class scope) are also deferred,
220+
// since they will likely be cleaned up by GC anyway.
217221
_PyObject_SetDeferredRefcount((PyObject *)op);
218222
}
219223
_PyObject_GC_TRACK(op);

Python/compile.c

+2
Original file line numberDiff line numberDiff line change
@@ -1289,6 +1289,8 @@ compute_code_flags(compiler *c)
12891289
flags |= CO_VARKEYWORDS;
12901290
if (ste->ste_has_docstring)
12911291
flags |= CO_HAS_DOCSTRING;
1292+
if (ste->ste_method)
1293+
flags |= CO_METHOD;
12921294
}
12931295

12941296
if (ste->ste_coroutine && !ste->ste_generator) {

Python/symtable.c

+7
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,13 @@ ste_new(struct symtable *st, identifier name, _Py_block_ty block,
138138

139139
ste->ste_has_docstring = 0;
140140

141+
ste->ste_method = 0;
142+
if (st->st_cur != NULL &&
143+
st->st_cur->ste_type == ClassBlock &&
144+
block == FunctionBlock) {
145+
ste->ste_method = 1;
146+
}
147+
141148
ste->ste_symbols = PyDict_New();
142149
ste->ste_varnames = PyList_New(0);
143150
ste->ste_children = PyList_New(0);

0 commit comments

Comments
 (0)