Skip to content

gh-127274: Defer nested methods #128012

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 5 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions Doc/library/inspect.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1708,6 +1708,13 @@ which is a bitmap of the following flags:

.. versionadded:: 3.14

.. data:: CO_METHOD

The flag is set when the code object is a function defined in class
scope.

.. 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
3 changes: 3 additions & 0 deletions Include/cpython/code.h
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ struct PyCodeObject _PyCode_DEF(1);
*/
#define CO_HAS_DOCSTRING 0x4000000

/* A function defined in class scope */
#define CO_METHOD 0x8000000

/* 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 @@ -124,6 +124,7 @@ typedef struct _symtable_entry {
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 */
unsigned ste_method : 1; /* true if block is a function block defined in class scope */
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
1 change: 1 addition & 0 deletions Lib/dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ def distb(tb=None, *, file=None, show_caches=False, adaptive=False, show_offsets
256: "ITERABLE_COROUTINE",
512: "ASYNC_GENERATOR",
0x4000000: "HAS_DOCSTRING",
0x8000000: "METHOD",
}

def pretty_flags(flags):
Expand Down
1 change: 1 addition & 0 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"CO_VARARGS",
"CO_VARKEYWORDS",
"CO_HAS_DOCSTRING",
"CO_METHOD",
"ClassFoundException",
"ClosureVars",
"EndOfBlock",
Expand Down
9 changes: 3 additions & 6 deletions Lib/test/test_monitoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -850,12 +850,6 @@ def __init__(self, events):
def __call__(self, code, offset, val):
self.events.append(("return", code.co_name, val))

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


class ExceptionMonitoringTest(CheckEvents):

Expand Down Expand Up @@ -1054,6 +1048,9 @@ def func():

@requires_specialization_ft
def test_no_unwind_for_shim_frame(self):
class ValueErrorRaiser:
def __init__(self):
raise ValueError()

def f():
try:
Expand Down
11 changes: 4 additions & 7 deletions Lib/test/test_opcache.py
Original file line number Diff line number Diff line change
Expand Up @@ -493,13 +493,6 @@ def f():
self.assertFalse(f())


# gh-127274: CALL_ALLOC_AND_ENTER_INIT will only cache __init__ methods that
# are deferred. We only defer functions defined at the top-level.
class MyClass:
def __init__(self):
pass


class InitTakesArg:
def __init__(self, arg):
self.arg = arg
Expand Down Expand Up @@ -536,6 +529,10 @@ def f(x, y):
@disabling_optimizer
@requires_specialization_ft
def test_assign_init_code(self):
class MyClass:
def __init__(self):
pass

def instantiate():
return MyClass()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add a new flag, ``CO_METHOD``, to :attr:`~codeobject.co_flags` that
indicates whether the code object belongs to a function defined in class
scope.
6 changes: 5 additions & 1 deletion Objects/funcobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -210,10 +210,14 @@ PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname
op->func_typeparams = NULL;
op->vectorcall = _PyFunction_Vectorcall;
op->func_version = FUNC_VERSION_UNSET;
if ((code_obj->co_flags & CO_NESTED) == 0) {
if (((code_obj->co_flags & CO_NESTED) == 0) ||
(code_obj->co_flags & CO_METHOD)) {
// Use deferred reference counting for top-level functions, but not
// nested functions because they are more likely to capture variables,
// which makes prompt deallocation more important.
//
// Nested methods (functions defined in class scope) are also deferred,
// since they will likely be cleaned up by GC anyway.
_PyObject_SetDeferredRefcount((PyObject *)op);
}
_PyObject_GC_TRACK(op);
Expand Down
2 changes: 2 additions & 0 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -1289,6 +1289,8 @@ compute_code_flags(compiler *c)
flags |= CO_VARKEYWORDS;
if (ste->ste_has_docstring)
flags |= CO_HAS_DOCSTRING;
if (ste->ste_method)
flags |= CO_METHOD;
}

if (ste->ste_coroutine && !ste->ste_generator) {
Expand Down
7 changes: 7 additions & 0 deletions Python/symtable.c
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,13 @@ ste_new(struct symtable *st, identifier name, _Py_block_ty block,

ste->ste_has_docstring = 0;

ste->ste_method = 0;
if (st->st_cur != NULL &&
st->st_cur->ste_type == ClassBlock &&
block == FunctionBlock) {
ste->ste_method = 1;
}

ste->ste_symbols = PyDict_New();
ste->ste_varnames = PyList_New(0);
ste->ste_children = PyList_New(0);
Expand Down
Loading