On Tue, 28 Aug 2007, Michael wrote:
> I now have several methods in my arsenal and they all look quite simple,
> now that I know. Just wondering, when would you use isInstance()?
Well, as this thread has shown, I'm no expert, but let me take a stab on
it. If nothing else, we'll all learn something as the experts come in to
correct me!
We have three (or four, depending on how you count) possible approaches:
1) Use duck-typing. Don't worry about the type of operand you're being
fed. You don't really care, for example, whether it is actually a file,
or an int, or a float, or a string, or whatever, as long as it acts like
one for the purpose for which you're using it. For example, if you're
writing an increment function:
def incr(n):
"returns an incremented value of n"
return n + 1
It will work whether you get an int or a float. It won't work with a file
or a string, but rather than spend your time checking that, just let
Python generate the exception. In the mean time, you code will work with
ints, floats, complex numbers, or anything that subclassed from them, with
no special work from you.
This is called "duck-typing" from the phrase, "if it looks like a duck,
walks like a duck and quacks like a duck, it's a duck." If it looks
enough like a number that it lets itself have one added to it, I'm calling
it a number.
1A) a variation of option 1 (which is why I said "depending on how you
count" above): duck-typing with error recovery.
def incr(n):
"returns an incremented value of n, or None if n is not incrementable"
try:
return n + 1
except:
return None
This will try to return a value equal to N + 1, but if n does not quack
like a duck, it just returns None (and it's up to the caller to deal with
it).
Options 2 & 3 are non-duck-typing approaches, usually called
Look-Before-You-Leap (LBYL), where the code is checking to see if it was
passed the right type, and behaving accordingly. These are:
2) Using type() to obtain the type of the operand, and making a decision
based on the type;
3) using isinstance() to see if the operand is an instance of a particular
type, and then making a decision based on whether or not it is.
These are subtly different, because of subclassing. An object has only
one type -- the type of its class. But it is an instance, not only of its
class, but of that class's superclass(es), if any; and the
superclass(es)'s superclass(s), if any, etc. all the way back to the
"object" class.
Here's an example.
I'm going to create a mutant list class that subclasses list. It produces
objects that are the same as an ordinary list with one difference: if the
list is sorted, the contents are sorted without regard to case; that is,
both "B" and "b" sort after both "A" and "a", and before "C" and "c".
Here's the class definition:
>>> class caseindependentlist(list):
... def __init__(self, *args):
... list.__init__(self, *args)
... def sort(self):
... return list.sort(self, key=str.upper)
It subclasses list, and overrides the usual sort method with a replacement
method that sorts based on what the value would be if converted to all
upper-case (which means the sort is case-independent).
Let's create a plain old ordinary list and a mutant caseindependentlist to
see how they compare. (I'm using a long form of the list(0 constructor
just to be consistent here)
>>> ordinary_list = list(["Zebra", "alligator", "Duck", "penguin"])
>>> mutant_list = caseindependentlist(["Zebra", "alligator", "Duck", "penguin"])
Let's look at each before and after sorting to show how they work...
>>> ordinary_list
['Zebra', 'alligator', 'Duck', 'penguin']
>>> ordinary_list.sort()
>>> ordinary_list
['Duck', 'Zebra', 'alligator', 'penguin']
That worked as expected, but the upper-case ones are sorted first.
But...
>>> mutant_list
['Zebra', 'alligator', 'Duck', 'penguin']
>>> mutant_list.sort()
>>> mutant_list
['alligator', 'Duck', 'penguin', 'Zebra']
Okay, that works as expected.
Now here's the thing: mutant_list, for almost every purpose, is just a
list. You can do anything with it that you can do with a plain old list.
the only thing different about it is that it sorts without regard to case.
But mutant_list and ordinary_list have differing types:
>>> type(ordinary_list)
<type 'list'>
>>> type(mutant_list)
<class '__main__.caseindependentlist'>
>>>
If you were to write a program that checked "to make sure" it was being
passed a list, and were checking with type(), it would wrongly reject
mutant_list.
isinstance does a better job here. isinstance doesn't care whether the
object in question is a list directly (as in the case of ordinary_list) or
indirectly (as in the case of mutant_list):
>>> isinstance(ordinary_list, list)
True
>>> isinstance(mutant_list, list)
True
So, when to use what?
I would say:
1) where possible, use duck-typing, either of the type 1 or type 1A
variety. That's the easiest way to have your program just work if it's
possible to work.
2) if you must expressly check type, use isinstance(). It doesn't care
whether the object is directly or indirectly the instance you're looking
for.
3) I only use type() interactively or experimentally, when trying to learn
about what I'm getting passed at any particular time.
In a program I'm writing now, I would like to use duck-typing, but I don't
think it works well. In my particular case, to simplify it a bit, I'm
writing a method that can either take a string that contains SQL
statements; or a list or tuple that contains strings of SQL statements.
Duck-typing doesn't work too well here, because a string of characters
looks an awful lot like a list or tuple of one-character strings for most
duck-typing tests. I'd have to put together a weird duck-typing approach
that would ultimately be a type-checking approach disguised as
duck-typing; so I opted to just be up-front about it and check the type
(using isinstance, of course).
_______________________________________________
Tutor maillist - [email protected]
http://mail.python.org/mailman/listinfo/tutor