MGU Computer Science Series — Programming with Python
Part VI: Object-Oriented and Larger Programs
Classes · Objects · Attributes · Methods · __init__ · self
Chapter 26 of 30
26
MGU Computer Science Series  ·  Programming with Python  ·  Library Textbook Edition

Chapter 26:
Classes and Objects

Combining data and behavior — blueprints, instances, constructors, attributes, methods, and the meaning of self

■ Part VI of VII ■

■ 10 Sections  •  Chapter 26 of 30 ■

Requires Parts I–V — functions, lists, dictionaries, and files

Chapter Introduction and Learning Objectives

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.

Opening Question

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.

10Sections
11Objectives
11Key Terms
30Total Chapters

Learning Objectives

By the end of this chapter, you should be able to:

  1. 1Explain why classes exist and what problem they solve compared to dictionaries and parallel variables.
  2. 2Distinguish between a class and an object (instance).
  3. 3Define a class in Python using the class statement.
  4. 4Create (instantiate) objects from a class and access them through variables.
  5. 5Explain what attributes are and how they are stored on objects using dot notation.
  6. 6Write instance methods and explain the role of self as the first parameter.
  7. 7Write an __init__ method that initializes an object's attributes at creation time.
  8. 8Explain what self refers to inside a method and why every instance method requires it.
  9. 9Follow a design process to plan and implement a simple class from scratch.
  10. 10Identify and correct the most common object-oriented mistakes: missing self, missing self., and confusing the class with an instance.
  11. 11Decide when a class is the right tool and when simpler structures are sufficient.

Sections at a Glance

Sections 26.1 – 26.4
The Concept: What Classes Are and How to Create Objects
Classes · Objects · Blueprints · Instantiation · Dot Notation

Why Classes Exist

The Problem Classes Solve

Imagine 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.

Data and Behavior Together

A class packages two things together:

First Component
Data — Attributes
The information an object carries. A Book object might have attributes title, author, pages, and checked_out. Each object stores its own copy of these values.
Second Component
Behavior — Methods
The actions an object can perform. A 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.

Classes Are Not Always the Answer

When to Use a Class

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?

Objects as Data Plus Behavior

What an Object Is

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.

Classes as Blueprints

The Blueprint Analogy

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.

TermMeaningAnalogy
ClassThe blueprint — defines structure and behaviorRecipe
Object / InstanceOne specific value created from the classA cake made from that recipe
InstantiationThe act of creating a new object from a classBaking the cake
AttributeA piece of data stored on an objectThe cake's flavor, size, or frosting
MethodA function defined in the class; an action the object can performSlicing the cake, serving it

Dot Notation

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.

Defining a Class

The class Statement

A 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.

Class Names: CapWords Convention

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.

A Minimal Working Class

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()
Output
This is a book.

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.

Creating Objects

Instantiation

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()

Checking an Object's Type

Python's built-in type() function tells you what class an object belongs to:

book_one = Book()
print(type(book_one))
Output
<class '__main__.Book'>

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

Objects Are Values

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'>
Objects in Lists

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.

Sections 26.5 – 26.7
The Details: Attributes, Methods, and __init__
Attributes · Methods · Constructors · Object State

Attributes

What Attributes Are

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.

Setting and Reading Attributes

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
Fragile Pattern

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.

Updating Attributes

Update an attribute using the same syntax as any variable assignment:

my_book.pages = 500      # update the attribute

Attributes Are Per Object

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

Methods

What Methods Are

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.

Defining a Method

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.

Calling Methods

my_book = Book()
my_book.title = "Dune"
my_book.author = "Frank Herbert"
my_book.pages = 412
my_book.display()
Output
Title: Dune Author: Frank Herbert Pages: 412

Methods That Accept Additional Parameters

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()
Output
Title: Dune

Methods That Return Values

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

The __init__ Method

The Problem with Setting Attributes Manually

If 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.

Defining __init__

The Constructor

__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.

Creating Objects with __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.

A More Complete Book Class

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()
Output
Title: Dune Author: Frank Herbert Pages: 412 Status: Available 'Dune' has been checked out. 'Dune' is already checked out. 'Dune' has been returned.
Sections 26.8 – 26.10
The Practice: Understanding self, Design, and Common Mistakes
self · Design Process · Common Errors

Understanding self

What self Is

self 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.

Why Every Method Needs self

Without 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 Name

By 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.

A Common Mistake

Missing self

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.

Tracing self Step by Step
What Python does when you write Book("Dune", "Frank Herbert", 412)
  • Step 1Python allocates memory for a new Book object. The object exists but has no attributes yet.
  • Step 2Python calls __init__ automatically, passing the new object as self and the three arguments as title, author, and pages.
  • Step 3self.title = "Dune" stores the string "Dune" on the object as an attribute named title.
  • Step 4The remaining lines set author, pages, and checked_out the same way.
  • Step 5__init__ returns. Python returns the fully constructed object, which you assign to my_book.

Designing Simple Classes

