]> granicus.if.org Git - python/commitdiff
bpo-32226: Make __class_getitem__ an automatic class method. (#5098)
authorSerhiy Storchaka <storchaka@gmail.com>
Thu, 4 Jan 2018 22:21:41 +0000 (00:21 +0200)
committerGitHub <noreply@github.com>
Thu, 4 Jan 2018 22:21:41 +0000 (00:21 +0200)
Lib/test/test_genericclass.py
Misc/NEWS.d/next/Core and Builtins/2018-01-04-15-06-15.bpo-32226.7cAvRG.rst [new file with mode: 0644]
Modules/_testcapimodule.c
Objects/abstract.c
Objects/typeobject.c

index 2057fc000c0d701aa1058d7faca2649678e6a967..37e755b1167e83529c377975f6a0b889b75efc02 100644 (file)
@@ -183,12 +183,21 @@ class TestClassGetitem(unittest.TestCase):
         self.assertEqual(D[int], 'D[int]')
         self.assertEqual(D[D], 'D[D]')
 
+    def test_class_getitem_classmethod(self):
+        class C:
+            @classmethod
+            def __class_getitem__(cls, item):
+                return f'{cls.__name__}[{item.__name__}]'
+        class D(C): ...
+        self.assertEqual(D[int], 'D[int]')
+        self.assertEqual(D[D], 'D[D]')
+
     def test_class_getitem_patched(self):
         class C:
             def __init_subclass__(cls):
                 def __class_getitem__(cls, item):
                     return f'{cls.__name__}[{item.__name__}]'
-                cls.__class_getitem__ = __class_getitem__
+                cls.__class_getitem__ = classmethod(__class_getitem__)
         class D(C): ...
         self.assertEqual(D[int], 'D[int]')
         self.assertEqual(D[D], 'D[D]')
@@ -254,7 +263,7 @@ class CAPITest(unittest.TestCase):
 
     def test_c_class(self):
         from _testcapi import Generic, GenericAlias
-        self.assertIsInstance(Generic.__class_getitem__(Generic, int), GenericAlias)
+        self.assertIsInstance(Generic.__class_getitem__(int), GenericAlias)
 
         IntGeneric = Generic[int]
         self.assertIs(type(IntGeneric), GenericAlias)
diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-01-04-15-06-15.bpo-32226.7cAvRG.rst b/Misc/NEWS.d/next/Core and Builtins/2018-01-04-15-06-15.bpo-32226.7cAvRG.rst
new file mode 100644 (file)
index 0000000..842775f
--- /dev/null
@@ -0,0 +1 @@
+``__class_getitem__`` is now an automatic class method.
index 47b50640f5d8be2c0ddbed5d6a2549fc71b1ac5b..7b2e2a3e5a494db2b98e569048db6d5b0224c4fe 100644 (file)
@@ -5104,17 +5104,13 @@ typedef struct {
 } PyGenericObject;
 
 static PyObject *
-generic_class_getitem(PyObject *self, PyObject *args)
+generic_class_getitem(PyObject *type, PyObject *item)
 {
-    PyObject *type, *item;
-    if (!PyArg_UnpackTuple(args, "__class_getitem__", 2, 2, &type, &item)) {
-        return NULL;
-    }
     return generic_alias_new(item);
 }
 
 static PyMethodDef generic_methods[] = {
-    {"__class_getitem__", generic_class_getitem, METH_VARARGS|METH_STATIC, NULL},
+    {"__class_getitem__", generic_class_getitem, METH_O|METH_CLASS, NULL},
     {NULL}  /* sentinel */
 };
 
index 0105c5d16961e05639a921421e4ae05bd16093d8..a68253bdb43d0f64b8785256be8abfb16800c083 100644 (file)
@@ -169,11 +169,11 @@ PyObject_GetItem(PyObject *o, PyObject *key)
     }
 
     if (PyType_Check(o)) {
-        PyObject *meth, *result, *stack[2] = {o, key};
+        PyObject *meth, *result, *stack[1] = {key};
         _Py_IDENTIFIER(__class_getitem__);
         meth = _PyObject_GetAttrId(o, &PyId___class_getitem__);
         if (meth) {
-            result = _PyObject_FastCall(meth, stack, 2);
+            result = _PyObject_FastCall(meth, stack, 1);
             Py_DECREF(meth);
             return result;
         }
index e570c4145dfd89e25a395934b6906c393dc41068..0c8f0adfb787b9ff004d6cea458fe8bd8ca4df3a 100644 (file)
@@ -55,6 +55,7 @@ static size_t method_cache_collisions = 0;
 /* alphabetical order */
 _Py_IDENTIFIER(__abstractmethods__);
 _Py_IDENTIFIER(__class__);
+_Py_IDENTIFIER(__class_getitem__);
 _Py_IDENTIFIER(__delitem__);
 _Py_IDENTIFIER(__dict__);
 _Py_IDENTIFIER(__doc__);
@@ -2694,8 +2695,8 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
         Py_DECREF(tmp);
     }
 
-    /* Special-case __init_subclass__: if it's a plain function,
-       make it a classmethod */
+    /* Special-case __init_subclass__ and __class_getitem__:
+       if they are plain functions, make them classmethods */
     tmp = _PyDict_GetItemId(dict, &PyId___init_subclass__);
     if (tmp != NULL && PyFunction_Check(tmp)) {
         tmp = PyClassMethod_New(tmp);
@@ -2708,6 +2709,18 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
         Py_DECREF(tmp);
     }
 
+    tmp = _PyDict_GetItemId(dict, &PyId___class_getitem__);
+    if (tmp != NULL && PyFunction_Check(tmp)) {
+        tmp = PyClassMethod_New(tmp);
+        if (tmp == NULL)
+            goto error;
+        if (_PyDict_SetItemId(dict, &PyId___class_getitem__, tmp) < 0) {
+            Py_DECREF(tmp);
+            goto error;
+        }
+        Py_DECREF(tmp);
+    }
+
     /* Add descriptors for custom slots from __slots__, or for __dict__ */
     mp = PyHeapType_GET_MEMBERS(et);
     slotoffset = base->tp_basicsize;