Classes & Objects
Learn how to define classes, create objects, and understand the difference between instance and class attributes in Python.
Introduction
In the last lesson, you learned what object-oriented programming is conceptually. Now it is time to actually write classes. A class is the Python keyword and syntax for defining a blueprint, and an object (also called an instance) is a specific thing built from that blueprint.
This lesson covers defining classes with the class keyword, understanding self, creating objects, and the important difference between instance attributes and class attributes.
- How to define a class using the class keyword.
- What self means and why every method needs it.
- How to create objects (instances) from a class.
- The difference between instance attributes and class attributes.
- A shared mutable default gotcha to avoid with class attributes.
- How to add instance methods and a __str__ method for clean printing.
Defining a Class
A class is defined using the class keyword, followed by a name written in PascalCase (each word capitalized), a colon, and an indented block containing its attributes and methods.
class Dog:
"""A simple class representing a dog."""
pass
print(Dog)
print(type(Dog))<class '__main__.Dog'>
<class 'type'>The pass keyword is used here as a placeholder since the class body is empty. On its own, a class is just a blueprint — it does not do anything until you create objects from it.
Understanding self
Inside a class, self refers to the specific object that a method is being called on. It is always the first parameter of every instance method, though Python passes it automatically — you never type it yourself when calling the method.
class Dog:
def bark(self):
print("Woof! This is", self)
my_dog = Dog()
my_dog.bark()Woof! This is <__main__.Dog object at 0x000001A2B3C4D5E6>When you call my_dog.bark(), Python automatically passes my_dog as self behind the scenes. That memory address in the output confirms self really is the specific Dog object you created.
Creating Objects (Instances)
To create an object from a class, call the class name like a function. This process is called instantiation, and the resulting object is called an instance of that class.
class Dog:
pass
dog1 = Dog()
dog2 = Dog()
print(type(dog1))
print(dog1 == dog2)<class '__main__.Dog'>
FalseEven though dog1 and dog2 come from the exact same class, they are two separate, independent objects in memory, which is why comparing them with == returns False by default.
Instance Attributes
Instance attributes are variables that belong to one specific object. They are usually set inside the __init__ method (Python's constructor, covered fully in the next lesson) using self.attribute_name = value.
class Dog:
def __init__(self, name, breed):
self.name = name
self.breed = breed
dog1 = Dog("Rex", "Labrador")
dog2 = Dog("Bella", "Poodle")
print(dog1.name, dog1.breed)
print(dog2.name, dog2.breed)Rex Labrador
Bella PoodleEach object keeps its own separate copy of name and breed. Changing dog1.name has no effect whatsoever on dog2.name.
Class Attributes
A class attribute is defined directly inside the class body, not inside a method. It is shared by every object created from that class, rather than belonging to just one object.
class Dog:
species = "Canis familiaris" # class attribute, shared by all dogs
def __init__(self, name):
self.name = name # instance attribute, unique per dog
dog1 = Dog("Rex")
dog2 = Dog("Bella")
print(dog1.species, dog2.species)
print(Dog.species)Canis familiaris Canis familiaris
Canis familiarisBoth dog1 and dog2 share the exact same species value, because it belongs to the class itself, not to any individual dog. Instance attributes like name, however, are unique to each object.
The Shared Mutable Default Gotcha
A common and dangerous mistake is using a mutable class attribute, like a list or dictionary, expecting each object to get its own copy. Since class attributes are shared, modifying that list through one object silently affects every other object too.
class Dog:
tricks = [] # DANGEROUS: shared by every Dog object
def __init__(self, name):
self.name = name
def add_trick(self, trick):
self.tricks.append(trick)
dog1 = Dog("Rex")
dog2 = Dog("Bella")
dog1.add_trick("Sit")
print(dog1.tricks)
print(dog2.tricks) # Bella "knows" Rex's trick too!['Sit']
['Sit']Bella never learned "Sit", yet it shows up in dog2.tricks because both objects were sharing the exact same list object in memory. The fix is to create the list fresh inside __init__ instead.
class Dog:
def __init__(self, name):
self.name = name
self.tricks = [] # each dog gets its OWN list
def add_trick(self, trick):
self.tricks.append(trick)
dog1 = Dog("Rex")
dog2 = Dog("Bella")
dog1.add_trick("Sit")
print(dog1.tricks)
print(dog2.tricks)['Sit']
[]Never use a mutable value like a list, dictionary, or set as a class attribute if you want each object to have its own independent copy. Create mutable attributes inside __init__ using self instead.
Instance Methods
An instance method is a function defined inside a class that operates on a specific object's data, accessed through self. You have already seen a few above — here is a slightly fuller example.
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def describe(self):
print(f"{self.name} is {self.age} years old.")
def have_birthday(self):
self.age += 1
print(f"Happy birthday, {self.name}! Now {self.age}.")
rex = Dog("Rex", 3)
rex.describe()
rex.have_birthday()
rex.describe()Rex is 3 years old.
Happy birthday, Rex! Now 4.
Rex is 4 years old.Creating Multiple Objects from One Class
The real power of a class is that you can create as many objects from it as you need, and each one independently tracks its own state.
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def describe(self):
print(f"{self.name} is {self.age} years old.")
dogs = [Dog("Rex", 3), Dog("Bella", 5), Dog("Max", 1)]
for dog in dogs:
dog.describe()Rex is 3 years old.
Bella is 5 years old.
Max is 1 years old.The __str__ Method
By default, printing an object shows an unhelpful memory address. Defining a __str__ method lets you control exactly what gets shown when the object is passed to print() or str(). This is your first look at a dunder ("double underscore") method — a full lesson on these is coming later in the course.
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"Dog(name={self.name}, age={self.age})"
rex = Dog("Rex", 3)
print(rex)Dog(name=Rex, age=3)Without __str__, print(rex) would instead show something like <__main__.Dog object at 0x...>, which is much less useful for debugging or display.
Common Mistakes
- Forgetting self as the first parameter of an instance method, causing a TypeError.
- Using a mutable value like a list or dictionary as a class attribute, causing it to be shared unexpectedly across all objects.
- Confusing a class attribute (shared) with an instance attribute (unique per object).
- Forgetting parentheses when creating an object, e.g. writing Dog instead of Dog().
- Expecting print(my_object) to look nice without defining a __str__ method.
Best Practices
- Use PascalCase for class names (e.g. Dog, BankAccount) and snake_case for methods and attributes.
- Set instance attributes inside __init__ using self, not as class attributes, unless truly shared.
- Avoid mutable class attributes like lists and dictionaries unless every object should genuinely share them.
- Define a __str__ method on classes you plan to print, for cleaner debugging output.
- Keep each class focused on one clear concept, like Dog or BankAccount.
Frequently Asked Questions
What is the difference between a class attribute and an instance attribute?
A class attribute is shared by every object created from the class, while an instance attribute belongs to just one specific object and is usually set inside __init__.
Why does every method need self as the first parameter?
self lets a method know which specific object it is operating on. Python passes it automatically when you call a method on an object, like my_dog.bark().
Can I have two objects with completely identical data?
Yes, they can hold identical values, but they are still two separate objects in memory unless you define custom equality behavior.
Why did my class attribute list end up shared across all my objects?
Class attributes are stored once on the class itself, not copied per object. Mutable class attributes like lists are shared, which is why they should usually be created inside __init__ with self instead.
What comes next after classes and objects?
The next lesson goes deep on __init__, Python's constructor method, including default parameter values and validating arguments.
Key Takeaways
- A class is defined with the class keyword and acts as a blueprint for objects.
- self refers to the specific object a method is operating on, and is passed automatically.
- Instance attributes belong to one object; class attributes are shared by all objects of that class.
- Mutable class attributes like lists can cause unexpected shared state across objects.
- A __str__ method controls how an object looks when printed.
Summary
You learned how to define classes, create objects from them, and distinguish between instance attributes (unique per object) and class attributes (shared across all objects) — including the important gotcha around mutable class attributes. You also saw how __str__ makes printed objects readable.
- You can define a class and create objects from it.
- You understand instance attributes vs class attributes.
- You know how to avoid the shared mutable default gotcha.
- You are ready to go deep on the __init__ constructor.