isinstance() necessary and helpful, sometimes

Kragen Sitaker kragen at pobox.com
Fri Jan 25 06:10:41 EST 2002


"Raymond Hettinger" <othello at javanet.com> writes:
> 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.

Yeah, I've done this too.  My __coerce__ example was kind of treading
on thin ice for this reason --- I think there are lots of times you
need multiple dispatch for optimal numeric coercion, although I don't
think they generally show up with Python's native numeric types.

> 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).

Yep, if you're writing things Lispishly you need isinstance().  If the
code that takes an argument that could either be atomic or complex has
only one such argument, you could make it a method of the atomic or
complex data, which is what I often do --- but since Python doesn't
let you add new methods to built-in types the way CLOS does (I think?
I don't know CLOS) that only works if the items *aren't* built-in
lists or strings, and your code is often a lot simpler if they are.

> 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).

(side note: x and y or z is different from x ? y : z if y is false!)

My usual Python solution for this uses hasattr(), and in fact usually
tries to call a method on the object to be wrapped --- the wrapper
objects simply return themselves with this method, while other objects
will tend not to have this method, so I fall back to wrapping them.

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

Yeah, I don't have a better solution for that either.

> 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)).

I assume this is to verify that LU factorization did successfully
produce a lower-triangular matrix and the matrix factory function
recognized this?  That sounds like an excellent reason for
isinstance() to me --- closely akin to regression testing.

> Cases Where hasattr() is Better:
> ------------------------------------
> ...
> 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]

I'd just say gens = [ iter(s) for s in sequences ] --- iter() uses the
same strategy I described above for wrappers, even though an iterator
isn't a Decorator.

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

Yeah, in cases like this, try/except is kind of a pain.  When I want
to find out something specific, I try to put as little code in the try
block as possible, to minimize the chance of such false hits.  But I
always worry.

> def tokenize(pattern, string=''):
>     if isinstance(pattern, str):
>         pattern = compile(pattern)

Well, re.compile will return the original RE object if you pass it an
RE object instead of a string, precisely for cases like this.  But it
takes a while to do so --- about 23 microseconds on my laptop,
compared to about 1.6 microseconds to do a null function call.  So
leaving out the if isinstance() check leaves you with more-correct
code (in this case, it accepts both Unicode and 8-bit strings) that is
simpler, but runs slightly slower.  This sort of illustrates the point
of the original post.  :)

Of course, re.compile is doing something much like isinstance --- in
Python 2.1, it's actually checking type(pattern) not in
sre_compile.STRING_TYPES.

Thank you for a very interesting and thought-provoking post.




More information about the Python-list mailing list