[Tutor] isinstance() evil?

Gonçalo Rodrigues op73418 at mail.telepac.pt
Tue Dec 2 09:31:03 EST 2003


On Mon, 1 Dec 2003 22:59:13 -0800 (PST), you wrote:

>I've read a couple aricle here and there that isinstance() is generally a 
>bad idea.
>
>I have a question on that.
>
>I'm writing a small method used for debugging programs using a particular 
>class I wrote.  I want it to accept either an instance of the class, or a 
>list of instances (or, for that matter, a list of list of instances, etc., 
>if that's really what you want to do).
>
>I use isinstance() for this; it seems very straightforward to do this 
>recursively:
>
>    Dump(thing):
>
>        if isinstance(thing, list):
>            for element in thing:
>                Dump(element)
>            return
>
>        [rest of Dump, for a one-element invocation, follows]
>
>Is there a preferred way to do something like this?

IMHO there are two different answers to your question. Let me start by
what you did not ask and answer that I think you are designing it
wrongly: a thing and a sequence of things are two very different
things (no pun intended) and should not be conflated. In terms of
design, what this means is simply:

1. Make Dump a method of thing(s).

2. Have special purpose code to deal with a "sequence of things.".
This is as simple as

for thing in things:
    thing.Dump()

where things is a "sequence" (a list, a tuple, a special-purpose
iterable, etc.) of thing objects.

Now, to your real question. Think of the question you are asking when
using isinstance.

    if isinstance(thing, list):
        <etc.>

As the code in the if block shows the *only* thing you are interestred
in is to know if thing is a sequence of sorts. You really do not care
if it's a list. A tuple is a reasonable sequence also. So why restrict
the usability of your code needlessly? Just go ahead and *assume* that
the object is a sequence of things and then deal with any exceptions
that may be raised:

try:
    things = iter(things)
except TypeError:
    raise TypeError("Object not iterable", things)
for thing in things:
    try:
        dump = thing.Dump
    except AttributeError:
        raise AttributeError("Object not a thing", thing)
    dump()

We first test if things is a "sequence" of sorts. In this case, we
only want to iterate over it so we just ask: are you, things,
iterable? This means exactly

iter(things)

To be explicit, I wrapped it in a try, except block, although it is
useless because we just reraise the exception.

Then for each thing in things, we ask first: do you have a Dump
attribute? We do this by just grabbing the attribute as

thing.Dump

once again I've wrapped it in a try, except block for the sake of
being explicit. Once again, it is useless in fact, since nothing more
is done than raise an AttributeError exception.

Once this "test" is passed we just go along and call dump =
thing.Dump. It can also fail here, e.g.thing.Dump is not callable
but... you get the point.

One can *improve* the code a little bit. As it is, the first
thing.Dump may succeed but the second (or the third, or...) can fail.
We can make sure that *all* succed by the following

try:
    things = iter(things)
except TypeError:
    raise TypeError("Object not iterable", things)
try:
    dumps = [thing.Dump for thing in things]
except AttributeError:
    raise AttributeError("Object not a thing")
for dump in dumps:
    dump()

I'll leave you to ponder on this one :-)

With my best regards,
G. Rodrigues



More information about the Tutor mailing list