PYTHON / VARIABLES AND DATA TYPES
Checking types with type() and isinstance()
Use type() to get an object's exact class and isinstance() to test class membership that respects inheritance, and know when each is right.
What you will learn
- Read type(x) as returning a class object, so compare it with `is`, never a string
- Use isinstance(x, C) when subclass instances should also be accepted
- Pass a tuple of classes to isinstance() to accept several types in one check
- Remember bool subclasses int, so isinstance(True, int) is True
Understanding Checking types with type() and isinstance()
Every Python object carries a reference to the class that made it, and type(x) hands you that class object back. It is not a name or a description: type(42) evaluates to the int class itself, which is why printing it shows <class 'int'> rather than the bare word int. Because the result is a single, unique object per class, the natural way to test it is identity: type(x) is int. Comparing against the string "int" can never be true, since a class object and a str are different objects entirely.
isinstance(x, C) asks a different question: is x an instance of C or of anything that inherits from C? That distinction matters the moment inheritance appears. A Dog instance is not the Animal class, so type(d) is Animal is False, but isinstance(d, Animal) is True because Dog was built on top of Animal and therefore supports everything Animal promises. isinstance also accepts a tuple of classes, so isinstance(x, (int, float)) is one check for two acceptable types, and it cooperates with abstract base classes that judge membership by which methods a class defines.
The practical rule follows from the two questions. Ask isinstance when you care about what an object can do, because subclasses can do it too, and reserve type(x) is C for the rare case where you need the exact class and want subclasses excluded. The classic trap sits at the boundary: bool inherits from int, so isinstance(True, int) is True while type(True) is int is False. Neither answer is wrong; they answer different questions, and picking the wrong one is where the bugs come from.
value = 42
print(type(value))
print(type(value) is int)
print(isinstance(value, int))
flag = True
print(type(flag) is int)
print(isinstance(flag, int))
print(isinstance(3.5, (int, float)))type() reports an object's exact class while isinstance() tests class membership including subclasses, so the choice between them is a choice about whether inheritance counts.
Worked examples
Inheritance changes the answer
Shows that an instance of a subclass fails an exact type check but passes isinstance.
class Animal:
pass
class Dog(Animal):
pass
d = Dog()
print(type(d).__name__)
print(type(d) is Animal)
print(isinstance(d, Animal))
print(isinstance(d, (str, Animal)))
print(issubclass(Dog, Animal))Example explained
Line 1type(d).__name__ gives the plain class name, useful in messages where <class '...'> is noise.
Line 2type(d) is Animal is False because d was created from Dog, and Dog and Animal are two distinct class objects.
Line 3isinstance(d, Animal) is True because the check walks up the inheritance chain from Dog.
Line 4issubclass compares two classes rather than an object and a class, which is the check to use when you hold a class, not an instance.
Branching on type in a function
Uses isinstance with a tuple to handle several input shapes and reports the rejected type by name.
def total_length(item):
if isinstance(item, str):
return len(item)
if isinstance(item, (list, tuple)):
return sum(total_length(x) for x in item)
raise TypeError(f"unsupported type: {type(item).__name__}")
print(total_length("hello"))
print(total_length(["ab", "cde", ("f", "gh")]))
try:
total_length(7)
except TypeError as e:
print(e)Example explained
Line 1The str check comes first because a string is also iterable, and without it the recursion would split "hello" into characters.
Line 2isinstance(item, (list, tuple)) accepts both container types with one call instead of two chained checks.
Line 3type(item).__name__ builds a readable error message: 'int' instead of "<class 'int'>".
Line 4The nested tuple contributes 1 + 2, so "ab" and "cde" plus that 3 gives 8.
isinstance with abstract base classes
Demonstrates that isinstance can answer capability questions that an exact type check cannot.
from collections.abc import Iterable
print(isinstance([1, 2], Iterable))
print(isinstance("ab", Iterable))
print(isinstance(5, Iterable))
print(type("ab") is Iterable)
class Countdown:
def __iter__(self):
yield 1
print(isinstance(Countdown(), Iterable))Example explained
Line 1Lists and strings both report True because both define __iter__, which is what Iterable checks for.
Line 2An int is not iterable, so the check is False, which is exactly the guard you want before a for loop.
Line 3type("ab") is Iterable is False and always will be: str is the exact class, Iterable is an abstract base class.
Line 4Countdown never mentions Iterable in its definition, yet isinstance says True because the ABC recognises the __iter__ method.
Important notes
isinstance() raises TypeError if the second argument is not a class or a tuple of classes, so isinstance(x, "int") is an error, not False.
Called with three arguments, type(name, bases, dict) creates a new class instead of inspecting one; the one-argument form is the inspection tool.
Common mistakes
Writing `if type(x) == "int"`: the comparison is between a class object and a string, so it is always False and the branch silently never runs.
Assuming isinstance(x, int) filters out booleans: True passes, so summing a list that contains True quietly adds 1 to your total.
Using type(x) is SomeClass in a function that should accept subclasses, which rejects perfectly valid objects and breaks inheritance-based designs.
Try it yourself
Change, predict, then run
Write describe(value) that returns "integer", "decimal", "text", or "other" using isinstance, then call it with 5, 2.5, "hi", True, and None and explain the result you get for True.
Open the Python workspaceCheck your understanding
A function guards its input with `if type(n) is not int: raise TypeError`. A caller passes True. What happens?
- It raises TypeError, because type(True) is bool and bool is a different class object from int, even though bool inherits from int
- It accepts True, because Python stores True as the integer 1 internally
- It accepts True, because type() follows the inheritance chain up to int
- It raises TypeError, because booleans have no type and type(True) returns None
Show answer
type() reports the exact class, which for True is bool, and bool is not the same object as int, so the identity test fails and the error is raised. The tempting option is the one about inheritance: bool really is a subclass of int, but that only matters to isinstance(True, int), which would return True; type() ignores inheritance entirely.