Skip to content

Commit 0a0ebe2

Browse files
committed
pythongh-87729: add instruction for faster zero-arg super()
1 parent fb38c1b commit 0a0ebe2

15 files changed

+652
-398
lines changed

Include/internal/pycore_opcode.h

+7-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_typeobject.h

+3
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ _Py_type_getattro(PyTypeObject *type, PyObject *name);
9898
PyObject *_Py_slot_tp_getattro(PyObject *self, PyObject *name);
9999
PyObject *_Py_slot_tp_getattr_hook(PyObject *self, PyObject *name);
100100

101+
PyObject *
102+
_PySuper_Lookup(PyTypeObject *su_type, PyObject *su_obj, PyObject *name, int *meth_found);
103+
101104
#ifdef __cplusplus
102105
}
103106
#endif

Include/opcode.h

+11-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/dis.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
FOR_ITER = opmap['FOR_ITER']
4242
SEND = opmap['SEND']
4343
LOAD_ATTR = opmap['LOAD_ATTR']
44+
LOAD_ZERO_SUPER_ATTR = opmap['LOAD_ZERO_SUPER_ATTR']
4445

4546
CACHE = opmap["CACHE"]
4647

@@ -471,7 +472,7 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
471472
argval, argrepr = _get_name_info(arg//2, get_name)
472473
if (arg & 1) and argrepr:
473474
argrepr = "NULL + " + argrepr
474-
elif deop == LOAD_ATTR:
475+
elif deop == LOAD_ATTR or deop == LOAD_ZERO_SUPER_ATTR:
475476
argval, argrepr = _get_name_info(arg//2, get_name)
476477
if (arg & 1) and argrepr:
477478
argrepr = "NULL|self + " + argrepr

Lib/importlib/_bootstrap_external.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,8 @@ def _write_atomic(path, data, mode=0o666):
439439
# Python 3.12a7 3523 (Convert COMPARE_AND_BRANCH back to COMPARE_OP)
440440
# Python 3.12a7 3524 (Shrink the BINARY_SUBSCR caches)
441441
# Python 3.12b1 3525 (Shrink the CALL caches)
442-
# Python 3.12a7 3526 (Add instrumentation support)
442+
# Python 3.12b1 3526 (Add instrumentation support)
443+
# Python 3.12b1 3527 (Optimize super() calls)
443444

444445
# Python 3.13 will start with 3550
445446

@@ -456,7 +457,7 @@ def _write_atomic(path, data, mode=0o666):
456457
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
457458
# in PC/launcher.c must also be updated.
458459

459-
MAGIC_NUMBER = (3526).to_bytes(2, 'little') + b'\r\n'
460+
MAGIC_NUMBER = (3527).to_bytes(2, 'little') + b'\r\n'
460461

461462
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c
462463

Lib/opcode.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ def pseudo_op(name, op, real_ops):
196196
def_op('DELETE_DEREF', 139)
197197
hasfree.append(139)
198198
jrel_op('JUMP_BACKWARD', 140) # Number of words to skip (backwards)
199-
199+
name_op('LOAD_ZERO_SUPER_ATTR', 141)
200200
def_op('CALL_FUNCTION_EX', 142) # Flags
201201

202202
def_op('EXTENDED_ARG', 144)
@@ -264,6 +264,7 @@ def pseudo_op(name, op, real_ops):
264264
pseudo_op('JUMP_NO_INTERRUPT', 261, ['JUMP_FORWARD', 'JUMP_BACKWARD_NO_INTERRUPT'])
265265

266266
pseudo_op('LOAD_METHOD', 262, ['LOAD_ATTR'])
267+
pseudo_op('LOAD_ZERO_SUPER_METHOD', 263, ['LOAD_ZERO_SUPER_ATTR'])
267268

268269
MAX_PSEUDO_OPCODE = MIN_PSEUDO_OPCODE + len(_pseudo_ops) - 1
269270

Lib/test/shadowed_super.py

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class super:
2+
msg = "truly super"
3+
4+
5+
class C:
6+
def method(self):
7+
return super().msg

Lib/test/test_super.py

+48-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""Unit tests for zero-argument super() & related machinery."""
22

33
import unittest
4+
from unittest.mock import patch
5+
from test import shadowed_super
46

57

68
class A:
@@ -283,17 +285,28 @@ def f(self):
283285
def test_obscure_super_errors(self):
284286
def f():
285287
super()
286-
self.assertRaises(RuntimeError, f)
288+
with self.assertRaisesRegex(RuntimeError, r"no arguments"):
289+
f()
290+
291+
class C:
292+
def f():
293+
super()
294+
with self.assertRaisesRegex(RuntimeError, r"no arguments"):
295+
C.f()
296+
287297
def f(x):
288298
del x
289299
super()
290-
self.assertRaises(RuntimeError, f, None)
300+
with self.assertRaisesRegex(RuntimeError, r"arg\[0\] deleted"):
301+
f(None)
302+
291303
class X:
292304
def f(x):
293305
nonlocal __class__
294306
del __class__
295307
super()
296-
self.assertRaises(RuntimeError, X().f)
308+
with self.assertRaisesRegex(RuntimeError, r"empty __class__ cell"):
309+
X().f()
297310

298311
def test_cell_as_self(self):
299312
class X:
@@ -325,6 +338,38 @@ def test_super_argtype(self):
325338
with self.assertRaisesRegex(TypeError, "argument 1 must be a type"):
326339
super(1, int)
327340

341+
def test_shadowed_global(self):
342+
self.assertEqual(shadowed_super.C().method(), "truly super")
343+
344+
def test_shadowed_local(self):
345+
class super:
346+
msg = "quite super"
347+
348+
class C:
349+
def method(self):
350+
return super().msg
351+
352+
self.assertEqual(C().method(), "quite super")
353+
354+
def test_shadowed_dynamic(self):
355+
class MySuper:
356+
msg = "super super"
357+
358+
class C:
359+
def method(self):
360+
return super().msg
361+
362+
with patch("test.test_super.super", MySuper) as m:
363+
self.assertEqual(C().method(), "super super")
364+
365+
def test_attribute_error(self):
366+
class C:
367+
def method(self):
368+
return super().msg
369+
370+
with self.assertRaisesRegex(AttributeError, "'super' object has no attribute 'msg'"):
371+
C().method()
372+
328373

329374
if __name__ == "__main__":
330375
unittest.main()

Objects/typeobject.c

+51-17
Original file line numberDiff line numberDiff line change
@@ -9358,14 +9358,15 @@ super_repr(PyObject *self)
93589358
}
93599359

93609360
static PyObject *
9361-
super_getattro(PyObject *self, PyObject *name)
9361+
do_super_lookup(superobject *su, PyTypeObject *su_type, PyObject *su_obj,
9362+
PyTypeObject *su_obj_type, PyObject *name, int *meth_found)
93629363
{
9363-
superobject *su = (superobject *)self;
93649364
PyTypeObject *starttype;
9365-
PyObject *mro;
9365+
PyObject *mro, *res;
93669366
Py_ssize_t i, n;
9367+
int temp_su = 0;
93679368

9368-
starttype = su->obj_type;
9369+
starttype = su_obj_type;
93699370
if (starttype == NULL)
93709371
goto skip;
93719372

@@ -9385,7 +9386,7 @@ super_getattro(PyObject *self, PyObject *name)
93859386

93869387
/* No need to check the last one: it's gonna be skipped anyway. */
93879388
for (i = 0; i+1 < n; i++) {
9388-
if ((PyObject *)(su->type) == PyTuple_GET_ITEM(mro, i))
9389+
if ((PyObject *)(su_type) == PyTuple_GET_ITEM(mro, i))
93899390
break;
93909391
}
93919392
i++; /* skip su->type (if any) */
@@ -9400,19 +9401,22 @@ super_getattro(PyObject *self, PyObject *name)
94009401
PyObject *dict = _PyType_CAST(obj)->tp_dict;
94019402
assert(dict != NULL && PyDict_Check(dict));
94029403

9403-
PyObject *res = PyDict_GetItemWithError(dict, name);
9404+
res = PyDict_GetItemWithError(dict, name);
94049405
if (res != NULL) {
94059406
Py_INCREF(res);
9406-
9407-
descrgetfunc f = Py_TYPE(res)->tp_descr_get;
9408-
if (f != NULL) {
9409-
PyObject *res2;
9410-
res2 = f(res,
9411-
/* Only pass 'obj' param if this is instance-mode super
9412-
(See SF ID #743627) */
9413-
(su->obj == (PyObject *)starttype) ? NULL : su->obj,
9414-
(PyObject *)starttype);
9415-
Py_SETREF(res, res2);
9407+
if (meth_found && _PyType_HasFeature(Py_TYPE(res), Py_TPFLAGS_METHOD_DESCRIPTOR)) {
9408+
*meth_found = 1;
9409+
} else {
9410+
descrgetfunc f = Py_TYPE(res)->tp_descr_get;
9411+
if (f != NULL) {
9412+
PyObject *res2;
9413+
res2 = f(res,
9414+
/* Only pass 'obj' param if this is instance-mode super
9415+
(See SF ID #743627) */
9416+
(su_obj == (PyObject *)starttype) ? NULL : su_obj,
9417+
(PyObject *)starttype);
9418+
Py_SETREF(res, res2);
9419+
}
94169420
}
94179421

94189422
Py_DECREF(mro);
@@ -9428,7 +9432,25 @@ super_getattro(PyObject *self, PyObject *name)
94289432
Py_DECREF(mro);
94299433

94309434
skip:
9431-
return PyObject_GenericGetAttr(self, name);
9435+
if (su == NULL) {
9436+
su = PyObject_Vectorcall((PyObject *)&PySuper_Type, NULL, 0, NULL);
9437+
if (su == NULL) {
9438+
return NULL;
9439+
}
9440+
temp_su = 1;
9441+
}
9442+
res = PyObject_GenericGetAttr((PyObject *)su, name);
9443+
if (temp_su) {
9444+
Py_DECREF(su);
9445+
}
9446+
return res;
9447+
}
9448+
9449+
static PyObject *
9450+
super_getattro(PyObject *self, PyObject *name)
9451+
{
9452+
superobject *su = (superobject *)self;
9453+
return do_super_lookup(su, su->type, su->obj, su->obj_type, name, NULL);
94329454
}
94339455

94349456
static PyTypeObject *
@@ -9484,6 +9506,18 @@ supercheck(PyTypeObject *type, PyObject *obj)
94849506
return NULL;
94859507
}
94869508

9509+
PyObject *
9510+
_PySuper_Lookup(PyTypeObject *su_type, PyObject *su_obj, PyObject *name, int *meth_found)
9511+
{
9512+
PyTypeObject *su_obj_type = supercheck(su_type, su_obj);
9513+
if (su_obj_type == NULL) {
9514+
return NULL;
9515+
}
9516+
PyObject *res = do_super_lookup(NULL, su_type, su_obj, su_obj_type, name, meth_found);
9517+
Py_DECREF(su_obj_type);
9518+
return res;
9519+
}
9520+
94879521
static PyObject *
94889522
super_descr_get(PyObject *self, PyObject *obj, PyObject *type)
94899523
{

0 commit comments

Comments
 (0)