Question

Python type() vs isinstance(): Exact Types and Subclasses

pythonoopinheritancetypes

Question

What is the difference between checking values with type() and with isinstance() in Python?

For example, how do these approaches differ?

# Legacy Python 2-style code
import types

if type(a) is types.DictType:
    do_something()

if type(b) in types.StringTypes:
    do_something_else()
# Class-based checks
if isinstance(a, dict):
    do_something()

if isinstance(b, str) or isinstance(b, unicode):
    do_something_else()

When should each approach be used, particularly when subclasses are involved?

Short Answer

type(value) tells you the value's exact runtime type, while isinstance(value, class_or_tuple) checks whether a value belongs to a class or any of its subclasses. In most application code, isinstance() is the better choice because it works naturally with inheritance. Exact type(...) is ... checks are appropriate only when an exact built-in type is genuinely required.

Concept

Python objects have a runtime type. You can inspect it with type():

value = {"status": "ok"}
print(type(value))  # <class 'dict'>

The important difference is in what you do with that result.

  • type(value) is dict asks: Is the object's type exactly dict?
  • isinstance(value, dict) asks: Is this object a dict, or a subclass of dict?

Inheritance is why this distinction matters. A class can extend another class and add behavior while still being usable wherever its parent class is expected. isinstance() respects that relationship.

class AuditDict(dict):
    pass

record = AuditDict()

print(type(record) is dict)       # False
print(isinstance(record, dict))   # True

Most code should accept valid subclasses rather than rejecting them merely because they are more specialized. This makes functions easier to reuse, test, and extend.

In modern Python 3, use built-in classes such as dict and str. The old Python 2 names types.DictType, types.StringTypes, and unicode are not the normal modern approach. Python 3 has one main text type: str.

Mental Model

Think of a class as a category on an identification card.

  • type(obj) is dict checks whether the card says exactly Dictionary.
  • isinstance(obj, dict) checks whether the object belongs to the broader Dictionary family.

A custom AuditDict is like a specialized kind of dictionary: it is still a dictionary, but its card has the more specific label AuditDict. An exact type() check rejects it; isinstance() accepts it.

Use exact checks only when you truly need the basic, unmodified item. Usually, accepting the whole family is more useful.

Take Quiz

Syntax and Examples

Use type() to retrieve an object's type:

name = "Ada"
print(type(name))          # <class 'str'>
print(type(name) is str)   # True

Use isinstance() to test one accepted type:

age = 42

if isinstance(age, int):
    print("Age is an integer")

Pass a tuple to accept any of several types:

value = "42"

if isinstance(value, (int, str)):
    print("The value is an integer or text")

This tuple form replaces repetitive or checks:

# Valid, but longer
if isinstance(value, int) or isinstance(value, str):
    print("Accepted")

Subclass example

class PremiumUser(dict):
    pass

user = PremiumUser(name="Ada")

print(type(user) is dict)      # False
print(isinstance(user, dict))  # True

PremiumUser has the exact type PremiumUser, not dict. However, because it inherits from dict, it is an instance of dict for isinstance() purposes.

Step by Step Execution

Consider this code:

class TaggedList(list):
    pass

items = TaggedList(["red", "blue"])

print(type(items))
print(type(items) is list)
print(isinstance(items, list))
print(isinstance(items, (tuple, list)))

Step by step:

  1. TaggedList is defined as a subclass of list.
  2. items is created. Its exact type is TaggedList.
  3. type(items) returns <class '__main__.TaggedList'> (the module portion may differ by environment).
  4. type(items) is list is False because TaggedList and list are different class objects.
  5. isinstance(items, list) is True because TaggedList inherits from list.
  6. isinstance(items, (tuple, list)) is also True: the tuple means “match any one of these types,” and list matches through inheritance.

Real World Use Cases

  • Input validation: Accept a string or path-like object before reading a file. When appropriate, prefer behavior-focused APIs such as os.fspath() rather than restricting callers to one exact class.
  • JSON-like data: Check whether a value is a dict before looking up named keys, or a list before iterating over expected entries.
  • Numeric data: Accept more than one numeric representation, such as int and float, when validating a user-entered measurement.
  • Framework extension: Frameworks often provide subclasses of standard types. isinstance() lets custom objects work with code that accepts their parent type.
  • Error handling: Verify that a caught or received object is an Exception subclass when building error-reporting utilities.
  • Testing: A test double may inherit from a production base class. isinstance() supports this substitution; exact type checks often make it harder.
Take Quiz

Real Codebase Usage

Developers commonly use isinstance() at boundaries where data enters a function, API, or parser.

Validate and return early

def format_tags(tags):
    if not isinstance(tags, list):
        raise TypeError("tags must be a list")

    return ", ".join(tags)

This guard clause makes the expected input explicit. If subclasses of list are valid, they are accepted too.

Accept several input types

def normalize_port(port):
    if not isinstance(port, (int, str)):
        raise TypeError("port must be an integer or string")

    return int(port)

