Wednesday, 27 July 2011

Checking Types In Python.

Checking Types in Python

Need to know the type of an object? Let me make a brief argument: No, you don't. Just use the object as if it was whatever you expect it to be, and handle any errors that result.
On the other hand, type-checking is convenient, easy to implement, and can save your life when debugging. Sometimes there isn't an obvious other way to attack a problem. (Look for one first though. Really.)
You have a few choices on how to type-check: the built-in functions type and isinstance and the instance property __class__. To compare the object to known classes, you can compare to the class directly or import the 'types' module (safe to do in your global scope) to get access to some types that don't provide user-accessible classes (like functions).
Before we start, note that Python has two types of classes: 'new-style' classes which inherit from the object class, and 'old-style' classes which don't. Most (all?) native Python types are now new-style classes, but classes you've created probably aren't.
Let's try some experiments, trying to print out the name of a class, and then comparing the type to a known class. Say we have the following classes and instances defined:
# 's
 1from types import *
 3# Instances of new-style classes
 4class NewClass(object): pass
 6new_class_instance = NewClass()
 8def function(): pass
10string = 'This is a string!'
12# Instances of old-style classes
13class OldClass: pass
15old_class_instance = OldClass()
Let's print out the name of the class using type and __class__. There's no way to do this with isinstance.:
# 's
 1print type(new_class_instance)
 2print new_class_instance.__class__
 3# both print "<class '__main__.new_class'>"
 5print type(function)
 6print function.__class__
 7# both print "<type 'function'>"
 9print type(string)
10print string.__class__
11# both print "<type 'str'>"
13print type(old_class)
14# prints "<type 'instance'>"
16print old_class.__class__
17# prints something like "class __main__.OldClass at 0x00F7F"
18# Note that this is different than what was printed by type()
It looks like type doesn't work for old-style classes. Now let's try checking against a known class or type, using type, isinstance, and __class__:
# 's
 1type(new_class_instance) == NewClass
 2isinstance(new_class_instance, NewClass)
 3new_class_instance.__class__ == NewClass
 4# All return True
 6type(function) == FunctionType # from 'types' module
 7isinstance(function, FunctionType)
 8function.__class__ == FunctionType
 9# All return True
11type(string) == str # we could also use StringType from 'types' module
12isinstance(string, str)
13string.__class__ == str
14# All return True
16type(old_class_instance) == OldClass
17# Returns False, even though old_class_instance is an instance of OldClass.
18# 'type' just can't understand these.
20isinstance(old_class_instance, OldClass)
21old_class_instance.__class__ == OldClass
22# Both return True as expected
Apparently, type is completely useless for both type printing and type comparisons, as it can't understand old-style classes while __class__ can.
__class__ is messy, but is the only method that both works for both types of classes and will print out the name of the class if desired. It is the best choice for debugging, when you have no idea what type of object you're looking at.
isinstance also works on all objects. It also has another couple of perks: it actually checks to see if your object is an instance of a class or subclass of the class you pass it. Generally if you're type-checking you're interested in the existence of a behavior or method, and all subclasses of the target class will probably have it. So, isinstance is more accurate. Additionally, you can pass it a tuple of classes, and it will check against all of them. These perks make it the best choice for legitimate type-checking in a real program.
Here's a summary of what we found: