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.