Every Python value has a type, and the Python type function is your primary tool for checking that type at runtime. This skill is essential when validating function inputs, debugging unexpected behavior, and writing code that must handle multiple kinds of data gracefully. Python gives you two built-in tools for type introspection: the type function, which returns the exact type of any object as a class, and the isinstance function, which tests whether an object belongs to a given type or any type that inherits from it. While both functions answer the question "what kind of value is this," they answer it differently, and understanding when to use each one separates clean, maintainable code from brittle type checks that break when code evolves.
If you have read the overview of Python data types and understand the difference between immutable and mutable types, type checking is the next logical skill to add to your toolkit. Knowing what types your data has is essential before you attempt to convert data types or debug type-related errors in your programs. This article covers the type function, the isinstance function, the critical difference between them, and the practical patterns that experienced Python programmers use to validate types in real code.
The type() function: exact type identity
The type function accepts a single argument, any Python object, and returns that object's type as a class object. When you call type on an integer, you get back the class int. When you call it on a string, you get str. When you call it on a list, you get list. The return value is itself an object that you can compare with the double-equals operator or print for inspection. This makes type useful for quick interactive exploration when you are trying to understand what kind of data a variable holds.
print(type(42))
print(type("hello"))
print(type([1, 2, 3]))Running this code prints class int, class str, and class list, each with the angle-bracket notation that Python uses for type representations. The type function always returns the most specific type, which is one of the reasons that type-based checks can be too narrow for many practical situations. The bool type is particularly interesting because type returns class bool, not class int, even though bool is technically a subclass of int.
The most common pattern you will see with type is comparing its return value to a known type using the equality operator: type(x) == int checks whether x is exactly an integer. This pattern works correctly for simple built-in types like int, str, float, list, dict, tuple, and set. However, it fails in any situation involving inheritance. If you define a custom class that inherits from list, comparing type to list returns False even though your custom object behaves like a list in every meaningful way. For that reason, type equality checks are considered brittle and are rarely the right choice in application code that may evolve over time.
The isinstance() function: flexible type checking with inheritance
The isinstance function takes two arguments: an object and a type or tuple of types. It returns True if the object is an instance of the given type or of any class that inherits from that type. This means isinstance respects Python's class hierarchy, which makes it more flexible and more aligned with the way object-oriented code is designed to work. If you check isinstance against a parent class, all subclasses pass the check automatically.
The second argument to isinstance can be a tuple of multiple types. When you provide a tuple, isinstance returns True if the object matches any of the types in the tuple. This is the idiomatic way to check whether a value is numeric. Passing (int, float, complex) as the second argument catches all three numeric types in a single expression. The same pattern works for checking string-like types, collection types, or any family of related types that your function might accept.
def double(value):
if isinstance(value, (int, float)):
return value * 2
if isinstance(value, str):
return value + value
raise TypeError("Expected a number or a string")
print(double(21))
print(double("Hi"))This example demonstrates the isinstance-based validation pattern that appears in countless Python functions. The function checks for numeric types first, then for strings, and raises a descriptive TypeError for anything else. Using a tuple of types for the numeric check means the function works with integers, floats, and complex numbers without needing separate branches for each. If someone later passes a custom numeric type that inherits from float, isinstance handles it correctly without changes to the function.
When type() beats isinstance()
There are a small number of situations where an exact type match using type is preferable to the flexible isinstance check. The most common case is when you are writing a serialization or deserialization function that must reconstruct objects of the exact same type that was originally serialized. If you store an integer and a boolean value, isinstance would conflate the two because bool is a subclass of int, but type distinguishes them correctly. Framework code that introspects classes at runtime also tends to use type because it needs to know the exact class, not just whether the class belongs to a category.
Another legitimate use of type is in testing code that verifies an object's exact type for correctness. If a function is documented to return a list, not a subclass of list, a test that checks type equality provides a stronger guarantee than one that uses isinstance. In everyday application code, however, isinstance is almost always the better choice. It is more flexible, more aligned with Python's design philosophy, and less likely to produce false negatives when code is extended with subclasses.
Practical type-checking patterns
The most robust pattern for validating function inputs combines isinstance checks with descriptive error messages. When a function receives an argument of the wrong type, raising a TypeError with a message that explains what was expected makes debugging easier for anyone calling your function. The error message should name the expected types and, if helpful, include the actual type that was received. This is the pattern used by Python's own standard library functions, and adopting it makes your code feel native to the language.
Type checking with isinstance also pairs well with the concept of duck typing, which is the Python philosophy that an object's suitability is determined by what it can do rather than what type it is. Sometimes the best approach is not to check the type at all but to try the operation and catch the exception if it fails. For example, instead of checking whether a value is a number before multiplying it, you can attempt the multiplication and catch a TypeError if it fails. This approach, known as "easier to ask forgiveness than permission," is the most Pythonic pattern of all. Use isinstance when you need to dispatch to different behaviors based on type. Use try-except when you just need to attempt an operation and handle failure gracefully.
Rune AI
Key Insights
- type(obj) returns the exact type of obj as a class object.
- isinstance(obj, SomeType) returns True if obj is an instance of SomeType or any of its subclasses.
- isinstance(obj, (TypeA, TypeB)) checks against multiple types at once using a tuple.
- Prefer isinstance() over type() == for most type checks because it respects inheritance.
- Use type() only when you need an exact match and want to reject subclasses.
Frequently Asked Questions
What is the difference between type() and isinstance()?
Can I check multiple types at once with isinstance()?
When should I use type() instead of isinstance()?
Conclusion
The type() function tells you exactly what type an object is, while isinstance() tells you whether an object belongs to a type or any of its subtypes. For most practical purposes, isinstance() is the right tool because it respects Python's inheritance model and produces more flexible, maintainable code. Reserve type() for the rare cases where you need an exact type match and want to explicitly reject subclasses.
More in this topic
Create and Assign Variables in Python
Learn every way to create and assign variables in Python, from basic single assignment to expressions, type hints, and practical patterns beginners use every day.
Python Variable Naming Rules
Learn Python's variable naming rules, including allowed characters, reserved keywords, PEP 8 conventions, and how to choose names that make your code readable.
Python Variables Explained
Understand what Python variables are, how they store and reference data, and why Python's variable model is different from other programming languages.