diff --git a/Doc/api/abstract.tex b/Doc/api/abstract.tex index 1cf69dca7dc..91c0944edf8 100644 --- a/Doc/api/abstract.tex +++ b/Doc/api/abstract.tex @@ -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} diff --git a/Doc/lib/libfuncs.tex b/Doc/lib/libfuncs.tex index fd9092d0a6e..4716dd256b5 100644 --- a/Doc/lib/libfuncs.tex +++ b/Doc/lib/libfuncs.tex @@ -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}} diff --git a/Lib/test/test_isinstance.py b/Lib/test/test_isinstance.py index 8dfb9566534..1bb09a6edc3 100644 --- a/Lib/test/test_isinstance.py +++ b/Lib/test/test_isinstance.py @@ -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,),)) + diff --git a/Misc/NEWS b/Misc/NEWS index 033c5c822c6..d51ddbb4273 100644 --- 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 diff --git a/Objects/abstract.c b/Objects/abstract.c index e77cde38a57..47d2f31dd7f 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -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; - } - 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; + 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; + } + } + 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); } diff --git a/Objects/classobject.c b/Objects/classobject.c index f7b442a80a3..f3b98733de0 100644 --- a/Objects/classobject.c +++ b/Objects/classobject.c @@ -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; diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 7e7ad2ef60c..ab760068903 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -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*