Prefer behavior when the exact container does not matter

Often, checking a concrete type is unnecessarily strict. If you only need to iterate, accept any iterable:

def print_lines(lines):
    for line in lines:
        print(line)

This works with lists, tuples, generators, and many custom objects. Add an isinstance() check only when your logic specifically needs operations guaranteed by a particular type.

Rare exact-type check

def needs_plain_dictionary(value):
    if type(value) is not dict:
        raise TypeError("a plain dict is required")

This is intentionally restrictive. It can be justified when a subclass's overridden behavior would be unsafe or unsupported, but it should be a deliberate API decision.

Common Mistakes

Using type() when subclasses should work

class ConfigDict(dict):
    pass

config = ConfigDict()

# Too strict in most cases
if type(config) is dict:
    print("Valid")

The condition is false. Prefer:

if isinstance(config, dict):
    print("Valid")

Comparing types with == instead of is

# Works for many normal classes, but is not the idiomatic exact check
if type(value) == str:
    pass

If you intentionally compare the exact type object, use identity:

if type(value) is str:
    pass

Classes are objects, and is clearly communicates that you mean the same class object.

Passing a list instead of a tuple to isinstance()

# Broken
isinstance(value, [str, int])

isinstance() requires a type or a tuple of types:

isinstance(value, (str, int))

Using Python 2-only names in Python 3

# Not modern Python 3 code
isinstance(text, unicode)

Python 3 text is str:

isinstance(text, str)

Treating bool as unrelated to int

print(isinstance(True, int))  # True

In Python, bool is a subclass of int. If a field must be an integer but must not allow booleans, explicitly exclude them:

is_plain_integer = isinstance(value, int) and not isinstance(value, bool)

Checking a type when a direct operation is clearer

Sometimes the best approach is to perform the needed operation and handle a meaningful failure, or to design the function around a documented interface. Do not add type checks merely out of habit.

Comparisons

ApproachWhat it checksAccepts subclasses?Typical use
type(value)Returns the exact runtime class objectNot applicableInspection, debugging, logging
type(value) is dictExact class is dictNoRare cases requiring a plain built-in dict
isinstance(value, dict)Membership in dict's inheritance hierarchyYesNormal runtime validation
isinstance(value, (str, bytes))Membership in any listed hierarchyYesAccepting several valid input types
issubclass(MyClass, BaseClass)A relationship between two classesYesClass-level checks, not object checks

isinstance() versus issubclass()

class Animal:
    pass

class Dog(Animal):
    pass

pet = Dog()

print(isinstance(pet, Animal))  # True: `pet` is an object
print(issubclass(Dog, Animal))  # True: `Dog` is a class

Use isinstance() with an object. Use issubclass() with classes.

Take Quiz

Cheat Sheet

# Find the exact runtime type
actual_type = type(value)

# Exact-type check: rejects subclasses
if type(value) is dict:
    ...

# Recommended normal check: accepts subclasses
if isinstance(value, dict):
    ...

# Accept one of several types
if isinstance(value, (str, int, float)):
    ...
  • Prefer isinstance() for ordinary validation.
  • Use type(value) is SomeType only when an exact type is required.
  • Use a tuple, not a list, for multiple accepted types.
  • Modern Python 3 uses str; unicode is a Python 2 name.
  • bool is a subclass of int.
  • type() is also useful for debugging: print(type(value)).

FAQ

What is the main difference between type() and isinstance() in Python?

type(obj) returns the object's exact class. isinstance(obj, Class) returns True for that class and its subclasses.

Should I use type(x) is int or isinstance(x, int)?

Usually use isinstance(x, int). Use type(x) is int only if subclasses of int must be rejected.

Does isinstance() accept multiple types?

Yes. Pass a tuple:

isinstance(value, (str, bytes))

Why is isinstance(True, int) true?

Python defines bool as a subclass of int for historical and compatibility reasons. Exclude bool explicitly when necessary.

Is types.DictType available in Python 3?

No. That was Python 2-era code. In Python 3, use dict directly.

Is unicode available in Python 3?

No. Python 3 uses str for Unicode text. Binary data uses bytes.

Can isinstance() be customized?

In advanced class designs, metaclasses and abstract base classes can influence instance checks. For everyday classes and built-ins, it follows normal inheritance relationships.

Is checking types always necessary in Python?

No. Python often relies on an object having the needed behavior rather than requiring one concrete type. Validate types when it improves error messages, safety, or API clarity.

Take Quiz

Mini Project

Description

Build a small configuration validator that accepts a dictionary or a dictionary subclass. It demonstrates why isinstance() is generally preferable when an API should support specialized mapping objects while still validating required data.

Goal

Create a function that validates a configuration object and rejects invalid values with clear errors.

Requirements

Use isinstance() to accept dictionaries and dictionary subclasses.|Require a non-empty name string.|Require retries to be an integer but reject True and False.|Return a normalized dictionary containing name and retries.

Take Quiz

Keep learning

Related questions

View more