A Design Process

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.

  1. 01Name the thing you are modeling. What real-world or program concept will objects represent? A bank account, a student, a task, an inventory item?
  2. 02List the data each object must carry. These become the attributes. Write them in plain English first: a student has a name, a student ID, and a list of grades.
  3. 03List the actions each object must perform. These become the methods. A student can add a grade, compute an average, and display a summary.
  4. 04Decide what __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.
  5. 05Write the class. Start with __init__, then write each method one at a time. Test each method before writing the next.

Worked Design: Student

Applying the five-step process to a student gradebook:

Design StepDecision
NameStudent
Attributesname, student_id, grades (starts as empty list)
Methodsadd_grade, get_average, display
__init__ requiresname 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()
Output
Name: Maria Chen Student ID: S-1042 Grades: [88, 92, 79] Average: 86.3

Worked Design: BankAccount

Design StepDecision
NameBankAccount
Attributesowner, balance
Methodsdeposit, withdraw, get_balance, display
__init__ requiresowner; 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()
Output
Account owner: James Osei Balance: $0.00 Deposited $500.00. New balance: $500.00 Withdrew $120.50. New balance: $379.50 Insufficient funds. Account owner: James Osei Balance: $379.50

Common Object-Oriented Mistakes

Mistake 1
Forgetting self in method definitions.
Wrong: def display(): — Python passes the object as the first argument and has nothing to receive it.
Fix: Always include self as the first parameter: def display(self):
Mistake 2
Forgetting self. when storing attributes in __init__.
Wrong: title = title inside __init__ — this creates a local variable that disappears immediately.
Fix: Use self.title = title to store the value as an attribute on the object.
Mistake 3
Calling a method on the class rather than on an instance.
Wrong: Book.display() — the class is the blueprint, not an object with attribute values.
Fix: Create an instance first: my_book = Book(...), then call my_book.display().
Mistake 4
Accessing attributes on the class instead of an instance.
Wrong: print(Book.title) — the class definition does not have attribute values, only instances do.
Fix: print(my_book.title) on an instance.
Mistake 5
Putting an entire program inside one class, or adding classes to scripts that do not need them.
A class should model one thing. Functions can still live outside classes. Most programs use a mix of classes, functions, and simple data structures. Add classes when they genuinely clarify the program's organization — not to appear more advanced.
MistakeSymptomFix
Missing self in method signaturetakes 0 positional arguments but 1 was givenAdd self as first parameter
Using name = name in __init__AttributeError: 'Book' object has no attribute 'title'Use self.name = name
Calling method on the classTypeError: missing 1 required positional argumentCall on an instance instead
Reading attribute on the classAttributeError: type object 'Book' has no attribute 'title'Access on an instance

Chapter Summary

Key Terms

attribute
A variable that belongs to an object, storing part of that object's data. Accessed using dot notation: object.attribute.
class
A blueprint that defines the structure and behavior of a type of object: what attributes it has and what methods it can perform.
constructor
The __init__ method; called automatically by Python when a new object is instantiated, setting up its initial attributes.
dot notation
The syntax object.attribute or object.method() used to access an object's attributes and call its methods.
__init__
A special method Python calls automatically when instantiating a new object. Used to initialize all the object's attributes.
instance
A specific object created from a class. Also called an object. Each instance is independent and holds its own attribute values.
instantiation
The act of creating a new object from a class, done by calling the class like a function: obj = ClassName(args).
method
A function defined inside a class that describes behavior objects of that class can perform. Always takes self as its first parameter.
object
A value that combines data (attributes) and behavior (methods). An instance of a class.
object-oriented programming
A programming style that organizes programs around objects — values that combine related data and the functions that operate on them.
self
The first parameter of every instance method; automatically receives the object the method is called on. Convention: always named self.
ExercisesCode Reading

Read each class definition carefully. Answer the questions without running the code.

Exercise 26-R1
Reading a Task class
  • 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}")
  • Q1What attributes does a Task object have when it is first created?
  • Q2What is the value of self.completed immediately after Task("Write report") is called?
  • Q3After calling .complete() on a task, what does .display() print?
  • Q4Can you create a Task object without providing a description? Explain why or why not.
Exercise 26-R2
Reading a Counter class
  • The Code
    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
  • Q1What is the value of self.count immediately after Counter() is called?
  • Q2What does the get_value method return?
  • Q3What does reset() do?
  • Q4Predict the exact output of: c = Counter(); c.increment(); c.increment(); c.increment(); print(c.get_value()); c.reset(); print(c.get_value())
ExercisesCode Tracing

Trace the execution by hand. Track each attribute's value at each step.

  1. 26-T1. Trace this program. Show the value of 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)
  2. 26-T2. Trace this program and write exactly what it prints, line by line.
    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()
ExercisesObject Diagram Practice

Draw an object diagram showing the object as a labeled box, with attribute names and their values listed inside.

  1. 26-D1. Draw an object diagram for: book = Book("The Hobbit", "J.R.R. Tolkien", 310). Assume Book.__init__ sets title, author, pages, and checked_out = False.
  2. 26-D2. Draw object diagrams for both objects after these lines complete:
    s1 = Student("Ana Lima", "S-2201")
    s1.add_grade(90)
    s1.add_grade(85)
    
    s2 = Student("Kwame Asante", "S-2202")
    s2.add_grade(78)
