What’s the canonical way to check for type in Python?

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 CaseCanonical Approach
Basic type checksisinstance(obj, TargetType)
Multiple allowed typesisinstance(obj, (TypeA, TypeB))
Interface complianceABCs (Iterable, Sized, etc.)
Inheritance checksisinstance() (not type())
None checksx 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()?

Featureisinstance()type()
Inheritance-aware✅ (checks subclass)❌ (exact type only)
Multiple Types✅ (via tuples)❌ (single type)
Duck TypingUse 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")  ✅

Leave a Reply

Your email address will not be published. Required fields are marked *