Index: Python/bltinmodule.c =================================================================== --- Python/bltinmodule.c (revision 54942) +++ Python/bltinmodule.c (working copy) @@ -181,7 +181,24 @@ \n\ Return the absolute value of the argument."); + static PyObject * +builtin_abstractmethod(PyObject *self, PyObject *func) +{ + if (PyObject_SetAttrString(func, "__isabstractmethod__", Py_True) < 0) + return NULL; + Py_INCREF(func); + return func; +} + +PyDoc_STRVAR(abstractmethod_doc, +"@abstractmethod\n\ +\n\ +A decorator to declare abstract methods.\n\ +Classes containing one or more abstract methods cannot be instantiated."); + + +static PyObject * builtin_all(PyObject *self, PyObject *v) { PyObject *it, *item; @@ -2264,6 +2281,7 @@ METH_VARARGS | METH_KEYWORDS, build_class_doc}, {"__import__", (PyCFunction)builtin___import__, METH_VARARGS | METH_KEYWORDS, import_doc}, {"abs", builtin_abs, METH_O, abs_doc}, + {"abstractmethod", builtin_abstractmethod, METH_O, abstractmethod_doc}, {"all", builtin_all, METH_O, all_doc}, {"any", builtin_any, METH_O, any_doc}, {"callable", builtin_callable, METH_O, callable_doc}, Index: Include/object.h =================================================================== --- Include/object.h (revision 54942) +++ Include/object.h (working copy) @@ -454,6 +454,9 @@ /* Set if the type allows subclassing */ #define Py_TPFLAGS_BASETYPE (1L<<10) +/* Abstract type -- cannot be instantiated */ +#define Py_TPFLAGS_ABSTRACT (1L<<11) + /* Set if the type is 'ready' -- fully initialized */ #define Py_TPFLAGS_READY (1L<<12) Index: Objects/typeobject.c =================================================================== --- Objects/typeobject.c (revision 54942) +++ Objects/typeobject.c (working copy) @@ -130,6 +130,8 @@ static PyTypeObject *best_base(PyObject *); static int mro_internal(PyTypeObject *); static int compatible_for_assignment(PyTypeObject *, PyTypeObject *, char *); +static Py_ssize_t check_new_abstracts(PyTypeObject *, PyObject *); +static int check_abstracts(PyTypeObject *, PyTypeObject *, PyObject *); static int add_subclass(PyTypeObject*, PyTypeObject*); static void remove_subclass(PyTypeObject *, PyTypeObject *); static void update_all_slots(PyTypeObject *); @@ -439,6 +441,13 @@ const size_t size = _PyObject_VAR_SIZE(type, nitems+1); /* note that we need to add one, for the sentinel */ + if (type->tp_flags & Py_TPFLAGS_ABSTRACT) { + PyErr_Format(PyExc_TypeError, + "cannot instantiate abstract class %.100s", + type->tp_name); + return NULL; + } + if (PyType_IS_GC(type)) obj = _PyObject_GC_Malloc(size); else @@ -3109,6 +3118,7 @@ PyType_Ready(PyTypeObject *type) { PyObject *dict, *bases; + PyObject *abstracts = NULL; PyTypeObject *base; Py_ssize_t i, n; @@ -3268,14 +3278,29 @@ } /* Link into each base class's list of subclasses */ + /* Also check for still-abstract methods */ + abstracts = PySet_New(NULL); + if (abstracts == NULL) + goto error; bases = type->tp_bases; n = PyTuple_GET_SIZE(bases); for (i = 0; i < n; i++) { PyObject *b = PyTuple_GET_ITEM(bases, i); - if (PyType_Check(b) && - add_subclass((PyTypeObject *)b, type) < 0) - goto error; + if (PyType_Check(b)) { + if (add_subclass((PyTypeObject *)b, type) < 0) + goto error; + if (check_abstracts(type, (PyTypeObject *)b, + abstracts) < 0) + goto error; + } } + /* Check for new abstract methods and set __abstractmethods__ */ + i = check_new_abstracts(type, abstracts); + if (i < 0) + goto error; + Py_XDECREF(abstracts); + if (i) + type->tp_flags |= Py_TPFLAGS_ABSTRACT; /* All done -- set the ready flag */ assert(type->tp_dict != NULL); @@ -3284,11 +3309,89 @@ return 0; error: + Py_XDECREF(abstracts); type->tp_flags &= ~Py_TPFLAGS_READYING; return -1; } static int +check_abstract_method(PyObject *name, PyObject *meth, PyObject *abstracts) +{ + PyObject *isab = PyObject_GetAttrString(meth, "__isabstractmethod__"); + int istrue; + if (isab == NULL) { + PyErr_Clear(); + return 0; + } + istrue = PyObject_IsTrue(isab); + Py_DECREF(isab); + if (istrue > 0) + istrue = PySet_Add(abstracts, name); + return istrue; +} + +static Py_ssize_t +check_new_abstracts(PyTypeObject *type, PyObject *abstracts) +{ + Py_ssize_t i = 0; + PyObject *key, *value; + + if (PyExc_MemoryErrorInst == NULL) + return 0; /* Too early during initialization */ + + assert(PyDict_Check(type->tp_dict)); + while (PyDict_Next(type->tp_dict, &i, &key, &value)) { + if (check_abstract_method(key, value, abstracts) < 0) + return -1; + } + if (PyDict_SetItemString(type->tp_dict, "__abstractmethods__", + abstracts) < 0) + return -1; + return PySet_Size(abstracts); +} + +static int +check_abstracts(PyTypeObject *type, PyTypeObject *base, PyObject *abstracts) +{ + PyObject *am; + PyObject *fast; + Py_ssize_t n, i; + PyObject **items; + + if (!(base->tp_flags & Py_TPFLAGS_ABSTRACT)) + return 0; + + am = PyObject_GetAttrString((PyObject *)base, "__abstractmethods__"); + if (am == NULL) { + PyErr_Clear(); + return 0; + } + fast = PySequence_Fast(am, "__abstractmethods__ is not iterable"); + if (fast == NULL) { + Py_DECREF(am); + return -1; + } + n = PySequence_Fast_GET_SIZE(fast); + items = PySequence_Fast_ITEMS(fast); + for (i = 0; i < n; i++) { + PyObject *name = items[i]; + PyObject *meth = PyObject_GetAttr((PyObject *)type, name); + if (meth == NULL) { + PyErr_Clear(); + continue; + } + if (check_abstract_method(name, meth, abstracts) < 0) { + Py_DECREF(fast); + Py_DECREF(am); + return -1; + } + } + Py_DECREF(fast); + Py_DECREF(am); + return 0; +} + +static int add_subclass(PyTypeObject *base, PyTypeObject *type) { Py_ssize_t i; Index: Lib/test/test_descr.py =================================================================== --- Lib/test/test_descr.py (revision 54942) +++ Lib/test/test_descr.py (working copy) @@ -3192,7 +3192,8 @@ if verbose: print("Testing dict-proxy iterkeys...") keys = [ key for key in C.__dict__.keys() ] keys.sort() - vereq(keys, ['__dict__', '__doc__', '__module__', '__weakref__', 'meth']) + vereq(keys, ['__abstractmethods__', + '__dict__', '__doc__', '__module__', '__weakref__', 'meth']) def dictproxyitervalues(): class C(object): @@ -3200,7 +3201,7 @@ pass if verbose: print("Testing dict-proxy itervalues...") values = [ values for values in C.__dict__.values() ] - vereq(len(values), 5) + vereq(len(values), 6) def dictproxyiteritems(): class C(object): @@ -3209,7 +3210,8 @@ if verbose: print("Testing dict-proxy iteritems...") keys = [ key for (key, value) in C.__dict__.items() ] keys.sort() - vereq(keys, ['__dict__', '__doc__', '__module__', '__weakref__', 'meth']) + vereq(keys, ['__abstractmethods__', + '__dict__', '__doc__', '__module__', '__weakref__', 'meth']) def funnynew(): if verbose: print("Testing __new__ returning something unexpected...") Index: Lib/test/test_abstract.py =================================================================== --- Lib/test/test_abstract.py (revision 0) +++ Lib/test/test_abstract.py (revision 0) @@ -0,0 +1,90 @@ +"""Unit tests for @abstractmethod decorator.""" + +import unittest +from test import test_support + + +class A: + @abstractmethod + def foo(self): pass + +class B(A): + pass + +class C(B): + def foo(self): return 42 + +class D(C): + pass + +class AbstractMethodTest(unittest.TestCase): + + def testCannotInstantiateAbstractClass(self): + self.assertRaises(TypeError, A) + + def testCannotInstantiateAbstractSubclass(self): + self.assertRaises(TypeError, B) + + def testCanInstantiateConcreteSubclass(self): + C() + + def testCanInstantiateConcreteSubsubclass(self): + D() + + def testValidAttributes(self): + self.assertEqual(A.foo.__isabstractmethod__, True) + self.assertEqual(A.__abstractmethods__, {"foo"}) + self.assertEqual(B.__abstractmethods__, {"foo"}) + self.assertEqual(C.__abstractmethods__, set()) + self.assertEqual(D.__abstractmethods__, set()) + + +def test_main(): + test_support.run_unittest(AbstractMethodTest) + + +if __name__ == "__main__": + unittest.main() +"""Unit tests for @abstractmethod decorator.""" + +import unittest +from test import test_support + + +class A: + @abstractmethod + def foo(self): pass + +class B(A): + pass + +class C(B): + def foo(self): return 42 + +class D(C): + pass + +class AbstractMethodTest(unittest.TestCase): + + def testCannotInstantiateAbstractClass(self): + self.assertRaises(TypeError, A) + + def testCannotInstantiateAbstractSubclass(self): + self.assertRaises(TypeError, B) + + def testCanInstantiateConcreteSubclass(self): + C() + + def testCanInstantiateConcreteSubsubclass(self): + D() + + def testValidAttributes(self): + self.assertEqual(A.foo.__isabstractmethod__, True) + self.assertEqual(A.__abstractmethods__, {"foo"}) + self.assertEqual(B.__abstractmethods__, {"foo"}) + self.assertEqual(C.__abstractmethods__, set()) + self.assertEqual(D.__abstractmethods__, set()) + + +if __name__ == "__main__": + unittest.main() Property changes on: Lib/test/test_abstract.py ___________________________________________________________________ Name: svn:keywords + Id Name: svn:eol-style + native