Skip to content

Commit 1b28187

Browse files
authored
bpo-44313: generate LOAD_ATTR/CALL_FUNCTION for top-level imported objects (GH-26677)
1 parent 66c53b4 commit 1b28187

File tree

9 files changed

+279
-214
lines changed

9 files changed

+279
-214
lines changed

Include/internal/pycore_symtable.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ extern PyTypeObject PySTEntry_Type;
7272

7373
#define PySTEntry_Check(op) Py_IS_TYPE(op, &PySTEntry_Type)
7474

75+
extern long _PyST_GetSymbol(PySTEntryObject *, PyObject *);
7576
extern int _PyST_GetScope(PySTEntryObject *, PyObject *);
7677

7778
extern struct symtable* _PySymtable_Build(

Lib/importlib/_bootstrap_external.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ def _write_atomic(path, data, mode=0o666):
360360
# Python 3.11a1 3455 (add MAKE_CELL bpo-43693)
361361
# Python 3.11a1 3456 (interleave cell args bpo-43693)
362362
# Python 3.11a1 3457 (Change localsplus to a bytes object bpo-43693)
363+
# Python 3.11a1 3458 (imported objects now don't use LOAD_METHOD/CALL_METHOD)
363364

364365
#
365366
# MAGIC must change whenever the bytecode emitted by the compiler may no

Lib/test/test_compile.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import _ast
77
import tempfile
88
import types
9+
import textwrap
910
from test import support
1011
from test.support import script_helper
1112
from test.support.os_helper import FakePath
@@ -791,6 +792,41 @@ def or_false(x):
791792
self.assertIn('LOAD_', opcodes[0].opname)
792793
self.assertEqual('RETURN_VALUE', opcodes[1].opname)
793794

795+
def test_imported_load_method(self):
796+
sources = [
797+
"""\
798+
import os
799+
def foo():
800+
return os.uname()
801+
""",
802+
"""\
803+
import os as operating_system
804+
def foo():
805+
return operating_system.uname()
806+
""",
807+
"""\
808+
from os import path
809+
def foo(x):
810+
return path.join(x)
811+
""",
812+
"""\
813+
from os import path as os_path
814+
def foo(x):
815+
return os_path.join(x)
816+
"""
817+
]
818+
for source in sources:
819+
namespace = {}
820+
exec(textwrap.dedent(source), namespace)
821+
func = namespace['foo']
822+
with self.subTest(func=func.__name__):
823+
opcodes = list(dis.get_instructions(func))
824+
instructions = [opcode.opname for opcode in opcodes]
825+
self.assertNotIn('LOAD_METHOD', instructions)
826+
self.assertNotIn('CALL_METHOD', instructions)
827+
self.assertIn('LOAD_ATTR', instructions)
828+
self.assertIn('CALL_FUNCTION', instructions)
829+
794830
def test_lineno_procedure_call(self):
795831
def call():
796832
(
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Directly imported objects and modules (through import and from import
2+
statements) don't generate ``LOAD_METHOD``/``CALL_METHOD`` for directly
3+
accessed objects on their namespace. They now use the regular
4+
``LOAD_ATTR``/``CALL_FUNCTION``.

Programs/test_frozenmain.h

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/compile.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4278,6 +4278,23 @@ check_index(struct compiler *c, expr_ty e, expr_ty s)
42784278
}
42794279
}
42804280

4281+
static int
4282+
is_import_originated(struct compiler *c, expr_ty e)
4283+
{
4284+
/* Check whether the global scope has an import named
4285+
e, if it is a Name object. For not traversing all the
4286+
scope stack every time this function is called, it will
4287+
only check the global scope to determine whether something
4288+
is imported or not. */
4289+
4290+
if (e->kind != Name_kind) {
4291+
return 0;
4292+
}
4293+
4294+
long flags = _PyST_GetSymbol(c->c_st->st_top, e->v.Name.id);
4295+
return flags & DEF_IMPORT;
4296+
}
4297+
42814298
// Return 1 if the method call was optimized, -1 if not, and 0 on error.
42824299
static int
42834300
maybe_optimize_method_call(struct compiler *c, expr_ty e)
@@ -4291,6 +4308,12 @@ maybe_optimize_method_call(struct compiler *c, expr_ty e)
42914308
if (meth->kind != Attribute_kind || meth->v.Attribute.ctx != Load) {
42924309
return -1;
42934310
}
4311+
4312+
/* Check that the base object is not something that is imported */
4313+
if (is_import_originated(c, meth->v.Attribute.value)) {
4314+
return -1;
4315+
}
4316+
42944317
/* Check that there aren't too many arguments */
42954318
argsl = asdl_seq_LEN(args);
42964319
kwdsl = asdl_seq_LEN(kwds);

0 commit comments

Comments
 (0)