]> granicus.if.org Git - python/commitdiff
Move abc._Abstract into object by adding a new flag Py_TPFLAGS_IS_ABSTRACT,
authorJeffrey Yasskin <jyasskin@gmail.com>
Thu, 28 Feb 2008 04:45:36 +0000 (04:45 +0000)
committerJeffrey Yasskin <jyasskin@gmail.com>
Thu, 28 Feb 2008 04:45:36 +0000 (04:45 +0000)
which forbids constructing types that have it set. The effect is to speed

  ./python.exe -m timeit -s 'import abc' -s 'class Foo(object): __metaclass__ = abc.ABCMeta' 'Foo()'

up from 2.5us to 0.201us. This fixes issue 1762.

Include/object.h
Lib/abc.py
Lib/test/test_descrtut.py
Objects/typeobject.c

index 65440a6511df63c2fbf768b7c7d54941d492e436..8d04935840b92b11c13a05ee7aa3c8ce36aaa62b 100644 (file)
@@ -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)
index 9d15012c30d4c9de0894a8a51c506ce8cc05764b..5da2590e4fc283561ccea255d83180fa07ab5429 100644 (file)
@@ -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()
index 94e984585ca6f723dafdaa9eee3b33e0f3ebf9b2..514e398787a6ba9f3de6c0fb643da6f6843e94e7 100644 (file)
@@ -209,6 +209,7 @@ Instead, you can get the same information from the list type:
      '__setitem__',
      '__setslice__',
      '__str__',
+     '__subclasshook__',
      'append',
      'count',
      'extend',
index 07ab61f87a823595cb702d2e18c691ce2513b8fc..7db6dac7ca817c195b5a02aca4afca77845663a0 100644 (file)
@@ -305,6 +305,40 @@ type_set_module(PyTypeObject *type, PyObject *value, void *context)
        return PyDict_SetItemString(type->tp_dict, "__module__", value);
 }
 
+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)
 {
@@ -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}