isinstance() considered harmful

Alex Martelli aleax at aleax.it
Fri Jan 25 06:44:18 EST 2002


<kosh at aesaeion.com> wrote in message
news:mailman.1011915337.20073.python-list at python.org...
    ...
> Well try except is slower then if else and rendering pages to the screen
> does have a time limit to it so speed is a pretty importance factor. Also

I think performance issues must be kept in the proper perspective.
How many hundreds, or thousands, of times do you need to test, while
rendering each page to the screen?  try/except does have overhead when
the try block raises, but how often will that happen?

Here's a typical toy performance measurement:

import time

class myclass:
    def pleek(self): return 1

manyok =  9999*[myclass()]+[None]
manyno =  9999*[None]+[myclass()]
mixedup = 5000*[None, myclass()]

def withii(x):
    if isinstance(x, myclass): return x.pleek()
    else: return 0

def withha(x):
    if hasattr(x, 'pleek'): return x.pleek()
    else: return 0

def withte(x):
    try: return x.pleek()
    except AttributeError: return 0

def withat(x):
    try: method = x.pleek
    except AttributeError: return 0
    else: return method()

funs = withii, withha, withte, withat
data = manyok, manyno, mixedup

def timeit(afun, somedata):
    start = time.clock()
    for item in somedata: afun(item)
    stend = time.clock()
    return stend-start

for afun in funs:
    print "%6s"%(afun.__name__[4:]),
print
for somedata in data:
    for afun in funs:
        print "%6.2f"%timeit(afun, somedata),
    print


Here are two runs on a slow, old machine:

C:\Python22>python -O tp.py
    ii     ha     te     at
  0.10   0.11   0.07   0.07
  0.12   0.21   0.86   0.86
  0.12   0.17   0.48   0.48

C:\Python22>python -O tp.py
    ii     ha     te     at
  0.10   0.11   0.07   0.07
  0.13   0.21   0.86   0.85
  0.13   0.17   0.47   0.49

the 'extra care' of withat as opposed to the rough withte
has no measurable performance cost so we can forget about
withte and only think about withat.  If the test always
succeeds (the try block never raises), the AT approach
is faster by 30 to 40 msec over 10,000 cases, i.e. about
3 or 4 microseconds per case on this slow box.  If, at
the other extreme, the test never succeeds, AT is slower
by over 700 msec over 10,000 cases, over 70 microseconds
per case (hasattr is also slower then by 80 or 90 msec
than isinstance, 8 or 9 microseconds per case).  In the
control-case of 50/50 successes and failures, as expected
this gives about 36 microseconds per case.

How many tests you have to do while rendering each single
page to the screen, and of many of them will succeed,
depends of course on your specific application.  In many
cases, the 3/4 microseconds per case will be irrelevant
even if the test almost always succeeds, and in most of
those even the 10-times-higher overhead for tests that
almost always fail will be imperceptible -- but if the
number of tests is truly huge this need not be the case.

However, this should be considered, if ever, only during
the optimization phase, the very last one in the three
steps approach to development:
    1. make it work,
    2. make it good,
    3. make it fast
(wish I knew who to credit for this pithy expression...:-).

In phases 1 and 2, you can typically afford to pay even 70
microseconds per test (unless you have millions of them,
so that this slows your development down substantially!).
In phase 3, you may or may not be able to afford that, so
it IS possible (not likely, but possible) that you'll need
to specialcase some tests -- even then, though, it may be
worth coding them as:

def withboth(x):
    if isinstance(x, myclass): return x.pleek()
    else:
        try: method = x.pleek
        except AttributeError: return 0
        else: return method()

i.e., specialcase the frequent case for speed, but still
let the user of this framework use some other pleek-supplied
argument, not just instances of myclass.  If almost all
cases of x satisfy isinstance, this has little cost and
does not curtail client-code programmers' ability to
tweak a few cases e.g. by encapsulation and delegation.


Alex






More information about the Python-list mailing list