Here’s a detailed guide with multiple examples demonstrating the canonical Pythonic approach to type checking using isinstance()
and related patterns:
1. Basic Type Checks
Check for common types:
value = 42
print(isinstance(value, int)) # True ✅
text = "Hello"
print(isinstance(text, str)) # True ✅
data = [1, 2, 3]
print(isinstance(data, list)) # True ✅
2. Check Against Multiple Types
Use a tuple of allowed types:
def process(value):
if isinstance(value, (int, float)):
return value * 2
else:
raise TypeError("Expected int/float")
process(5) # 10 ✅
process(3.14) # 6.28 ✅
process("5") # ❌ TypeError
3. Inheritance Handling
Subclass-aware check:
class MyList(list): pass # Inherits from list
custom_list = MyList([1, 2, 3])
print(isinstance(custom_list, list)) # True ✅ (recognizes parent class)
print(type(custom_list) is list) # False ❌ (exact type is MyList)
4. Abstract Base Classes (ABCs)
Check for interface compliance (duck typing):
from collections.abc import Iterable, Sized
def is_iterable(obj):
return isinstance(obj, Iterable) # True for lists, tuples, etc.
print(is_iterable([1, 2])) # True ✅
print(is_iterable(123)) # False ✅
def has_length(obj):
return isinstance(obj, Sized) # True if obj has __len__
print(has_length({"a": 1})) # True ✅ (dicts are Sized)
print(has_length(3.14)) # False ✅
5. User-Defined Classes
class Animal: pass
class Dog(Animal): pass
buddy = Dog()
print(isinstance(buddy, Animal)) # True ✅ (inheritance-aware)
print(isinstance(buddy, Dog)) # True ✅
6. Special Cases
Booleans vs. integers:
print(isinstance(True, int)) # True ✅ (bool is subclass of int)
print(isinstance(True, bool)) # True ✅
# To explicitly exclude booleans:
def strict_int_check(x):
return isinstance(x, int) and type(x) is int # ❌ False for True/False
None checks:
x = None
print(x is None) # True ✅ (preferred for None)
print(isinstance(x, type(None))) # True ✅ (equivalent but less common)
7. When to Avoid Type Checking
Prefer duck typing where possible:
# Instead of:
def add(a, b):
if isinstance(a, (int, float)) and isinstance(b, (int, float)):
return a + b
else:
raise TypeError
# Consider:
def add(a, b):
try:
return a + b
except TypeError:
raise TypeError("Unsupported operand types")
8. Edge Case: Metaclasses
class Meta(type): pass
class MyClass(metaclass=Meta): pass
obj = MyClass()
print(isinstance(obj, MyClass)) # True ✅
print(type(obj) is MyClass) # True ✅
Key Takeaways
Use Case | Canonical Approach |
---|---|
Basic type checks | isinstance(obj, TargetType) |
Multiple allowed types | isinstance(obj, (TypeA, TypeB)) |
Interface compliance | ABCs (Iterable , Sized , etc.) |
Inheritance checks | isinstance() (not type() ) |
None checks | x is None (most idiomatic) |
By using isinstance()
and ABCs, you write Pythonic code that respects inheritance and interfaces while avoiding rigid type coupling.
Why isinstance()
Over type()
?
Feature | isinstance() | type() |
---|---|---|
Inheritance-aware | ✅ (checks subclass) | ❌ (exact type only) |
Multiple Types | ✅ (via tuples) | ❌ (single type) |
Duck Typing | Use with ABCs* | Not recommended |
*Use Abstract Base Classes (ABCs) (e.g., collections.abc.Iterable
) for interface checks.
When to Use type()
Rarely. Only if you need exact type equivalence (ignoring inheritance):
x = 5.0
if type(x) is float:
print("Exact float type") ✅