Object-Oriented Programming~25 min

Objects and Classes

An object bundles state and behavior together, with its own identity — a class is the blueprint that objects are built from.

By the end of this lesson, you can

  • Describe an object as a bundle of state (attributes) and behavior (methods)
  • Write a small class with __init__ and a method, and create more than one independent instance
  • Explain why each instance keeps its own separate state, even though every instance shares the same class

Why it matters

A library holds many books, and each one needs to be tracked separately: its own title, its own loan status. Tracking this with loose variables works for one book, but falls apart with more than one — which variable belongs to which book? An object solves this by bundling related state and behavior together under one name, so each book can be handled as a single thing with its own identity.

Mental model

Before any Python syntax, think about a library book directly:

  • It has state: a title, and whether it’s currently on loan.
  • It has behavior: it can be borrowed.
  • It has identity: two copies of the same title on a shelf are still two separate books — borrowing one doesn’t affect the other.

An object is exactly this: state and behavior bundled together, with its own identity. A class is the blueprint that says what every book object will have; each individual book built from it is called an instance.

Small example

PythonA class, and two independent instances
class LibraryBook:
    def __init__(self, title):
        self.title = title
        self.is_on_loan = False

    def borrow(self):
        if self.is_on_loan:
            return False
        self.is_on_loan = True
        return True


book1 = LibraryBook("Dune")
book2 = LibraryBook("Foundation")

print(book1.borrow())
print(book2.is_on_loan)
Output
True
False

__init__ is a special method that runs once, when a new object is created — it’s where an object’s starting attributes (title, is_on_loan) get set. self refers to this particular object: inside borrow, self.is_on_loan means “the is_on_loan attribute of whichever book this method was called on” — not every book.

Trace it

book1 and book2 keep completely independent state
Stepbook1.titlebook1.is_on_loanbook2.titlebook2.is_on_loan
after creationDuneFalseFoundationFalse
book1.borrow()DuneTrueFoundationFalse

Calling book1.borrow() only changes book1’s attributes. book2.is_on_loan is untouched — even though book1 and book2 were built from the exact same class, using the exact same method.

Check your understanding

What does this print? book1 = LibraryBook("Dune") print(book1.borrow()) print(book1.borrow())
Or reveal the answer without checking

Answer:True, then False
The first borrow() sets is_on_loan to True and returns True. The second call sees is_on_loan is already True, so its if branch runs, returning False without changing anything.

After book1.borrow() runs, what happens to book2 (a separate LibraryBook instance)?
Or reveal the answer without checking

Answer:Nothing — book2's state is completely independent of book1's
Each instance holds its own independent attributes. Calling a method on book1 can only ever change book1's state, regardless of what other instances of the same class exist.

Practice: warm-up

Trace this program by hand — write down is_on_loan for both books after each line — before revealing the answer.

a = LibraryBook("Dune")
b = LibraryBook("Dune")
a.borrow()
b.borrow()
a.borrow()

Stuck? Reveal one hint at a time.

  1. Hint 1

    a and b are two separate instances, even though they have the same title — track them separately.

  2. Hint 2

    The third line calls borrow() on a again, while it is already on loan — what does borrow() do in that case?

Reveal the trace

Try the problem yourself before reading this. There is often more than one reasonable approach — treat this as one worked example, not the only correct answer.

a = LibraryBook("Dune")  -> a.is_on_loan = False
b = LibraryBook("Dune")  -> b.is_on_loan = False (independent of a)
a.borrow()               -> a.is_on_loan = True,  returns True
b.borrow()               -> b.is_on_loan = True,  returns True (independent of a)
a.borrow()               -> a.is_on_loan stays True, returns False (already on loan)

Practice: apply it

LibraryBook has no way to return a book once it’s borrowed.

What should a return_book method do?
Or reveal the answer without checking

Answer:Set self.is_on_loan to False
Returning a book should reverse exactly the state change borrow() made — is_on_loan back to False — so it can be borrowed again.

Modification challenge: add a return_book method to LibraryBook that sets self.is_on_loan back to False, then trace a.borrow() followed by a.return_book() followed by a.borrow() again to confirm it can be borrowed a second time.

Summary

  • An object bundles state (attributes) and behavior (methods) together under one identity.
  • A class is the blueprint; each object built from it is an instance, with its own independent copies of the attributes __init__ sets up.
  • self inside a method refers to the specific instance the method was called on — never every instance of the class.
  • Sharing a class means sharing the blueprint, not the state — calling a method on one instance never affects another.