Understanding isinstance(), hasattr(), Classes, Attributes, and Objects in Python

When you start writing Python beyond simple scripts, you’ll run into classes, objects, isinstance(), and hasattr(). These are basic building blocks of object-oriented programming, and understanding them makes a big difference when reading other people’s code or working with libraries like boto3, Django, or Flask.

This post explains each concept with practical examples and expected output. If you need to set up Python on your machine, check How to Install and manage Python Versions on WSL Ubuntu.

Classes and Objects

A class is a template. An object is a specific thing built from that template. Think of it like this: “Server” is a class — it describes what a server has (IP, name, status). A particular server like web-01 is an object.

class Server:
    def __init__(self, name, ip, status="running"):
        self.name = name
        self.ip = ip
        self.status = status

    def restart(self):
        self.status = "restarting"
        print(f"{self.name} is restarting...")


# Create two objects from the Server class
web = Server("web-01", "10.0.1.10")
db = Server("db-01", "10.0.2.20", status="stopped")

print(web.name)      # web-01
print(web.ip)        # 10.0.1.10
print(db.status)     # stopped

web.restart()        # web-01 is restarting...

The __init__ method runs when you create a new object. self refers to the specific object being created — so self.name means “this particular server’s name.”

Attributes

Attributes are variables attached to an object. In the example above, name, ip, and status are all attributes. Each object has its own copy — changing web.status doesn’t affect db.status.

You can access them with dot notation and even add new attributes on the fly:

web.region = "ap-southeast-1"
print(web.region)    # ap-southeast-1
print(hasattr(db, "region"))  # False — only web has it

This is a Python thing that surprises people coming from other languages — you can add attributes to an individual object without changing the class definition. It’s not always a good idea, but it’s useful to know it’s possible.

isinstance() — Checking an Object’s Type

isinstance() checks if an object is an instance of a specific class. It returns True or False.

print(isinstance(web, Server))   # True
print(isinstance(web, str))      # False
print(isinstance("hello", str))  # True
print(isinstance(42, int))       # True

When Do You Actually Use This?

You’ll use isinstance() when a function receives different types of input and needs to handle each one differently. This comes up a lot when processing API responses or configuration data:

def process_config(value):
    if isinstance(value, dict):
        for k, v in value.items():
            print(f"  {k}: {v}")
    elif isinstance(value, list):
        for item in value:
            print(f"  - {item}")
    elif isinstance(value, str):
        print(f"  {value}")
    else:
        print(f"  (unknown type: {type(value).__name__})")


config = {
    "regions": ["us-east-1", "ap-southeast-1"],
    "environment": "production",
    "tags": {"team": "platform", "project": "infra"},
}

for key, val in config.items():
    print(f"{key}:")
    process_config(val)

Output:

regions:
  - us-east-1
  - ap-southeast-1
environment:
  production
tags:
  team: platform
  project: infra

Checking Multiple Types

You can pass a tuple of classes to check against multiple types at once:

print(isinstance(42, (int, float)))      # True
print(isinstance(3.14, (int, float)))    # True
print(isinstance("hello", (int, float))) # False

isinstance() Works with Inheritance

If a class inherits from another class, isinstance() returns True for both the child and parent class:

class Vehicle:
    pass

class Car(Vehicle):
    pass

my_car = Car()

print(isinstance(my_car, Car))      # True
print(isinstance(my_car, Vehicle))  # True

This is one reason isinstance() is preferred over type(obj) == SomeClass — it respects inheritance.

hasattr() — Checking If an Attribute Exists

hasattr(object, "attribute_name") returns True if the object has that attribute, False otherwise. The attribute name is passed as a string.

print(hasattr(web, "name"))    # True
print(hasattr(web, "ip"))      # True
print(hasattr(web, "color"))   # False

When Do You Actually Use This?

Mostly when you’re not sure what kind of object you’re dealing with. This is common when working with third-party APIs or processing data where some fields are optional:

