Index: Objects/funcobject.c =================================================================== --- Objects/funcobject.c (revision 83965) +++ Objects/funcobject.c (working copy) @@ -775,6 +775,80 @@ {NULL} /* Sentinel */ }; +static PyObject * +_get_isabs(PyObject *callable) +{ + PyObject *abs; + int flag; + + if (callable == NULL) { + PyErr_BadInternalCall(); + return NULL; + } + + flag = PyObject_HasAttrString(callable, "__isabstractmethod__"); + if (flag == -1) { + return NULL; + } + if (flag) { + abs = PyObject_GetAttrString(callable, "__isabstractmethod__"); + if (abs == NULL) { + return NULL; + } + flag = PyObject_IsTrue(abs); + Py_DECREF(abs); + if (flag == -1) { + return NULL; + } + if (flag) { + Py_RETURN_TRUE; + } + } + Py_RETURN_FALSE; +} + +static PyObject * +cm_get_isabs(classmethod *cm) +{ + return _get_isabs(cm->cm_callable); +} + +static int +_set_isabs(PyObject *callable, PyObject *value) +{ + int flag; + + if (callable == NULL || value == NULL) { + PyErr_BadInternalCall(); + return -1; + } + + flag = PyObject_IsTrue(value); + if (flag == -1) + return -1; + + if (flag) + flag = PyObject_SetAttrString(callable, "__isabstractmethod__", Py_True); + else + flag = PyObject_SetAttrString(callable, "__isabstractmethod__", Py_False); + + if (flag == -1) + return -1; + + return 0; +} + +static int +cm_set_isabs(classmethod *cm, PyObject *value) +{ + return _set_isabs(cm->cm_callable, value); +} + +static PyGetSetDef cm_getsetlist[] = { + {"__isabstractmethod__", (getter)cm_get_isabs, (setter)cm_set_isabs}, + {NULL} /* Sentinel */ +}; + PyDoc_STRVAR(classmethod_doc, "classmethod(function) -> method\n\ \n\ @@ -826,7 +900,7 @@ 0, /* tp_iternext */ 0, /* tp_methods */ cm_memberlist, /* tp_members */ - 0, /* tp_getset */ + cm_getsetlist, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ cm_descr_get, /* tp_descr_get */ @@ -930,6 +1004,23 @@ {NULL} /* Sentinel */ }; +static PyObject * +sm_get_isabs(staticmethod *sm) +{ + return _get_isabs(sm->sm_callable); +} + +static int +sm_set_isabs(staticmethod *sm, PyObject *value) +{ + return _set_isabs(sm->sm_callable, value); +} + +static PyGetSetDef sm_getsetlist[] = { + {"__isabstractmethod__", (getter)sm_get_isabs, (setter)sm_set_isabs}, + {NULL} /* Sentinel */ +}; + PyDoc_STRVAR(staticmethod_doc, "staticmethod(function) -> method\n\ \n\ @@ -978,7 +1069,7 @@ 0, /* tp_iternext */ 0, /* tp_methods */ sm_memberlist, /* tp_members */ - 0, /* tp_getset */ + sm_getsetlist, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ sm_descr_get, /* tp_descr_get */ Index: Lib/test/test_descr.py =================================================================== --- Lib/test/test_descr.py (revision 83965) +++ Lib/test/test_descr.py (working copy) @@ -1270,6 +1270,17 @@ else: self.fail("classmethod shouldn't accept keyword args") + # issue 5867: test if classmethods support + # the __isabstractmethod__ attribute: + C.__dict__["goo"].__isabstractmethod__ = True + self.assertTrue(C.__dict__["goo"].__isabstractmethod__) + self.assertTrue(C.goo.__isabstractmethod__) + self.assertTrue(C.foo.__isabstractmethod__) + C.__dict__["goo"].__isabstractmethod__ = False + self.assertFalse(C.__dict__["goo"].__isabstractmethod__) + self.assertFalse(C.goo.__isabstractmethod__) + self.assertFalse(C.foo.__isabstractmethod__) + @support.impl_detail("the module 'xxsubtype' is internal") def test_classmethods_in_c(self): # Testing C-based class methods... @@ -1302,6 +1313,17 @@ self.assertEqual(d.foo(1), (d, 1)) self.assertEqual(D.foo(d, 1), (d, 1)) + # issue 5867: test if staticmethods support + # the __isabstractmethod__ attribute: + C.__dict__["goo"].__isabstractmethod__ = True + self.assertTrue(C.__dict__["goo"].__isabstractmethod__) + self.assertTrue(C.goo.__isabstractmethod__) + self.assertTrue(C.foo.__isabstractmethod__) + C.__dict__["goo"].__isabstractmethod__ = False + self.assertFalse(C.__dict__["goo"].__isabstractmethod__) + self.assertFalse(C.goo.__isabstractmethod__) + self.assertFalse(C.foo.__isabstractmethod__) + @support.impl_detail("the module 'xxsubtype' is internal") def test_staticmethods_in_c(self): # Testing C-based static methods... Index: Lib/test/test_abc.py =================================================================== --- Lib/test/test_abc.py (revision 83965) +++ Lib/test/test_abc.py (working copy) @@ -19,6 +19,28 @@ def bar(self): pass self.assertEqual(hasattr(bar, "__isabstractmethod__"), False) + # issue 5867: test if we can create abstract + # classmethods and staticmethods: + @abc.abstractmethod + @classmethod + def abs_cls(cls): pass + self.assertTrue(abs_cls.__isabstractmethod__) + + @classmethod + @abc.abstractmethod + def cls_abs(cls): pass + self.assertTrue(cls_abs.__isabstractmethod__) + + @abc.abstractmethod + @staticmethod + def abs_stat(): pass + self.assertTrue(abs_stat.__isabstractmethod__) + + @staticmethod + @abc.abstractmethod + def stat_abs(): pass + self.assertTrue(stat_abs.__isabstractmethod__) + def test_abstractproperty_basics(self): @abc.abstractproperty def foo(self): pass @@ -60,6 +82,60 @@ self.assertRaises(TypeError, F) # because bar is abstract now self.assertTrue(isabstract(F)) + # issue 5867: test if we can create abstract + # classmethods and staticmethods: + class C(metaclass=abc.ABCMeta): + @abc.abstractmethod + @classmethod + def abs_cls(cls): pass # abstract classmethod + @classmethod + @abc.abstractmethod + def cls_abs(cls): pass # other abstract classmethod + @abc.abstractmethod + @staticmethod + def abs_stat(): pass # abstract staticmethod + @staticmethod + @abc.abstractmethod + def stat_abs(): pass # other abstract staticmethod + self.assertIsInstance(C.__dict__["abs_cls"], classmethod) + self.assertIsInstance(C.__dict__["cls_abs"], classmethod) + self.assertIsInstance(C.__dict__["abs_stat"], staticmethod) + self.assertIsInstance(C.__dict__["stat_abs"], staticmethod) + self.assertEqual(C.__abstractmethods__, {"abs_cls", "cls_abs", + "abs_stat", "stat_abs"}) + self.assertRaises(TypeError, C) # because it has abstract methods + self.assertTrue(isabstract(C)) + + class D(C): + @classmethod + def abs_cls(cls): pass # concrete classmethod + @classmethod + def cls_abs(cls): pass # other concrete classmethod + @staticmethod + def abs_stat(): pass # concrete staticmethod + @staticmethod + def stat_abs(): pass # other constrete staticmethod + self.assertEqual(D.__abstractmethods__, set()) + D() # shouldn't raise + self.assertFalse(isabstract(D)) + class E(D): + @abc.abstractmethod + @classmethod + def abs_cls(cls): pass # abstract override of concrete + @classmethod + @abc.abstractmethod + def cls_abs(cls): pass + @abc.abstractmethod + @staticmethod + def abs_stat(): pass + @staticmethod + @abc.abstractmethod + def stat_abs(): pass + self.assertEqual(E.__abstractmethods__, {"abs_cls", "cls_abs", + "abs_stat", "stat_abs"}) + self.assertRaises(TypeError, E) # because it is has abstract methods + self.assertTrue(isabstract(E)) + def test_subclass_oldstyle_class(self): class A: __metaclass__ = abc.ABCMeta