From d9a6ad3bebc2b451482db152171fca3144b2cd97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Walter=20D=C3=B6rwald?= Date: Thu, 12 Dec 2002 16:41:44 +0000 Subject: [PATCH] Enhance issubclass() and PyObject_IsSubclass() so that a tuple is 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 | 11 ++++-- Doc/lib/libfuncs.tex | 12 ++++--- Lib/test/test_isinstance.py | 9 +++++ Misc/NEWS | 4 +++ Objects/abstract.c | 70 ++++++++++++++++++++++--------------- Objects/classobject.c | 7 ++++ Python/bltinmodule.c | 4 ++- 7 files changed, 80 insertions(+), 37 deletions(-) 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*