summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/api/abstract.tex11
-rw-r--r--Doc/lib/libfuncs.tex12
-rw-r--r--Lib/test/test_isinstance.py9
-rw-r--r--Misc/NEWS4
-rw-r--r--Objects/abstract.c70
-rw-r--r--Objects/classobject.c7
-rw-r--r--Python/bltinmodule.c4
7 files changed, 80 insertions, 37 deletions
diff --git a/Doc/api/abstract.tex b/Doc/api/abstract.tex
index 1cf69dc..91c0944 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 fd9092d..4716dd2 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 8dfb956..1bb09a6 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 033c5c8..d51ddbb 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 e77cde3..47d2f31 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;
+
+ 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);
}
diff --git a/Objects/classobject.c b/Objects/classobject.c
index f7b442a..f3b9873 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 7e7ad2e..ab76006 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*