Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d9a6ad3

Browse files
committed
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.
1 parent b083cb3 commit d9a6ad3

7 files changed

Lines changed: 80 additions & 37 deletions

File tree

Doc/api/abstract.tex

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,15 @@ \section{Object Protocol \label{object}}
205205
PyObject *cls}
206206
Returns \code{1} if the class \var{derived} is identical to or
207207
derived from the class \var{cls}, otherwise returns \code{0}. In
208-
case of an error, returns \code{-1}. If either \var{derived} or
209-
\var{cls} is not an actual class object, this function uses the
210-
generic algorithm described above.
208+
case of an error, returns \code{-1}. If \var{cls}
209+
is a tuple, the check will be done against every entry in \var{cls}.
210+
The result will be \code{1} when at least one of the checks returns
211+
\code{1}, otherwise it will be \code{0}. If either \var{derived} or
212+
\var{cls} is not an actual class object (or tuple), this function
213+
uses the generic algorithm described above.
211214
\versionadded{2.1}
215+
\versionchanged[Older versions of Python did not support a tuple
216+
as the second argument]{2.3}
212217
\end{cfuncdesc}
213218

214219

Doc/lib/libfuncs.tex

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -550,11 +550,13 @@ \section{Built-in Functions \label{built-in-funcs}}
550550
\versionchanged[Support for a tuple of type information was added]{2.2}
551551
\end{funcdesc}
552552

553-
\begin{funcdesc}{issubclass}{class1, class2}
554-
Return true if \var{class1} is a subclass (direct or indirect) of
555-
\var{class2}. A class is considered a subclass of itself. If
556-
either argument is not a class object, a \exception{TypeError}
557-
exception is raised.
553+
\begin{funcdesc}{issubclass}{class, classinfo}
554+
Return true if \var{class} is a subclass (direct or indirect) of
555+
\var{classinfo}. A class is considered a subclass of itself.
556+
\var{classinfo} may be a tuple of class objects, in which case every
557+
entry in \var{classinfo} will be checked. In any other case, a
558+
\exception{TypeError} exception is raised.
559+
\versionchanged[Support for a tuple of type information was added]{2.3}
558560
\end{funcdesc}
559561

560562
\begin{funcdesc}{iter}{o\optional{, sentinel}}

Lib/test/test_isinstance.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,15 @@ def test_subclass_abstract(self):
218218
self.assertEqual(False, issubclass(AbstractChild, Super))
219219
self.assertEqual(False, issubclass(AbstractChild, Child))
220220

221+
def test_subclass_tuple(self):
222+
# test with a tuple as the second argument classes
223+
self.assertEqual(True, issubclass(Child, (Child,)))
224+
self.assertEqual(True, issubclass(Child, (Super,)))
225+
self.assertEqual(False, issubclass(Super, (Child,)))
226+
self.assertEqual(True, issubclass(Super, (Child, Super)))
227+
self.assertEqual(False, issubclass(Child, ()))
228+
self.assertRaises(TypeError, issubclass, Child, ((Child,),))
229+
221230

222231

223232

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ Type/class unification and new-style classes
8484
Core and builtins
8585
-----------------
8686

87+
- issubclass now supports a tuple as the second argument, just like
88+
isinstance does. ``issubclass(X, (A, B))`` is equivalent to
89+
``issubclass(X, A) or issubclass(X, B)``.
90+
8791
- Thanks to Armin Rigo, the last known way to provoke a system crash
8892
by cleverly arranging for a comparison function to mutate a list
8993
during a list.sort() operation has been fixed. The effect of

Objects/abstract.c

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1914,6 +1914,15 @@ abstract_issubclass(PyObject *derived, PyObject *cls)
19141914
if (derived == cls)
19151915
return 1;
19161916

1917+
if (PyTuple_Check(cls)) {
1918+
/* Not a general sequence -- that opens up the road to
1919+
recursion and stack overflow. */
1920+
n = PyTuple_GET_SIZE(cls);
1921+
for (i = 0; i < n; i++) {
1922+
if (derived == PyTuple_GET_ITEM(cls, i))
1923+
return 1;
1924+
}
1925+
}
19171926
bases = abstract_get_bases(derived);
19181927
if (bases == NULL) {
19191928
if (PyErr_Occurred())
@@ -1932,6 +1941,20 @@ abstract_issubclass(PyObject *derived, PyObject *cls)
19321941
return r;
19331942
}
19341943

