Skip to content

Commit ce5b0e9

Browse files
bpo-32226: Make __class_getitem__ an automatic class method. (#5098)
1 parent 87be28f commit ce5b0e9

File tree

5 files changed

+31
-12
lines changed

5 files changed

+31
-12
lines changed

Lib/test/test_genericclass.py

+11-2
Original file line numberDiff line numberDiff line change
@@ -183,12 +183,21 @@ def __class_getitem__(cls, item):
183183
self.assertEqual(D[int], 'D[int]')
184184
self.assertEqual(D[D], 'D[D]')
185185

186+
def test_class_getitem_classmethod(self):
187+
class C:
188+
@classmethod
189+
def __class_getitem__(cls, item):
190+
return f'{cls.__name__}[{item.__name__}]'
191+
class D(C): ...
192+
self.assertEqual(D[int], 'D[int]')
193+
self.assertEqual(D[D], 'D[D]')
194+
186195
def test_class_getitem_patched(self):
187196
class C:
188197
def __init_subclass__(cls):
189198
def __class_getitem__(cls, item):
190199
return f'{cls.__name__}[{item.__name__}]'
191-
cls.__class_getitem__ = __class_getitem__
200+
cls.__class_getitem__ = classmethod(__class_getitem__)
192201
class D(C): ...
193202
self.assertEqual(D[int], 'D[int]')
194203
self.assertEqual(D[D], 'D[D]')
@@ -254,7 +263,7 @@ class CAPITest(unittest.TestCase):
254263

255264
def test_c_class(self):
256265
from _testcapi import Generic, GenericAlias
257-
self.assertIsInstance(Generic.__class_getitem__(Generic, int), GenericAlias)
266+
self.assertIsInstance(Generic.__class_getitem__(int), GenericAlias)
258267

259268
IntGeneric = Generic[int]
260269
self.assertIs(type(IntGeneric), GenericAlias)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
``__class_getitem__`` is now an automatic class method.

Modules/_testcapimodule.c

+2-6
Original file line numberDiff line numberDiff line change
@@ -5104,17 +5104,13 @@ typedef struct {
51045104
} PyGenericObject;
51055105

51065106
static PyObject *
5107-
generic_class_getitem(PyObject *self, PyObject *args)
5107+
generic_class_getitem(PyObject *type, PyObject *item)
51085108
{
5109-
PyObject *type, *item;
5110-
if (!PyArg_UnpackTuple(args, "__class_getitem__", 2, 2, &type, &item)) {
5111-
return NULL;
5112-
}
51135109
return generic_alias_new(item);
51145110
}
51155111

51165112
static PyMethodDef generic_methods[] = {
5117-
{"__class_getitem__", generic_class_getitem, METH_VARARGS|METH_STATIC, NULL},
5113+
{"__class_getitem__", generic_class_getitem, METH_O|METH_CLASS, NULL},
51185114
{NULL} /* sentinel */
51195115
};
51205116

Objects/abstract.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -169,11 +169,11 @@ PyObject_GetItem(PyObject *o, PyObject *key)
169169
}
170170

171171
if (PyType_Check(o)) {
172-
PyObject *meth, *result, *stack[2] = {o, key};
172+
PyObject *meth, *result, *stack[1] = {key};
173173
_Py_IDENTIFIER(__class_getitem__);
174174
meth = _PyObject_GetAttrId(o, &PyId___class_getitem__);
175175
if (meth) {
176-
result = _PyObject_FastCall(meth, stack, 2);
176+
result = _PyObject_FastCall(meth, stack, 1);
177177
Py_DECREF(meth);
178178
return result;
179179
}

Objects/typeobject.c

+15-2
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ static size_t method_cache_collisions = 0;
5555
/* alphabetical order */
5656
_Py_IDENTIFIER(__abstractmethods__);
5757
_Py_IDENTIFIER(__class__);
58+
_Py_IDENTIFIER(__class_getitem__);
5859
_Py_IDENTIFIER(__delitem__);
5960
_Py_IDENTIFIER(__dict__);
6061
_Py_IDENTIFIER(__doc__);
@@ -2694,8 +2695,8 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
26942695
Py_DECREF(tmp);
26952696
}
26962697

2697-
/* Special-case __init_subclass__: if it's a plain function,
2698-
make it a classmethod */
2698+
/* Special-case __init_subclass__ and __class_getitem__:
2699+
if they are plain functions, make them classmethods */
26992700
tmp = _PyDict_GetItemId(dict, &PyId___init_subclass__);
27002701
if (tmp != NULL && PyFunction_Check(tmp)) {
27012702
tmp = PyClassMethod_New(tmp);
@@ -2708,6 +2709,18 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
27082709
Py_DECREF(tmp);
27092710
}
27102711

2712+
tmp = _PyDict_GetItemId(dict, &PyId___class_getitem__);
2713+
if (tmp != NULL && PyFunction_Check(tmp)) {
2714+
tmp = PyClassMethod_New(tmp);
2715+
if (tmp == NULL)
2716+
goto error;
2717+
if (_PyDict_SetItemId(dict, &PyId___class_getitem__, tmp) < 0) {
2718+
Py_DECREF(tmp);
2719+
goto error;
2720+
}
2721+
Py_DECREF(tmp);
2722+
}
2723+
27112724
/* Add descriptors for custom slots from __slots__, or for __dict__ */
27122725
mp = PyHeapType_GET_MEMBERS(et);
27132726
slotoffset = base->tp_basicsize;

0 commit comments

Comments
 (0)