Combining data and behavior — blueprints, instances, constructors, attributes, methods, and the meaning of self
You have spent twenty-five chapters writing Python programs that use variables, functions, lists, dictionaries, files, and modules. You know how to organize instructions into functions, how to store collections of values in lists and dictionaries, and how to build programs that read, transform, and save data.
This chapter introduces a different kind of organization: the class. A class lets you combine data and behavior into a single, named unit. Instead of tracking a student's name, grade, and score as three separate variables, you can create a Student object that carries all three together and knows how to do useful things with them. Instead of passing a dictionary through fifteen functions, you can create a BankAccount object that manages its own balance and knows how to deposit, withdraw, and report.
Object-oriented programming is not a replacement for what you already know. It is an additional tool for organizing programs that manage many related pieces of information. By the end of this chapter, you will be able to define a class, create objects from it, give objects attributes, write methods, and read object-oriented Python code with confidence.
A library program tracks hundreds of books. Each book has a title, an author, a page count, and a status showing whether it is available or checked out. Should you store each book as a loose dictionary, as four separate parallel lists, or as an object designed around the concept of a book? This chapter gives you the tools to answer that question — and the judgment to know when each approach makes sense.
By the end of this chapter, you should be able to:
class statement.self as the first parameter.__init__ method that initializes an object's attributes at creation time.self refers to inside a method and why every instance method requires it.self, missing self., and confusing the class with an instance.class statement; CapWords naming convention; a minimal class; creating an instance; calling a methodtype(); isinstance(); objects as ordinary Python values__init__ MethodThe constructor; guaranteed attributes at creation time; default attribute values; a complete Book class walkthroughselfWhat self refers to; why every method needs it; self is just a name; common mistake of omitting selfStudent and BankAccount; planning before writingself; forgetting self.; calling on the class not an instance; confusing class and instance; overusing classesImagine you are writing a program that tracks library books. Each book has a title, an author, a number of pages, and a status that tells you whether it is checked out or available. Using what you already know, you might store each book as a dictionary:
book = {
"title": "The Midnight Library",
"author": "Matt Haig",
"pages": 304,
"checked_out": False
}
That works for one book. But as your program grows, you add functions to check a book out, return it, display its information, and compare books by page count. Every function receives a dictionary and works with its keys. If you ever rename a key or forget to include one, your functions break silently.
Now imagine your program tracks five hundred books, each with slightly different keys because different parts of the code were written at different times. Tracking down inconsistencies becomes exhausting.
Classes solve this problem by giving you a blueprint. You define once what a book is: what information it carries and what it can do. Every book object built from that blueprint is guaranteed to have the same structure.
A class packages two things together:
Book object might have attributes title, author, pages, and checked_out. Each object stores its own copy of these values.Book object might have methods check_out(), return_book(), and display(). These are functions that operate on the object's own data.When data and the functions that operate on that data live together in the same class, your program becomes easier to read, easier to test, and easier to extend.
Object-oriented programming shines when your program manages multiple similar things, each with its own state that changes over time — students in a gradebook, accounts in a bank, items in an inventory, tasks in a to-do list. A short script that reads a file, counts words, and prints a result does not need a class. Functions and simple data structures are often the right choice. As you learn classes in this chapter, keep asking: does this program actually benefit from objects, or am I adding structure just because I can?
An object is a value that combines data and behavior. You have already used objects throughout this book, even if you did not call them that.
A string is an object. It carries text data, and it has methods like .upper(), .split(), and .replace(). A list is an object with methods like .append(), .sort(), and .remove(). A dictionary is an object with methods like .get(), .keys(), and .items().
When you write "hello".upper() or my_list.append(7), you are calling a method on an object. The object knows how to perform that action because the class it belongs to defines the behavior.
A class is the blueprint that defines what objects of a certain kind look like and how they behave. An object is a specific instance built from that blueprint.
Think of a recipe. The recipe is the class — a blueprint defining what a cake is and how to make one. A cake baked from that recipe is an object, or instance. You can bake a hundred cakes from the same recipe. Each cake is its own independent object: it exists separately, can be eaten without affecting the others, and holds its own state (fresh, stale, half-eaten). But every cake follows the same structure because they all came from the same blueprint.
| Term | Meaning | Analogy |
|---|---|---|
| Class | The blueprint — defines structure and behavior | Recipe |
| Object / Instance | One specific value created from the class | A cake made from that recipe |
| Instantiation | The act of creating a new object from a class | Baking the cake |
| Attribute | A piece of data stored on an object | The cake's flavor, size, or frosting |
| Method | A function defined in the class; an action the object can perform | Slicing the cake, serving it |
You access an object's attributes and call its methods using dot notation:
object.attribute # read an attribute
object.attribute = val # set an attribute
object.method() # call a method with no arguments
object.method(arg) # call a method with an argument
You have used this syntax with strings, lists, and file objects throughout earlier chapters. The same syntax applies to every object you create from your own classes.
class StatementA class definition begins with the keyword class, followed by the class name, followed by a colon. The body of the class is indented, just like a function body.
class Book:
pass
The keyword pass is a placeholder that means "this block has no content yet." This is valid Python — you have defined a class called Book that currently does nothing.
By Python convention, class names use CapWords (also called PascalCase): every word begins with a capital letter, with no underscores between words.
Book
BankAccount
LibraryCard
StudentRecord
InventoryItem
This is different from function and variable names, which use lowercase_with_underscores. The capitalization helps you distinguish class names from variable names at a glance.
Here is a class with one method:
class Book:
def display(self):
print("This is a book.")
This defines a class called Book with one method called display. The method display takes one parameter called self. The role of self is explained in section 26.8 — for now, accept that every method in a class must include self as its first parameter.
Creating an object and calling the method:
my_book = Book()
my_book.display()
The line my_book = Book() creates a new Book object and assigns it to the variable my_book. The parentheses after Book look like a function call — in a sense, that is exactly what instantiation is. You call the class like a function to create a new instance.
Instantiation is the act of creating an object from a class. The syntax is:
variable_name = ClassName()
You can create as many objects from the same class as you need. Each object is independent — changing one does not affect the others.
book_one = Book()
book_two = Book()
book_three = Book()
Python's built-in type() function tells you what class an object belongs to:
book_one = Book()
print(type(book_one))
The output shows that book_one is an instance of the Book class defined in the main program. Python's isinstance() function checks whether an object is an instance of a given class:
print(isinstance(book_one, Book)) # True
print(isinstance(book_one, str)) # False
An object is a value in Python, just like an integer or a string. You can assign it to a variable, pass it to a function, store it in a list, return it from a function, and print it (though what printing shows depends on how the class is defined).
def describe(item):
print(type(item))
describe(book_one) # <class '__main__.Book'>
describe(42) # <class 'int'>
describe("hello") # <class 'str'>
You can store objects in a list just like any other value: books = [Book(), Book(), Book()]. This pattern — a list of objects — is extremely common in real programs, and you will practice it in the exercises at the end of this chapter.
An attribute is a piece of data that belongs to an object. It is stored inside the object and accessed using dot notation. Every object of the same class has the same set of attributes, but each object's attribute values can differ. One Book object might have the title "Dune", while another has "1984". Same class, same attribute name, different values.
Python allows you to set attributes on an object directly:
class Book:
pass
my_book = Book()
my_book.title = "Dune"
my_book.author = "Frank Herbert"
my_book.pages = 412
print(my_book.title) # Dune
print(my_book.author) # Frank Herbert
print(my_book.pages) # 412
Setting attributes manually after instantiation is fragile. If you create a second book and forget to set pages, your program will crash the moment any code tries to read book.pages. The __init__ method (section 26.7) is the solution: it guarantees all attributes exist from the moment the object is created.
Update an attribute using the same syntax as any variable assignment:
my_book.pages = 500 # update the attribute
Each object maintains its own copy of each attribute. Changing one object's attribute has no effect on any other object:
book_one = Book()
book_one.title = "Dune"
book_two = Book()
book_two.title = "1984"
print(book_one.title) # Dune
print(book_two.title) # 1984
A method is a function defined inside a class. Methods define the behavior of objects — the actions they can perform. You call a method using dot notation: object.method_name(). You have called methods on strings and lists throughout this book. Now you will write your own.
A method is defined with def inside the class body. The first parameter is always self:
class Book:
def display(self):
print("Title:", self.title)
print("Author:", self.author)
print("Pages:", self.pages)
The self parameter refers to the specific object the method is called on. When you write my_book.display(), Python automatically passes my_book as the self argument. You do not pass it manually.
my_book = Book()
my_book.title = "Dune"
my_book.author = "Frank Herbert"
my_book.pages = 412
my_book.display()
Methods can accept additional parameters beyond self. When you call my_book.set_title("Dune"), Python passes my_book as self and "Dune" as new_title. You only write "Dune" in the call — self is handled automatically.
class Book:
def set_title(self, new_title):
self.title = new_title
def display(self):
print("Title:", self.title)
my_book = Book()
my_book.set_title("Dune")
my_book.display()
Methods can return values, just like regular functions:
class Book:
def is_long(self):
return self.pages > 400
my_book = Book()
my_book.pages = 412
print(my_book.is_long()) # True
__init__ MethodIf you create objects by setting attributes directly after instantiation, you risk creating objects with missing attributes. The __init__ method solves this by running automatically whenever a new object is created, setting up all its attributes in one place.
__init____init__ is a special method called a constructor. Python calls it automatically when you instantiate a new object. The double underscores on each side mark it as a built-in special method. Every new object is guaranteed to arrive fully equipped with all the attributes __init__ sets up.
class Book:
def __init__(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
self.checked_out = False
The parameters title, author, and pages are the information you provide when creating a new Book. The line self.title = title stores the passed-in value as an attribute on the object. self.checked_out = False sets a default attribute — every new book starts as not checked out.
__init__When __init__ is defined, you pass the required values during instantiation:
book_one = Book("Dune", "Frank Herbert", 412)
book_two = Book("1984", "George Orwell", 328)
print(book_one.title) # Dune
print(book_two.title) # 1984
print(book_one.checked_out) # False
Every Book object is now guaranteed to have title, author, pages, and checked_out attributes from the moment it is created.
Here is a full Book class combining __init__ with several methods that use and update the object's state:
class Book:
def __init__(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
self.checked_out = False
def check_out(self):
if self.checked_out:
print(f"'{self.title}' is already checked out.")
else:
self.checked_out = True
print(f"'{self.title}' has been checked out.")
def return_book(self):
if not self.checked_out:
print(f"'{self.title}' was not checked out.")
else:
self.checked_out = False
print(f"'{self.title}' has been returned.")
def display(self):
status = "Checked out" if self.checked_out else "Available"
print(f"Title: {self.title}")
print(f"Author: {self.author}")
print(f"Pages: {self.pages}")
print(f"Status: {status}")
my_book = Book("Dune", "Frank Herbert", 412)
my_book.display()
print()
my_book.check_out()
my_book.check_out()
my_book.return_book()
selfself Isself is the first parameter of every instance method. It refers to the specific object the method is being called on. When you write:
my_book.check_out()
Python translates this behind the scenes to:
Book.check_out(my_book)
The object my_book is passed as self. Inside the method, every reference to self.title, self.author, and so on refers to the attributes of that specific object.
selfWithout self, a method would have no way to know which object's data to use. If you have two Book objects, book_one and book_two, and you call book_one.display(), self is the mechanism that tells display() to use book_one's attributes and not book_two's.
self Is Just a NameBy convention, the first parameter of every instance method is named self. Python does not enforce this name — you could technically use any identifier — but every Python programmer uses self. Do not use a different name. Code that uses this or me instead of self will confuse every reader.
Forgetting to include self as the first parameter causes a confusing error:
class Book:
def display(): # missing self
print(self.title)
If you call my_book.display(), Python passes my_book as the first argument and there is no parameter to receive it. The error message will say something like: display() takes 0 positional arguments but 1 was given. Always include self as the first parameter of every instance method.
self in __init__self in __init__ works the same way. When you write:
def __init__(self, title, author, pages):
self.title = title
The line self.title = title creates a new attribute called title on the object being constructed and assigns it the value passed in as title. This is how object attributes are born.
Book("Dune", "Frank Herbert", 412)Book object. The object exists but has no attributes yet.__init__ automatically, passing the new object as self and the three arguments as title, author, and pages.self.title = "Dune" stores the string "Dune" on the object as an attribute named title.author, pages, and checked_out the same way.__init__ returns. Python returns the fully constructed object, which you assign to my_book.Before writing a class, think through what it needs. Rushing to code before planning leads to missing attributes, poorly named methods, and classes that do not match how they will actually be used.
__init__ should require. Some attributes are set at creation time and required. Others have sensible defaults. name and student_id are required. grades starts as an empty list.__init__, then write each method one at a time. Test each method before writing the next.Applying the five-step process to a student gradebook:
| Design Step | Decision |
|---|---|
| Name | Student |
| Attributes | name, student_id, grades (starts as empty list) |
| Methods | add_grade, get_average, display |
__init__ requires | name and student_id — grades is always empty at the start |
class Student:
def __init__(self, name, student_id):
self.name = name
self.student_id = student_id
self.grades = []
def add_grade(self, grade):
self.grades.append(grade)
def get_average(self):
if len(self.grades) == 0:
return 0.0
return sum(self.grades) / len(self.grades)
def display(self):
avg = self.get_average()
print(f"Name: {self.name}")
print(f"Student ID: {self.student_id}")
print(f"Grades: {self.grades}")
print(f"Average: {avg:.1f}")
s = Student("Maria Chen", "S-1042")
s.add_grade(88)
s.add_grade(92)
s.add_grade(79)
s.display()
| Design Step | Decision |
|---|---|
| Name | BankAccount |
| Attributes | owner, balance |
| Methods | deposit, withdraw, get_balance, display |
__init__ requires | owner; balance defaults to 0.0 |
class BankAccount:
def __init__(self, owner, balance=0.0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
if amount <= 0:
print("Deposit amount must be positive.")
return
self.balance += amount
print(f"Deposited ${amount:.2f}. New balance: ${self.balance:.2f}")
def withdraw(self, amount):
if amount <= 0:
print("Withdrawal amount must be positive.")
return
if amount > self.balance:
print("Insufficient funds.")
return
self.balance -= amount
print(f"Withdrew ${amount:.2f}. New balance: ${self.balance:.2f}")
def get_balance(self):
return self.balance
def display(self):
print(f"Account owner: {self.owner}")
print(f"Balance: ${self.balance:.2f}")
account = BankAccount("James Osei")
account.display()
account.deposit(500.00)
account.withdraw(120.50)
account.withdraw(1000.00)
account.display()
self in method definitions.def display(): — Python passes the object as the first argument and has nothing to receive it.self as the first parameter: def display(self):
self. when storing attributes in __init__.title = title inside __init__ — this creates a local variable that disappears immediately.self.title = title to store the value as an attribute on the object.
Book.display() — the class is the blueprint, not an object with attribute values.my_book = Book(...), then call my_book.display().
print(Book.title) — the class definition does not have attribute values, only instances do.print(my_book.title) on an instance.
| Mistake | Symptom | Fix |
|---|---|---|
Missing self in method signature | takes 0 positional arguments but 1 was given | Add self as first parameter |
Using name = name in __init__ | AttributeError: 'Book' object has no attribute 'title' | Use self.name = name |
| Calling method on the class | TypeError: missing 1 required positional argument | Call on an instance instead |
| Reading attribute on the class | AttributeError: type object 'Book' has no attribute 'title' | Access on an instance |
class statement. Class names use CapWords by convention. Every method in a class includes self as its first parameter. Calling the class like a function instantiates a new object.type() and isinstance() identify what class an object belongs to. Objects are ordinary Python values that can be stored in variables, lists, and function arguments.__init__ is the reliable solution.self as its first parameter. Methods can accept additional arguments, update object attributes, and return values.__init__ method (constructor) is called automatically when a new object is created. It sets up all attributes immediately, guaranteeing that every object of the class arrives fully equipped. Default attribute values can be set inside __init__ without requiring them as arguments.self refers to the specific object a method is called on. It is how methods know whose data to read and modify. Every instance method must include self as its first parameter. By convention self is always named self.__init__ requires, then write and test incrementally. Planning before writing produces cleaner, more usable classes.self in a method signature, forgetting self. when storing attributes, calling a method on the class instead of an instance, and overusing classes where simpler structures would suffice.object.attribute.__init__ method; called automatically by Python when a new object is instantiated, setting up its initial attributes.object.attribute or object.method() used to access an object's attributes and call its methods.obj = ClassName(args).self as its first parameter.self.Read each class definition carefully. Answer the questions without running the code.
class Task:
def __init__(self, description):
self.description = description
self.completed = False
def complete(self):
self.completed = True
def display(self):
status = "Done" if self.completed else "Pending"
print(f"[{status}] {self.description}")
Task object have when it is first created?self.completed immediately after Task("Write report") is called?.complete() on a task, what does .display() print?Task object without providing a description? Explain why or why not.class Counter:
def __init__(self):
self.count = 0
def increment(self):
self.count += 1
def reset(self):
self.count = 0
def get_value(self):
return self.count
self.count immediately after Counter() is called?get_value method return?reset() do?c = Counter(); c.increment(); c.increment(); c.increment(); print(c.get_value()); c.reset(); print(c.get_value())Trace the execution by hand. Track each attribute's value at each step.
balance after each line that changes it, and write what the final print outputs.
class BankAccount:
def __init__(self, owner, balance=0.0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
self.balance += amount
def withdraw(self, amount):
if amount <= self.balance:
self.balance -= amount
account = BankAccount("Priya", 100.0)
account.deposit(50.0)
account.withdraw(30.0)
account.withdraw(200.0)
account.deposit(10.0)
print(account.balance)
class Light:
def __init__(self):
self.on = False
def toggle(self):
self.on = not self.on
def status(self):
if self.on:
print("Light is on.")
else:
print("Light is off.")
lamp = Light()
lamp.status()
lamp.toggle()
lamp.status()
lamp.toggle()
lamp.toggle()
lamp.status()
Draw an object diagram showing the object as a labeled box, with attribute names and their values listed inside.
book = Book("The Hobbit", "J.R.R. Tolkien", 310). Assume Book.__init__ sets title, author, pages, and checked_out = False.s1 = Student("Ana Lima", "S-2201")
s1.add_grade(90)
s1.add_grade(85)
s2 = Student("Kwame Asante", "S-2202")
s2.add_grade(78)
Predict exactly what each program prints. Then check your prediction.
class Dog:
def __init__(self, name, breed):
self.name = name
self.breed = breed
def speak(self):
print(f"{self.name} says: Woof!")
d1 = Dog("Rex", "Labrador")
d2 = Dog("Luna", "Poodle")
d1.speak()
d2.speak()
print(d1.breed)
class Scoreboard:
def __init__(self):
self.score = 0
def add_points(self, points):
self.score += points
return self.score
def reset(self):
self.score = 0
board = Scoreboard()
result = board.add_points(10)
print(result)
board.add_points(5)
print(board.score)
board.reset()
print(board.score)
Each program below contains one or more errors. Identify the error type, explain what is wrong, and write the corrected code.
class Circle:
def __init__(self, radius):
radius = radius
def area(self):
return 3.14159 * self.radius ** 2
c = Circle(5)
print(c.area())
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area():
return self.width * self.height
r = Rectangle(4, 6)
print(r.area())
class Item:
def __init__(self, name, price):
self.name = name
self.price = price
def apply_discount(self, percent):
discount = self.price * (percent / 100)
self.price = self.price - discount
i = Item("Notebook", 12.00)
Item.apply_discount(10)
print(i.price)
Rectangle with attributes width and height. Include methods area() that returns the area, perimeter() that returns the perimeter, and display() that prints both. Create two Rectangle objects and call all three methods on each.TemperatureReading with attributes location and celsius. Include a method to_fahrenheit() that returns the temperature in Fahrenheit (celsius * 9/5 + 32), and a method display() that prints both values. Create three objects with different cities and temperatures.Inventory with attributes item_name, quantity, and price. Include methods restock(amount) that adds to quantity, sell(amount) that reduces quantity (and prints a message if stock is insufficient), and total_value() that returns quantity * price. Test with at least three operations.Flashcard with attributes question, answer, and times_shown (starts at 0). Include a method show() that prints the question and increments times_shown, a method reveal() that prints the answer, and a method stats() that prints how many times the card has been shown. Create three flashcards and simulate a study session.Todo with attributes task and done (starts as False). Include methods mark_done(), is_done() (returns a Boolean), and display() that prints [x] task if done or [ ] task if not. Create a list of five Todo objects and demonstrate marking some as done.Design and implement a small class-based tracker program that manages a collection of objects. Apply __init__, attributes, and methods to build a usable interactive program.
__init__ to initialize all attributes.class Task:
def __init__(self, description):
self.description = description
self.completed = False
def mark_done(self):
self.completed = True
def display(self):
status = "[x]" if self.completed else "[ ]"
print(f"{status} {self.description}")
def show_menu():
print("\n--- Task Tracker ---")
print("1. Add task")
print("2. Mark task done")
print("3. View all tasks")
print("4. Quit")
def main():
tasks = []
while True:
show_menu()
choice = input("Choose an option: ").strip()
if choice == "1":
description = input("Enter task description: ").strip()
if description:
tasks.append(Task(description))
print("Task added.")
elif choice == "2":
if not tasks:
print("No tasks yet.")
else:
for i, task in enumerate(tasks):
print(f" ({i})", end=" ")
task.display()
try:
index = int(input("Enter index of task to mark done: "))
tasks[index].mark_done()
print("Task marked done.")
except (ValueError, IndexError):
print("Invalid index.")
elif choice == "3":
if not tasks:
print("No tasks yet.")
else:
for task in tasks:
task.display()
elif choice == "4":
print("Goodbye.")
break
else:
print("Invalid choice. Please enter 1, 2, 3, or 4.")
main()
If you finish early: save tasks to a file when the program exits and load them at the start; add a way to remove a task; add a priority attribute (high, medium, low) and display tasks sorted by priority; add a summary() method that returns a one-line string describing the object.
__init__ do, and when does Python call it?self as its first parameter?self.balance = balance does inside __init__.self and then call it on an object?BankAccount objects. What happens to account_one's balance when you deposit money into account_two? Why?__init__) considered fragile? Describe a specific scenario where it causes a problem.class Dog: def bark(): print("Woof"). They then write rex = Dog(); rex.bark(). What error occurs and why? Rewrite the class correctly.self appears as the first parameter of every instance method but you do not pass it when calling the method.Movie class. Describe your decisions for each step. (Do not write the full class — just the design plan.)Timer with a single attribute seconds that starts at 0, a method tick() that increments seconds by 1, and a method display() that prints the current time in seconds. Demonstrate it running for five ticks.Student class with name, student_id, and grades attributes. Write a function called highest_average(students) that accepts a list of Student objects and returns the student with the highest grade average.[{"title": "Dune", "pages": 412, "read": False}, ...]. Describe at least two specific advantages of replacing this with a list of Book objects instead.class statement and the CapWords naming convention.__init__ method that guarantees all attributes are set at creation time.self refers to inside a method and why every instance method requires it.__init__ signature.Chapter 27 deepens your understanding of object-oriented programming by exploring object state more carefully. You will learn how methods read and modify state, how objects can interact with other objects, how to write a useful string representation of your objects using __str__, and how to work with lists of objects — a pattern that appears constantly in real programs. You will also compare dictionary-based and class-based designs, developing your judgment for when each approach makes your code clearer. By the end of Chapter 27, object-oriented design will be a genuine tool in your programming toolkit, not just new syntax.