Python type vs isinstance: When to Use Each
python type vs isinstance: Learn the difference between type() and isinstance() in Python, including inheritance behavior, performance, and when to use each for robust...
In Python, the choice between type() and isinstance() affects how you validate types at runtime. The difference becomes critical when inheritance or duck typing is involved. This article explains python type vs isinstance in practical terms, with code examples and guidance for production code.
The Core Difference
type() returns the exact class of an object, while isinstance() checks whether an object is an instance of a given class or a subclass of it. This distinction matters whenever inheritance is involved. For example, type(instance) == SomeClass is True only when the object's class is exactly SomeClass. isinstance(instance, SomeClass) is True when the object is an instance of SomeClass or any of its subclasses.
How type() Works
type() is a built-in function that returns the type object of its argument. When called with one argument, it gives you the exact class. When called with three arguments, it creates a new class, but that usage is unrelated to type checking. For type checks, you typically compare the result with a class using == or is. Because type() returns the exact class, it does not consider inheritance.
class Animal: pass class Dog(Animal): pass d = Dog() print(type(d) == Dog) # True print(type(d) == Animal) # False
The second comparison is False even though Dog is a subclass of Animal. This behavior is often surprising to developers who expect a subclass to also match the base class. That expectation is reasonable in a language with polymorphism, but type() does not provide it.
How isinstance() Works
isinstance() takes an object and a class or a tuple of classes. It returns True if the object is an instance of any of the given classes or of a subclass of them. The check uses the object's type and walks up the inheritance chain.
class Animal: pass class Dog(Animal): pass d = Dog() print(isinstance(d, Dog)) # True print(isinstance(d, Animal)) # True
This makes isinstance() the natural choice when you need to accept subclasses or when you are working with an interface or abstract base class. It also supports multiple types in a single call, such as isinstance(value, (int, float)).
Inheritance and Subclass Behavior
The key difference becomes visible in a class hierarchy. Suppose you have a base class Shape and a subclass Circle. If you want to check whether an object is a Circle or any other shape, isinstance() is the only straightforward way. With type(), you would need to manually inspect the class hierarchy or use issubclass(), which is more verbose and error-prone.
class Shape: pass class Circle(Shape): pass c = Circle() print(type(c) is Circle) # True print(type(c) is Shape) # False print(isinstance(c, Shape)) # True
In a polymorphic system, isinstance() expresses the intent more clearly. It also handles cases where the object is an instance of a dynamically created class or a proxy that mimics a subclass.
Practical Usage: When to Use Each
Use type() when you need an exact match and you explicitly do not want to accept subclasses. This is rare but can be useful when you are comparing singletons or when you are implementing a strict type guard that should reject any derived class.
Use isinstance() for almost every other type check. It is the idiomatic way to validate input in functions, especially when you are accepting a base class or an interface. It also works with abstract base classes and protocol classes, which makes it more flexible in modern Python code.
def process_number(value): if not isinstance(value, (int, float)): raise TypeError("Expected a number") return value * 2
The tuple form of isinstance() is a concise way to accept multiple types without repeating the check.
Performance and Runtime Cost
Both functions are implemented in C and are fast, but there are subtle differences. isinstance() has to traverse the MRO (method resolution order) of the object's class to check all base classes. In practice, this traversal is short and the overhead is negligible for typical code. type() performs a direct comparison, which is slightly cheaper, but the difference is rarely the bottleneck in an application.
The more important performance consideration is what you do after the check. If you use type() and then manually handle subclasses, you add complexity and risk. If you use isinstance(), you get the correct behavior without extra code. Prematurely optimizing type checks is usually a mistake; correctness and maintainability matter more.
Edge Cases and Duck Typing
Python's philosophy encourages duck typing: if an object behaves like a duck, treat it as a duck. Type checks should be used sparingly, and when they are necessary, isinstance() aligns better with that philosophy because it can accept any object that implements the required interface, including through structural typing with typing.Protocol.
from typing import Protocol class Named(Protocol): name: str def greet(obj): if isinstance(obj, Named): return f"Hello, {obj.name}" raise TypeError("Expected a Named object")
Here, isinstance() works with a protocol class, while type() would require an exact class match. This makes isinstance() the better choice when you want to support duck typing without losing the ability to validate inputs.
Maintainability Considerations
Using isinstance() consistently makes your code easier to extend. If you introduce a new subclass later, existing checks continue to work without modification. With type(), you would need to update every exact comparison to include the new subclass. In a large codebase, this can lead to subtle bugs where a valid subclass is rejected.
When you are writing public APIs, prefer isinstance() with abstract base classes or protocols. This gives callers flexibility while still providing clear error messages. Reserve type() for internal checks where you have full control over the class hierarchy and you explicitly need to prevent subclassing.