Previous | Tutorial index | Next
Learn how to create objects from classes and interact with them.
Instantiation is the process of creating a specific, concrete object (instance) from a class blueprint. When you instantiate a class, you are:
__init__ to initialize the object's state.To create an instance, you call the class name as if it were a function, passing any required arguments that __init__ expects.
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
# Instantiating the Dog class
my_dog = Dog("Frieda", 5)
What happens behind the scenes:
Dog.__init__(my_dog, "Frieda", 5) â note that my_dog is automatically passed as self.__init__ assigns "Frieda" to my_dog.name and 5 to my_dog.age.my_dog now holds a reference to this new object.Dog("Frieda", 5) is often called a constructor call, even though Python's actual constructor is __new__.One of the most powerful features of classes is that you can create as many independent instances as you need. Each instance has its own separate copy of the instance attributes.
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def speak(self):
return f"{self.name} says woof!"
# Creating multiple independent instances
dog1 = Dog("Frieda", 5)
dog2 = Dog("Rex", 3)
dog3 = Dog("Buddy", 7)
print(dog1.name) # Frieda
print(dog2.name) # Rex
print(dog3.name) # Buddy
Key Insight: dog1, dog2, and dog3 are completely separate objects in memory. Changing dog1.age does not affect dog2.age or dog3.age. This independence is crucial for modelling realâworld systems where each entity has its own state.
Memory Address 1000 (dog1): [name="Frieda", age=5]
Memory Address 2000 (dog2): [name="Rex", age=3]
Memory Address 3000 (dog3): [name="Buddy", age=7]
Each object lives at a different memory location and maintains its own data.
Once you have an instance, you can access its attributes using dot notation: object.attribute_name.
class Book:
def __init__(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
self.current_page = 1
# Create an instance
my_book = Book("1984", "George Orwell", 328)
# Accessing attributes
print(my_book.title) # 1984
print(my_book.author) # George Orwell
print(my_book.pages) # 328
print(my_book.current_page) # 1
book_title = my_book.titlemy_book.current_page = 50Remember the encapsulation principle from Tutorial 1? In Python, all attributes are technically "public" by defaultâyou can access and modify them directly from outside the class. Later, in Tutorial 5, you'll learn how to use naming conventions (_ and __) to signal which attributes should not be accessed directly.
Instance attributes are mutable (unless they are immutable types like strings or integers, in which case you assign new values). You can modify them at any time.
class Car:
def __init__(self, model, year):
self.model = model
self.year = year
self.mileage = 0
my_car = Car("Tesla Model 3", 2023)
print(my_car.mileage) # 0
# Modifying an attribute directly
my_car.mileage = 15000
print(my_car.mileage) # 15000
# Modifying using arithmetic
my_car.year += 1 # Now 2024
While direct modification is allowed, it's often better to use methods to modify attributes. This allows you to add validation and maintain encapsulation.
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
if amount <= 0:
raise ValueError("Deposit amount must be positive")
self.balance += amount
return self.balance
def withdraw(self, amount):
if amount <= 0:
raise ValueError("Withdrawal amount must be positive")
if amount > self.balance:
raise ValueError("Insufficient funds")
self.balance -= amount
return self.balance
account = BankAccount("Alice", 1000)
# Using methods is safer than direct modification
account.deposit(500) # balance becomes 1500
account.withdraw(200) # balance becomes 1300
# Direct modification bypasses validation (not recommended!)
account.balance = 9999999 # This would work but is dangerous!
Best Practice: Use methods to modify attributes when you need to enforce rules or trigger side effects. Direct modification is acceptable for simple, "dumb" data containers.
Instance methods are functions defined inside a class that operate on instances. They are called using dot notation: object.method_name(arguments).
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
self.energy = 100
def speak(self):
return f"{self.name} says woof!"
def play(self, minutes):
self.energy -= minutes * 2
if self.energy < 0:
self.energy = 0
return f"{self.name} played for {minutes} min. Energy: {self.energy}"
def birthday(self):
self.age += 1
return f"Happy birthday, {self.name}! Now {self.age} years old."
# Create an instance
fido = Dog("Fido", 3)
# Call methods
print(fido.speak()) # Fido says woof!
print(fido.play(15)) # Fido played for 15 min. Energy: 70
print(fido.birthday()) # Happy birthday, Fido! Now 4 years old.
# Correct: calling the method
result = fido.speak() # "Fido says woof!"
# Incorrect: referencing the method object
result = fido.speak # <bound method Dog.speak of <__main__.Dog object at 0x...>>
Without parentheses, you get a bound method object, not the result of the method. This is a very common beginner mistake.
speak() returns a string).play() changes energy).deposit() modifies balance and returns the new balance.Each instance is completely independent. They share the same class definition (the blueprint), but their attribute values are stored separately.
class Student:
def __init__(self, name, grade):
self.name = name
self.grade = grade
def improve_grade(self, amount):
self.grade += amount
return self.grade
# Create two independent students
alice = Student("Alice", 85)
bob = Student("Bob", 92)
# Alice improves her grade
alice.improve_grade(5) # Alice's grade: 90
print(alice.grade) # 90
print(bob.grade) # 92 (unchanged!)
# Bob improves his grade differently
bob.improve_grade(3) # Bob's grade: 95
print(alice.grade) # 90 (still unchanged!)
print(bob.grade) # 95
__str__ and __repr__ Dunder Methods (Preview)Although you'll cover dunder methods in detail in Tutorial 8, it's useful to briefly introduce __str__ here because it makes working with objects much more pleasant.
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})"
def __repr__(self):
return f"Dog('{self.name}', {self.age})"
fido = Dog("Fido", 3)
print(fido) # Dog(name=Fido, age=3) â uses __str__
print(repr(fido)) # Dog('Fido', 3) â uses __repr__
Without these methods, print(fido) would show something unhelpful like <__main__.Dog object at 0x7f8a1c0b4a90>.
Let's build a small library system that demonstrates everything we've covered.
class Book:
"""A simple model of a library book."""
def __init__(self, title, author, isbn):
self.title = title
self.author = author
self.isbn = isbn
self.is_checked_out = False
self.current_borrower = None
def check_out(self, borrower_name):
if self.is_checked_out:
return f"Sorry, '{self.title}' is already checked out."
self.is_checked_out = True
self.current_borrower = borrower_name
return f"'{self.title}' checked out to {borrower_name}."
def return_book(self):
if not self.is_checked_out:
return f"'{self.title}' is already in the library."
self.is_checked_out = False
borrower = self.current_borrower
self.current_borrower = None
return f"'{self.title}' returned by {borrower}."
def __str__(self):
status = "Checked out" if self.is_checked_out else "Available"
return f"{self.title} by {self.author} (ISBN: {self.isbn}) - {status}"
# --- Creating multiple book instances ---
book1 = Book("1984", "George Orwell", "978-0-452-28423-4")
book2 = Book("To Kill a Mockingbird", "Harper Lee", "978-0-06-112008-4")
book3 = Book("The Great Gatsby", "F. Scott Fitzgerald", "978-0-7432-7356-5")
# --- Interacting with books ---
print(book1) # 1984 by George Orwell (ISBN: 978-0-452-28423-4) - Available
# Check out a book
print(book1.check_out("Alice")) # '1984' checked out to Alice.
print(book1) # 1984 ... - Checked out
# Try to check out the same book again
print(book1.check_out("Bob")) # Sorry, '1984' is already checked out.
# Return the book
print(book1.return_book()) # '1984' returned by Alice.
print(book1) # 1984 ... - Available
# Check out to a different person
print(book1.check_out("Charlie")) # '1984' checked out to Charlie.
# Book2 remains available (independence!)
print(book2) # To Kill a Mockingbird ... - Available
Key observations:
Book instance maintains its own is_checked_out and current_borrower state.book1 do not affect book2 or book3.__str__ method makes printing objects informative.| Pitfall | Explanation | How to Avoid |
|---|---|---|
| Forgetting parentheses when calling a method | book.check_out returns a method object, not the result. |
Always use book.check_out("Alice"). |
| Modifying an attribute directly instead of using a method | book.is_checked_out = False bypasses any logic in return_book(). |
Use the provided methods to maintain encapsulation. |
| Assuming objects are copied when assigned | book2 = book1 makes book2 and book1 refer to the same object. |
Use copy or create a new instance if you need a separate object. |
Printing an object without __str__ |
You get an unhelpful memory address. | Define __str__ for userâfriendly printing. |
| Mutating default arguments in methods | def __init__(self, items=[]): creates a shared list. |
Use None as default: def __init__(self, items=None): |
| Confusing class attributes with instance attributes | Modifying a class attribute affects all instances. | Use self.attribute for instanceâspecific data. |
Answer the following questions to check your understanding.
1. What is the correct syntax to create an instance of a class named Car that has an __init__ method expecting model and year?
my_car = Carmy_car = Car("Tesla", 2023)my_car = Car.init("Tesla", 2023)my_car = new Car("Tesla", 2023)2. How do you access the model attribute of a Car instance stored in variable my_car?
my_car.model()my_car["model"]my_car.modelmodel(my_car)3. Consider the following code:
class Player:
def __init__(self, name):
self.name = name
self.score = 0
p1 = Player("Alice")
p2 = Player("Bob")
p1.score = 100
What is p2.score?
1000None4. What does the following code print?
class Greeter:
def __init__(self, name):
self.name = name
def greet(self):
return f"Hello, {self.name}!"
g = Greeter("World")
print(g.greet)
Hello, World!<bound method Greeter.greet of <__main__.Greeter object at ...>>Hello, World! is printed but with an error.None5. (True/False) If you create two instances of the same class, they share the same memory location.
6. Which of the following is a valid way to modify the age attribute of an instance dog?
dog.age = 5dog.set_age(5) if such a method existsdog.age += 17. What will be the output of the following code?
class Counter:
def __init__(self):
self.count = 0
def increment(self):
self.count += 1
c1 = Counter()
c2 = Counter()
c1.increment()
c1.increment()
c2.increment()
print(c1.count, c2.count)
2 12 21 13 18. What does the phrase "instantiating a class" mean?
Part A: Create an Employee Class
Define a class called Employee with:
name (str), employee_id (int), position (str), salary (float), years_at_company (int, default 0).promote(new_position, raise_amount): Updates position and adds raise_amount to salary. Returns a confirmation string.work_year(): Increments years_at_company by 1. Returns the new total years.display(): Returns a formatted string like "Alice Johnson (ID: 123) - Manager, $75,000.00, 5 years".Part B: Instantiate and Interact
Create three employee objects (e.g., for a tech company). Perform these operations:
work_year()).Part C: Method Call Mistakes
Write a small script that demonstrates the following mistakes and then fix them:
self in a method definition (this will raise an error).__init__.Submit your corrected code with comments explaining each fix.
Task: You are building a simple inventory management system for a small store. Create a complete Python script that implements an InventoryItem class with the following detailed specifications.
Attributes (set in __init__):
item_id (int) â unique identifier (passed in).name (str) â product name.category (str) â product category (e.g., "Electronics", "Clothing").price (float) â price per unit.quantity_in_stock (int) â number of units available (default 0).reorder_level (int) â minimum quantity before reordering (default 5).restock_history (list) â list of tuples (timestamp, quantity) recording each restock. Initialize as empty list.Instance Methods:
sell(quantity): Reduces quantity_in_stock by quantity. If quantity is negative or exceeds current stock, raise a ValueError with an appropriate message. Return the new stock level.
restock(quantity): Increases quantity_in_stock by quantity. Add a tuple (current_datetime, quantity) to restock_history. If quantity is not positive, raise a ValueError. Return the new stock level.
is_low_stock(): Returns True if quantity_in_stock is less than or equal to reorder_level, otherwise False.
total_value(): Returns the total monetary value of the stock (price * quantity_in_stock).
display(): Returns a formatted string like:
Item #101: Laptop (Electronics)
Price: $999.99 | Stock: 12 units
Reorder Level: 5 | Low Stock: No
Total Value: $11,999.88
Last Restock: 2026-08-11 14:30:22 (5 units)
Hint: You can simulate timestamps with from datetime import datetime and use datetime.now().
Part 1: Code
Write the complete class definition with all methods. Include:
sell() and restock().datetime for restock timestamps.Part 2: Testing Script
In the same file, create a main script that does the following:
display() on all four items.is_low_stock().Part 3: Written Reflection
Answer these questions in a comment block at the top of your script:
sell() method, why is it important to validate that the quantity is not negative and does not exceed the stock? What could happen if you omitted this validation?item1, item2, item3, and item4 completely independent even though they were created from the same InventoryItem class?restock_history attribute uses a list of tuples. Why is using a tuple appropriate for a restock event? (Hint: think about immutability.)discount attribute and a apply_discount() method, what principle of OOP would that demonstrate?Before moving to Tutorial 4 (Inheritance), ensure you can confidently say YES to the following:
Ready for the next tutorial? In Tutorial 4, you will explore inheritance and the super() functionâhow to create class hierarchies, reuse code, and build relationships between classes.