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.
This commit is contained in:
Walter Dörwald 2002-12-12 16:41:44 +00:00
parent b083cb3901
commit d9a6ad3beb
7 changed files with 80 additions and 37 deletions

View 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}

View 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}}

View 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,),))

View File

@ -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

View 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;
}
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);
}

View 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;

View 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*