ExercisesPredict the Output

Predict exactly what each program prints. Then check your prediction.

  1. 26-P1.
    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)
  2. 26-P2.
    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)
ExercisesDebugging Practice

Each program below contains one or more errors. Identify the error type, explain what is wrong, and write the corrected code.

  1. 26-Bug1.
    class Circle:
        def __init__(self, radius):
            radius = radius
    
        def area(self):
            return 3.14159 * self.radius ** 2
    
    c = Circle(5)
    print(c.area())
  2. 26-Bug2.
    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())
  3. 26-Bug3.
    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)
ExercisesShort Programming Tasks
  1. 26-S1. Write a class called 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.
  2. 26-S2. Write a class called 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.
  3. 26-S3. Write a class called 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.
  4. 26-S4. Write a class called 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.
  5. 26-S5. Write a class called 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.
Mini-ProjectSimple Class-Based Tracker

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.

Choose One Option

Option A
Task Tracker
Each task has a description and a status of pending or done. Let the user add tasks, mark tasks as done, and display all tasks.
Option B
Reading Log
Each entry has a title, author, and reading status (want to read, reading, finished). Let the user add books, update status, and display the log.
Option C
Simple Inventory
Each item has a name, quantity, and price. Let the user add items, sell units (reducing quantity), restock, and display the inventory with total value.

Requirements for All Options

  1. Define a class for the tracked object with at least three attributes and at least three methods.
  2. Use __init__ to initialize all attributes.
  3. Store a collection of objects in a list.
  4. Provide a text menu that lets the user add, update, and view records.
  5. Handle invalid inputs gracefully — do not crash if the user types something unexpected.

Starter Structure (Option A)

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()
Extension Ideas

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.

ReviewReview Questions

Recall and Vocabulary

  1. What is the difference between a class and an object?
  2. What is instantiation? Write a one-line code example.
  3. What does __init__ do, and when does Python call it?
  4. Why does every instance method need self as its first parameter?
  5. What is the difference between an attribute and a method?
  6. Explain what self.balance = balance does inside __init__.
  7. What naming convention do Python class names use? Give three examples.
  8. What error occurs if you define a method without self and then call it on an object?

Understanding and Explanation

  1. You create two BankAccount objects. What happens to account_one's balance when you deposit money into account_two? Why?
  2. Why is setting attributes directly after instantiation (without __init__) considered fragile? Describe a specific scenario where it causes a problem.
  3. A classmate writes: class Dog: def bark(): print("Woof"). They then write rex = Dog(); rex.bark(). What error occurs and why? Rewrite the class correctly.
  4. Explain the blueprint analogy for classes and objects using an example from the chapter. What does the blueprint correspond to? What does the instance correspond to?
  5. When should you use a class instead of a dictionary? Give a concrete scenario where a class is clearly the better choice, and one where a dictionary is clearly sufficient.
  6. Explain why self appears as the first parameter of every instance method but you do not pass it when calling the method.

Application and Design

  1. Apply the five-step design process to plan a Movie class. Describe your decisions for each step. (Do not write the full class — just the design plan.)
  2. Write a class called 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.
  3. You have a 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.
  4. A program uses a list of dictionaries to store books: [{"title": "Dune", "pages": 412, "read": False}, ...]. Describe at least two specific advantages of replacing this with a list of Book objects instead.
Chapter 26 Checklist
Confirm your understanding before moving to Chapter 27
Concepts
  • I can explain why classes exist and what problem they solve compared to dictionaries.
  • I can distinguish between a class (blueprint) and an object (instance).
  • I can define a class using the class statement and the CapWords naming convention.
  • I can instantiate objects and explain that each is independent.
  • I can explain what attributes are and how they differ from local variables.
  • I can explain what methods are and write methods that accept parameters and return values.
  • I can write an __init__ method that guarantees all attributes are set at creation time.
  • I can explain what self refers to inside a method and why every instance method requires it.
Skills
  • I can read an unfamiliar class definition and identify its attributes, methods, and __init__ signature.
  • I can trace an object's attribute values step by step as methods are called.
  • I can use the five-step design process to plan a class before writing it.
  • I can identify and fix the four most common OOP mistakes.
  • I can build a working class-based program with a menu and a list of objects.
Judgment
  • I can identify situations where a class is clearly the right tool.
  • I can identify situations where functions and dictionaries are sufficient.
  • I understand that classes are one organizational tool among several — not always the answer.
Preview — Chapter 27
Methods, Attributes, and Object State

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.

MGU Library Connection
  • Programming with Python — Appendix K: Object-Oriented Programming Reference
  • Companion resource: Class Design Worksheet (name, attributes, methods, __init__ planner)
  • Companion resource: Object Diagram Guide
  • Earlier chapters: Chapter 17 (Lists) and Chapter 18 (Dictionaries) — essential background for lists of objects
  • Next chapter: Chapter 27 — Methods, Attributes, and Object State
  • Next course path: Data Structures and Algorithms (uses classes extensively)