]> granicus.if.org Git - python/commitdiff
Enhance issubclass() and PyObject_IsSubclass() so that a tuple is
authorWalter Dörwald <walter@livinglogic.de>
Thu, 12 Dec 2002 16:41:44 +0000 (16:41 +0000)
committerWalter Dörwald <walter@livinglogic.de>
Thu, 12 Dec 2002 16:41:44 +0000 (16:41 +0000)
supported as the second argument. This has the same meaning as
for isinstance(), i.e. issubclass(X, (A, B)) is equivalent
to issubclass(X, A) or issubclass(X, B). Compared to isinstance(),
this patch does not search the tuple recursively for classes, i.e.
any entry in the tuple that is not a class, will result in a
TypeError.

This closes SF patch #649608.

Doc/api/abstract.tex
Doc/lib/libfuncs.tex
Lib/test/test_isinstance.py
Misc/NEWS
Objects/abstract.c
Objects/classobject.c
Python/bltinmodule.c

index 1cf69dca7dcf1cc676a0ac5057fcc3e42fa25526..91c0944edf8c1bcb38ee60e596d1ac9027ce7095 100644 (file)
@@ -205,10 +205,15 @@ determination.
                                             PyObject *cls}
   Returns \code{1} if the class \var{derived} is identical to or
   derived from the class \var{cls}, otherwise returns \code{0}.  In
-  case of an error, returns \code{-1}.  If either \var{derived} or
-  \var{cls} is not an actual class object, this function uses the
-  generic algorithm described above.
+  case of an error, returns \code{-1}. If \var{cls}
+  is a tuple, the check will be done against every entry in \var{cls}.
+  The result will be \code{1} when at least one of the checks returns
+  \code{1}, otherwise it will be \code{0}. If either \var{derived} or
+  \var{cls} is not an actual class object (or tuple), this function
+  uses the generic algorithm described above.
   \versionadded{2.1}
+  \versionchanged[Older versions of Python did not support a tuple
+                  as the second argument]{2.3}
 \end{cfuncdesc}
 
 
index fd9092d0a6eb59c8e7595bfb05d2fd879d5a2fcf..4716dd256b55183d806d62b58255c60564749fa6 100644 (file)
@@ -550,11 +550,13 @@ def my_import(name):
   \versionchanged[Support for a tuple of type information was added]{2.2}
 \end{funcdesc}
 
-\begin{funcdesc}{issubclass}{class1, class2}
-  Return true if \var{class1} is a subclass (direct or indirect) of
-  \var{class2}.  A class is considered a subclass of itself.  If
-  either argument is not a class object, a \exception{TypeError}
-  exception is raised.
+\begin{funcdesc}{issubclass}{class, classinfo}
+  Return true if \var{class} is a subclass (direct or indirect) of
+  \var{classinfo}.  A class is considered a subclass of itself.
+  \var{classinfo} may be a tuple of class objects, in which case every
+  entry in \var{classinfo} will be checked. In any other case, a
+  \exception{TypeError} exception is raised.
+  \versionchanged[Support for a tuple of type information was added]{2.3}
 \end{funcdesc}
 
 \begin{funcdesc}{iter}{o\optional{, sentinel}}
index 8dfb9566534ef3f2a109e37b771f467b2faaa822..1bb09a6edc353f695712a1fdb38bbd0db503f778 100644 (file)
@@ -218,6 +218,15 @@ class TestIsInstanceIsSubclass(unittest.TestCase):
         self.assertEqual(False, issubclass(AbstractChild, Super))
         self.assertEqual(False, issubclass(AbstractChild, Child))
 
+    def test_subclass_tuple(self):
+        # test with a tuple as the second argument classes
+        self.assertEqual(True, issubclass(Child, (Child,)))
+        self.assertEqual(True, issubclass(Child, (Super,)))
+        self.assertEqual(False, issubclass(Super, (Child,)))
+        self.assertEqual(True, issubclass(Super, (Child, Super)))
+        self.assertEqual(False, issubclass(Child, ()))
+        self.assertRaises(TypeError, issubclass, Child, ((Child,),))
+
 
 
 \f
index 033c5c822c6cf84d1a8192bfca62c6186f69dd51..d51ddbb4273a38ecd5c3014ef6c2da2cd1845b9b 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -84,6 +84,10 @@ Type/class unification and new-style classes
 Core and builtins
 -----------------
 
+- issubclass now supports a tuple as the second argument, just like
+  isinstance does. ``issubclass(X, (A, B))`` is equivalent to
+  ``issubclass(X, A) or issubclass(X, B)``.
+
 - Thanks to Armin Rigo, the last known way to provoke a system crash
   by cleverly arranging for a comparison function to mutate a list
   during a list.sort() operation has been fixed.  The effect of
index e77cde38a5780dc659f85eef131165cfe92097f1..47d2f31dd7f1da99131b219226f70888e34e1e54 100644 (file)
@@ -1914,6 +1914,15 @@ abstract_issubclass(PyObject *derived, PyObject *cls)
        if (derived == cls)
                return 1;
 
