diff options
-rw-r--r-- | Include/object.h | 3 | ||||
-rw-r--r-- | Lib/abc.py | 49 | ||||
-rw-r--r-- | Lib/test/test_descrtut.py | 1 | ||||
-rw-r--r-- | Objects/typeobject.c | 103 |
4 files changed, 108 insertions, 48 deletions
diff --git a/Include/object.h b/Include/object.h index 65440a6..8d04935 100644 --- a/Include/object.h +++ b/Include/object.h @@ -537,6 +537,9 @@ given type object has a specified feature. #define Py_TPFLAGS_HAVE_VERSION_TAG (1L<<18) #define Py_TPFLAGS_VALID_VERSION_TAG (1L<<19) +/* Type is abstract and cannot be instantiated */ +#define Py_TPFLAGS_IS_ABSTRACT (1L<<20) + /* These flags are used to determine if a type is a subclass. */ #define Py_TPFLAGS_INT_SUBCLASS (1L<<23) #define Py_TPFLAGS_LONG_SUBCLASS (1L<<24) @@ -51,52 +51,6 @@ class abstractproperty(property): __isabstractmethod__ = True -class _Abstract(object): - - """Helper class inserted into the bases by ABCMeta (using _fix_bases()). - - You should never need to explicitly subclass this class. - - There should never be a base class between _Abstract and object. - """ - - def __new__(cls, *args, **kwds): - am = cls.__dict__.get("__abstractmethods__") - if am: - raise TypeError("Can't instantiate abstract class %s " - "with abstract methods %s" % - (cls.__name__, ", ".join(sorted(am)))) - if (args or kwds) and cls.__init__ is object.__init__: - raise TypeError("Can't pass arguments to __new__ " - "without overriding __init__") - return super(_Abstract, cls).__new__(cls) - - @classmethod - def __subclasshook__(cls, subclass): - """Abstract classes can override this to customize issubclass(). - - This is invoked early on by __subclasscheck__() below. It - should return True, False or NotImplemented. If it returns - NotImplemented, the normal algorithm is used. Otherwise, it - overrides the normal algorithm (and the outcome is cached). - """ - return NotImplemented - - -def _fix_bases(bases): - """Helper method that inserts _Abstract in the bases if needed.""" - for base in bases: - if issubclass(base, _Abstract): - # _Abstract is already a base (maybe indirectly) - return bases - if object in bases: - # Replace object with _Abstract - return tuple([_Abstract if base is object else base - for base in bases]) - # Append _Abstract to the end - return bases + (_Abstract,) - - class ABCMeta(type): """Metaclass for defining Abstract Base Classes (ABCs). @@ -119,7 +73,6 @@ class ABCMeta(type): _abc_invalidation_counter = 0 def __new__(mcls, name, bases, namespace): - bases = _fix_bases(bases) cls = super(ABCMeta, mcls).__new__(mcls, name, bases, namespace) # Compute set of abstract method names abstracts = set(name @@ -130,7 +83,7 @@ class ABCMeta(type): value = getattr(cls, name, None) if getattr(value, "__isabstractmethod__", False): abstracts.add(name) - cls.__abstractmethods__ = abstracts + cls.__abstractmethods__ = frozenset(abstracts) # Set up inheritance registry cls._abc_registry = set() cls._abc_cache = set() diff --git a/Lib/test/test_descrtut.py b/Lib/test/test_descrtut.py index 94e9845..514e398 100644 --- a/Lib/test/test_descrtut.py +++ b/Lib/test/test_descrtut.py @@ -209,6 +209,7 @@ Instead, you can get the same information from the list type: '__setitem__', '__setslice__', '__str__', + '__subclasshook__', 'append', 'count', 'extend', diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 07ab61f..7db6dac 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -306,6 +306,40 @@ type_set_module(PyTypeObject *type, PyObject *value, void *context) } static PyObject * +type_abstractmethods(PyTypeObject *type, void *context) +{ + PyObject *mod = PyDict_GetItemString(type->tp_dict, + "__abstractmethods__"); + if (!mod) { + PyErr_Format(PyExc_AttributeError, "__abstractmethods__"); + return NULL; + } + Py_XINCREF(mod); + return mod; +} + +static int +type_set_abstractmethods(PyTypeObject *type, PyObject *value, void *context) +{ + /* __abstractmethods__ should only be set once on a type, in + abc.ABCMeta.__new__, so this function doesn't do anything + special to update subclasses. + */ + int res = PyDict_SetItemString(type->tp_dict, + "__abstractmethods__", value); + if (res == 0) { + type_modified(type); + if (value && PyObject_IsTrue(value)) { + type->tp_flags |= Py_TPFLAGS_IS_ABSTRACT; + } + else { + type->tp_flags &= ~Py_TPFLAGS_IS_ABSTRACT; + } + } + return res; +} + +static PyObject * type_get_bases(PyTypeObject *type, void *context) { Py_INCREF(type->tp_bases); @@ -542,6 +576,8 @@ static PyGetSetDef type_getsets[] = { {"__name__", (getter)type_name, (setter)type_set_name, NULL}, {"__bases__", (getter)type_get_bases, (setter)type_set_bases, NULL}, {"__module__", (getter)type_module, (setter)type_set_module, NULL}, + {"__abstractmethods__", (getter)type_abstractmethods, + (setter)type_set_abstractmethods, NULL}, {"__dict__", (getter)type_dict, NULL, NULL}, {"__doc__", (getter)type_get_doc, NULL, NULL}, {0} @@ -2749,6 +2785,56 @@ object_new(PyTypeObject *type, PyObject *args, PyObject *kwds) } if (err < 0) return NULL; + + if (type->tp_flags & Py_TPFLAGS_IS_ABSTRACT) { + static PyObject *comma = NULL; + PyObject *abstract_methods = NULL; + PyObject *builtins; + PyObject *sorted; + PyObject *sorted_methods = NULL; + PyObject *joined = NULL; + const char *joined_str; + + /* Compute ", ".join(sorted(type.__abstractmethods__)) + into joined. */ + abstract_methods = type_abstractmethods(type, NULL); + if (abstract_methods == NULL) + goto error; + builtins = PyEval_GetBuiltins(); + if (builtins == NULL) + goto error; + sorted = PyDict_GetItemString(builtins, "sorted"); + if (sorted == NULL) + goto error; + sorted_methods = PyObject_CallFunctionObjArgs(sorted, + abstract_methods, + NULL); + if (sorted_methods == NULL) + goto error; + if (comma == NULL) { + comma = PyString_InternFromString(", "); + if (comma == NULL) + goto error; + } + joined = PyObject_CallMethod(comma, "join", + "O", sorted_methods); + if (joined == NULL) + goto error; + joined_str = PyString_AsString(joined); + if (joined_str == NULL) + goto error; + + PyErr_Format(PyExc_TypeError, + "Can't instantiate abstract class %s " + "with abstract methods %s", + type->tp_name, + joined_str); + error: + Py_XDECREF(joined); + Py_XDECREF(sorted_methods); + Py_XDECREF(abstract_methods); + return NULL; + } return type->tp_alloc(type, 0); } @@ -3210,6 +3296,21 @@ object_reduce_ex(PyObject *self, PyObject *args) return _common_reduce(self, proto); } +static PyObject * +object_subclasshook(PyObject *cls, PyObject *args) +{ + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; +} + +PyDoc_STRVAR(object_subclasshook_doc, +"Abstract classes can override this to customize issubclass().\n" +"\n" +"This is invoked early on by abc.ABCMeta.__subclasscheck__().\n" +"It should return True, False or NotImplemented. If it returns\n" +"NotImplemented, the normal algorithm is used. Otherwise, it\n" +"overrides the normal algorithm (and the outcome is cached).\n"); + /* from PEP 3101, this code implements: @@ -3259,6 +3360,8 @@ static PyMethodDef object_methods[] = { PyDoc_STR("helper for pickle")}, {"__reduce__", object_reduce, METH_VARARGS, PyDoc_STR("helper for pickle")}, + {"__subclasshook__", object_subclasshook, METH_CLASS | METH_VARARGS, + object_subclasshook_doc}, {"__format__", object_format, METH_VARARGS, PyDoc_STR("default object formatter")}, {0} |