class EC2Instance:
    def __init__(self, instance_id, public_ip=None):
        self.instance_id = instance_id
        if public_ip:
            self.public_ip = public_ip


instances = [
    EC2Instance("i-abc123", "203.0.113.10"),
    EC2Instance("i-def456"),  # no public IP (private subnet)
]

for inst in instances:
    ip = inst.public_ip if hasattr(inst, "public_ip") else "no public IP"
    print(f"{inst.instance_id}: {ip}")

Output:

i-abc123: 203.0.113.10
i-def456: no public IP

You could also use getattr() with a default value for the same thing:

ip = getattr(inst, "public_ip", "no public IP")

getattr() is often shorter if you just need the value with a fallback. Use hasattr() when you need to branch logic — like doing something completely different depending on whether the attribute exists.

Putting It All Together

Here’s a more complete example that combines classes, isinstance(), and hasattr(). Imagine you have a mixed list of infrastructure resources and you want to generate a simple report:

class EC2Instance:
    resource_type = "EC2"
    def __init__(self, instance_id, state):
        self.instance_id = instance_id
        self.state = state

class RDSInstance:
    resource_type = "RDS"
    def __init__(self, db_identifier, engine, multi_az=False):
        self.db_identifier = db_identifier
        self.engine = engine
        self.multi_az = multi_az

class S3Bucket:
    resource_type = "S3"
    def __init__(self, name):
        self.name = name


resources = [
    EC2Instance("i-abc123", "running"),
    EC2Instance("i-def456", "stopped"),
    RDSInstance("prod-db", "postgres", multi_az=True),
    S3Bucket("my-app-assets"),
]

for r in resources:
    # Get a display name — different resources use different attribute names
    if hasattr(r, "instance_id"):
        label = r.instance_id
    elif hasattr(r, "db_identifier"):
        label = r.db_identifier
    elif hasattr(r, "name"):
        label = r.name
    else:
        label = "(unknown)"

    # Build extra info based on type
    extra = ""
    if isinstance(r, EC2Instance):
        extra = f" [{r.state}]"
    elif isinstance(r, RDSInstance):
        ha = "multi-AZ" if r.multi_az else "single-AZ"
        extra = f" [{r.engine}, {ha}]"

    print(f"[{r.resource_type}] {label}{extra}")

Output:

[EC2] i-abc123 [running]
[EC2] i-def456 [stopped]
[RDS] prod-db [postgres, multi-AZ]
[S3] my-app-assets

This pattern — iterating through a mixed collection and handling each type differently — shows up often when writing CLI tools, reporting scripts, or Lambda functions that process different kinds of AWS resources.

isinstance() vs type()

Quick note on this since it comes up: type(obj) == SomeClass does a strict check — the object must be exactly that class, not a subclass. isinstance() checks the entire inheritance chain. In almost all cases, isinstance() is what you want.

class Animal:
    pass

class Dog(Animal):
    pass

buddy = Dog()

print(type(buddy) == Animal)       # False (strict match fails)
print(isinstance(buddy, Animal))   # True (inheritance respected)

Quick Reference

Function What It Does Returns
isinstance(obj, cls) Check if obj is an instance of cls (or its subclass) True / False
hasattr(obj, "attr") Check if obj has an attribute named “attr” True / False
getattr(obj, "attr", default) Get attribute value, return default if missing The attribute value or default
type(obj) Get the exact class of obj (no inheritance check) The class itself

Conclusion

Classes, objects, isinstance(), and hasattr() are fundamentals you’ll keep using as your Python code grows beyond single-file scripts. The main takeaway: use isinstance() to check what type something is, and hasattr() to check what it has before accessing it.

If you want to keep your Python code clean as your projects grow, check out How to Use Flake8 and Black for Python Code Quality and Style Consistency. And for structuring larger projects, How to Structure Your Python Projects for AWS Lambda, APIs, and CLI Tools is a practical follow-up.