1944+
static int
1945+
check_class(PyObject *cls, const char *error)
1946+
{
1947+
PyObject *bases = abstract_get_bases(cls);
1948+
if (bases == NULL) {
1949+
/* Do not mask errors. */
1950+
if (!PyErr_Occurred())
1951+
PyErr_SetString(PyExc_TypeError, error);
1952+
return 0;
1953+
}
1954+
Py_DECREF(bases);
1955+
return -1;
1956+
}
1957+
19351958
int
19361959
PyObject_IsInstance(PyObject *inst, PyObject *cls)
19371960
{
@@ -1962,16 +1985,10 @@ PyObject_IsInstance(PyObject *inst, PyObject *cls)
19621985
return retval;
19631986
}
19641987
else {
1965-
PyObject *cls_bases = abstract_get_bases(cls);
1966-
if (cls_bases == NULL) {
1967-
/* Do not mask errors. */
1968-
if (!PyErr_Occurred())
1969-
PyErr_SetString(PyExc_TypeError,
1970-
"isinstance() arg 2 must be a class, type,"
1971-
" or tuple of classes and types");
1988+
if (!check_class(cls,
1989+
"isinstance() arg 2 must be a class, type,"
1990+
" or tuple of classes and types"))
19721991
return -1;
1973-
}
1974-
Py_DECREF(cls_bases);
19751992
if (__class__ == NULL) {
19761993
__class__ = PyString_FromString("__class__");
19771994
if (__class__ == NULL)
@@ -1997,28 +2014,25 @@ PyObject_IsSubclass(PyObject *derived, PyObject *cls)
19972014
int retval;
19982015

19992016
if (!PyClass_Check(derived) || !PyClass_Check(cls)) {
2000-
PyObject *derived_bases;
2001-
PyObject *cls_bases;
2002-
2003-
derived_bases = abstract_get_bases(derived);
2004-
if (derived_bases == NULL) {
2005-
/* Do not mask errors */
2006-
if (!PyErr_Occurred())
2007-
PyErr_SetString(PyExc_TypeError,
2008-
"issubclass() arg 1 must be a class");
2017+
if (!check_class(derived, "issubclass() arg 1 must be a class"))
20092018
return -1;
2019+
2020+
if (PyTuple_Check(cls)) {
2021+
int i;
2022+
int n = PyTuple_GET_SIZE(cls);
2023+
for (i = 0; i < n; ++i) {
2024+
if (!check_class(PyTuple_GET_ITEM(cls, i),
2025+
"issubclass() arg 2 must be a class"
2026+
" or tuple of classes"))
2027+
return -1;
2028+
}
20102029
}
2011-
Py_DECREF(derived_bases);
2012-
2013-
cls_bases = abstract_get_bases(cls);
2014-
if (cls_bases == NULL) {
2015-
/* Do not mask errors */
2016-
if (!PyErr_Occurred())
2017-
PyErr_SetString(PyExc_TypeError,
2018-
"issubclass() arg 2 must be a class");
2019-
return -1;
2030+
else {
2031+
if (!check_class(cls,
2032+
"issubclass() arg 2 must be a class"
2033+
" or tuple of classes"))
2034+
return -1;
20202035
}
2021-
Py_DECREF(cls_bases);
20222036

20232037
retval = abstract_issubclass(derived, cls);
20242038
}

Objects/classobject.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,13 @@ PyClass_IsSubclass(PyObject *class, PyObject *base)
487487
PyClassObject *cp;
488488
if (class == base)
489489
return 1;
490+
if (PyTuple_Check(base)) {
491+
n = PyTuple_GET_SIZE(base);
492+
for (i = 0; i < n; i++) {
493+
if (class == PyTuple_GET_ITEM(base, i))
494+
return 1;
495+
}
496+
}
490497
if (class == NULL || !PyClass_Check(class))
491498
return 0;
492499
cp = (PyClassObject *)class;

Python/bltinmodule.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1586,7 +1586,9 @@ builtin_issubclass(PyObject *self, PyObject *args)
15861586
PyDoc_STRVAR(issubclass_doc,
15871587
"issubclass(C, B) -> bool\n\
15881588
\n\
1589-
Return whether class C is a subclass (i.e., a derived class) of class B.");
1589+
Return whether class C is a subclass (i.e., a derived class) of class B.\n\
1590+
When using a tuple as the second argument issubclass(X, (A, B, ...)),\n\
1591+
is a shortcut for issubclass(X, A) or issubclass(X, B) or ... (etc.).");
15901592

15911593

15921594
static PyObject*

0 commit comments

Comments
 (0)