isinstance() necessary and helpful, sometimes

Raymond Hettinger othello at javanet.com
Fri Jan 25 00:27:31 EST 2002


Since I've made many uses of isinstance(), Kragen Sitaker's post inspired me
to scan my code and review each occurance.

I found three categories:
1) isinstance is necessary and helpful
2) isinstance needed to be replaced with hasattr()
3) isinstance() was the least worst alternative in the absence of PEP-246.

Helpful and Necessary Cases:
---------------------------------

1.  Since Python does not directly support multiple-dispatch like CLOS and
Dylan, isinstance() provides a way to handle multiple argument types.  For
instance, Mat.__mul__(self, other) behaves differently depending on whether
'other' is a scalar, a vector, or another matrix.  isinstance(other,Mat) and
isinstance(other,Vec) were used to switch methods -- note that Vec and Mat
have many of the same methods and are not readily differentiated by their
attributes.

2.  Several pieces of code used the Composite pattern and needed a way to
differentiate between complex and atomic arguments.  The most basic and
common case occurs in code recursing through nested lists --
isinstance(elem, list) provided the necessary test (try solving basic LISP
problems without a test to discriminate between atoms and lists).   A more
complex version of this case occurs when the container is a user defined
class rather than built-in type (sort of a Russian dolls phenomenon).

3.  Adding functionality with a Decorator pattern, it's easy to end up
re-wrapping an object more than once.  To avoid multiple wraps, it's helpful
to use isinstance() to see if the argument is already of the right type.  In
my code, the Table class added elementwise operations to built-in lists.
Everywhere I needed a Table, the user could have easily supplied either a
Table or a List, so used something like:   arg = isinstance(arg,Table) and
arg or Table(arg).

4.  Some code needs to fork paths when the class indicates an intrinsic
quality not revealed by the attributes.  Implementing matrix exponentation,
__pow__(self,exponent), efficiently requires testing
isinstance(exponent,int) because the float and complex cases are to be
computed differently.

In the same package, lower triangular matrices were a sub-class of
generalized matrices.  Occasionally, it was necessary to assert that a
matrix was of a certain type eventhough it supported exactly the same
methods as general matrices (for instance the Mat.LU method carries an
assertion, isinstance(L, LowerTri)).


Cases Where hasattr() is Better:
------------------------------------
1.  Supplying a default result when the required methods are not available
for all possible argument types.  For example, .conjugate() is only
available for complex numbers but has meaning for other numeric types.
Defining myConj(z) as:  hasattr(z,'conjugate') and z.conjugate() or z.
Similar definitions can be supplied for .imag and .real so that any numeric
type will "do the right thing" when fed to the functions.  For example:
map( myConj, [ 3+4j, 5, 6.0, 7L ] )

2.   In a similar vein, I needed to make sure that an iteration interface
was supplied even when xrange or sequence types were supplied as arguments:
       gens = [ type(s)==types.GeneratorType and s or iter(s) for s in
sequences]
Was better replaced by:
       gens = [ hasattr(s,'next') and s or iter(s) for s in sequences]


Case Where isinstance() Was the Least Worst Alternative
-----------------------------------------------------------------
In the following code, the goal is to accept a regular expression pattern in
the form of a string or in the form of an already compiled expression.
isinstance() gives a direct way of checking to see if a string was supplied.
hasattr(pattern,'search') was a weak alternative because it did not check
for other attributes such as .lastgroup, .group, and .end.    Wrapping the
whole thing in a try/except was ugly for two reasons.  First, to handle the
exception, the argument would have to be coerced and then the whole function
either needed to be repeated or recursed.  Second, 'except AttributeError:'
is dangerous in that it also traps other programming errors which should be
made visible -- for instance, misspelling pattern.seeearch() would be
trapped eventhough you would want to know that there is no such attribute.

def tokenize(pattern, string=''):
    if isinstance(pattern, str):
        pattern = compile(pattern)
    m = pattern.search(string, 0)
    while m:
        yield (m.lastgroup, m.group(m.lastgroup))
        m = pattern.search(string, m.end())




Raymond Hettinger



"Kragen Sitaker" <kragen at pobox.com> wrote in message
news:83n0z44t11.fsf at panacea.canonical.org...
> isinstance() considered harmful
> ===============================
  [snipped]
> \(b Every use of isinstance is a violation of this
> promise), large or small.  Whenever isinstance is
> used, control flow forks; one type of object goes
> down one code path, and other types of object go
> down the other --- even if they implement the same
> interface!

>
> Sometimes, of course, violating this promise is
> worth the payoffs --- isinstance, like goto, is
> not pure evil.  But it is a trap for new
> programmers.  Beware!  Don't use isinstance unless
> you know what you're doing.  It can make your code
> non-extensible and break it in strange ways down
> the line.
>
> Reasons for using isinstance
> ----------------------------
>
> Isinstance is used for a variety of reasons:
> \(ul
> - to determine whether an object supports a
>   particular interface
> - to keep people from writing possibly incorrect
>   programs, or to indicate possible bugs
>   (type-checking)
> - to test the implementation of an object to see
>   if certain optimizations can be applied
> - to write regression tests with knowledge of the
>   implementation
> - to define things that should really be methods
>   outside of the class they operate on
> - for no apparent reason whatsoever
> )






More information about the Python-list mailing list