+       if (PyTuple_Check(cls)) {
+               /* Not a general sequence -- that opens up the road to
+                  recursion and stack overflow. */
+               n = PyTuple_GET_SIZE(cls);
+               for (i = 0; i < n; i++) {
+                       if (derived == PyTuple_GET_ITEM(cls, i))
+                               return 1;
+               }
+       }
        bases = abstract_get_bases(derived);
        if (bases == NULL) {
                if (PyErr_Occurred())
@@ -1932,6 +1941,20 @@ abstract_issubclass(PyObject *derived, PyObject *cls)
        return r;
 }
 
+static int
+check_class(PyObject *cls, const char *error)
+{
+       PyObject *bases = abstract_get_bases(cls);
+       if (bases == NULL) {
+               /* Do not mask errors. */
+               if (!PyErr_Occurred())
+                       PyErr_SetString(PyExc_TypeError, error);
+               return 0;
+       }
+       Py_DECREF(bases);
+       return -1;
+}
+
 int
 PyObject_IsInstance(PyObject *inst, PyObject *cls)
 {
@@ -1962,16 +1985,10 @@ PyObject_IsInstance(PyObject *inst, PyObject *cls)
                return retval;
        }
        else {
-               PyObject *cls_bases = abstract_get_bases(cls);
-               if (cls_bases == NULL) {
-                       /* Do not mask errors. */
-                       if (!PyErr_Occurred())
-                               PyErr_SetString(PyExc_TypeError,
-                               "isinstance() arg 2 must be a class, type,"
-                               " or tuple of classes and types");
+               if (!check_class(cls,
+                       "isinstance() arg 2 must be a class, type,"
+                       " or tuple of classes and types"))
                        return -1;
-               }
-               Py_DECREF(cls_bases);
                if (__class__ == NULL) {
                        __class__ = PyString_FromString("__class__");
                        if (__class__ == NULL)
@@ -1997,28 +2014,25 @@ PyObject_IsSubclass(PyObject *derived, PyObject *cls)
        int retval;
 
        if (!PyClass_Check(derived) || !PyClass_Check(cls)) {
-               PyObject *derived_bases;
-               PyObject *cls_bases;
-
-               derived_bases = abstract_get_bases(derived);
-               if (derived_bases == NULL) {
-                       /* Do not mask errors */
-                       if (!PyErr_Occurred())
-                               PyErr_SetString(PyExc_TypeError,
-                                       "issubclass() arg 1 must be a class");
+               if (!check_class(derived, "issubclass() arg 1 must be a class"))
                        return -1;
+
+               if (PyTuple_Check(cls)) {
+                       int i;
+                       int n = PyTuple_GET_SIZE(cls);
+                       for (i = 0; i < n; ++i) {
+                               if (!check_class(PyTuple_GET_ITEM(cls, i),
+                                               "issubclass() arg 2 must be a class"
+                                               " or tuple of classes"))
+                                       return -1;
+                       }
                }
-               Py_DECREF(derived_bases);
-
-               cls_bases = abstract_get_bases(cls);
-               if (cls_bases == NULL) {
-                       /* Do not mask errors */
-                       if (!PyErr_Occurred())
-                               PyErr_SetString(PyExc_TypeError,
-                                       "issubclass() arg 2 must be a class");
-                       return -1;
+               else {
+                       if (!check_class(cls,
+                                       "issubclass() arg 2 must be a class"
+                                       " or tuple of classes"))
+                               return -1;
                }
-               Py_DECREF(cls_bases);
 
                retval = abstract_issubclass(derived, cls);
        }
index f7b442a80a3e134b67b6e0fc75a45f432b64d154..f3b98733de02e00f975a67112b33215239504569 100644 (file)
@@ -487,6 +487,13 @@ PyClass_IsSubclass(PyObject *class, PyObject *base)
        PyClassObject *cp;
        if (class == base)
                return 1;
+       if (PyTuple_Check(base)) {
+               n = PyTuple_GET_SIZE(base);
+               for (i = 0; i < n; i++) {
+                       if (class == PyTuple_GET_ITEM(base, i))
+                               return 1;
+               }
+       }
        if (class == NULL || !PyClass_Check(class))
                return 0;
        cp = (PyClassObject *)class;
index 7e7ad2ef60c3c3d12ac1bbbfe6ba2bae6d5fb757..ab760068903799b7d5c67e62aa66912c30487c60 100644 (file)
@@ -1586,7 +1586,9 @@ builtin_issubclass(PyObject *self, PyObject *args)
 PyDoc_STRVAR(issubclass_doc,
 "issubclass(C, B) -> bool\n\
 \n\
-Return whether class C is a subclass (i.e., a derived class) of class B.");
+Return whether class C is a subclass (i.e., a derived class) of class B.\n\
+When using a tuple as the second argument issubclass(X, (A, B, ...)),\n\
+is a shortcut for issubclass(X, A) or issubclass(X, B) or ... (etc.).");
 
 
 static PyObject*