Skip to content

Object-Oriented Programming

By the end of this lesson you'll be able to:

  • Define a class with __init__(), self, attributes, and methods
  • Create class instances and access their attributes with dot notation
  • Define a __str__() method so print() shows a useful description
  • Use inheritance and super() to build subclasses that extend parent classes

You've been using objects all along — turtle, strings, lists, and dictionaries are all objects. Now it's time to create your own.

Welcome to Chapter 25!

Monty waving welcome You've used objects like Lego bricks — now you get to design your own bricks! Object-oriented programming is how most large Python programs are organized. Let's build something! Let's code it together!

Attributes and Methods

You already know that objects have attributes (data stored inside them) and methods (functions you call on them).

1
2
3
t = turtle.Turtle()
t.speed(5)          # method call
t.pencolor("red")   # method call — sets an attribute

Now you'll define your own objects.

The class Keyword

A class is a blueprint for creating objects. Use class ClassName: followed by indented code that defines the class. By convention, class names use CamelCase (each word capitalized, no underscores).

1
2
class Dog:
    pass   # empty class — just a placeholder for now

__init__() — The Constructor

__init__() is the constructor — a special method Python calls automatically when you create a new instance. It sets up the object's initial attributes.

self refers to the specific instance being created. Every method in a class takes self as its first parameter.

Before we define attributes, note that self.attribute_name = value stores a value inside the object:


  

Creating Instances and Dot Notation

Each time you call the class like a function, Python creates a new instance (a separate object from the same blueprint).

1
2
fido  = Dog("Fido", "Labrador", 3)   # instance 1
bella = Dog("Bella", "Poodle", 5)    # instance 2

Access attributes and methods with dot notation: fido.name, fido.bark().

What Do You Think Will Happen?

Monty thinking The code below creates two Student instances. Which student has the higher score? What will the loop print for each one? Make your guess — then run it!


  

See It: Blueprint vs Real Dogs

What Do You Think Will Happen?

Monty thinking In the inspector below, the program defines class Dog and then calls Dog(...) twice. Predict: when rex.bark() runs, how does Python know which dog's name to print — Rex's or Bella's? Step through and watch the orange arrow!

Explore the Object Instance Inspector MicroSim

The answer: self is always the object left of the dot. The class is just a dashed blueprint — the solid cards are the real objects, each with its own attribute boxes. The last step shows one more secret: rex.species is not on rex's card at all, so Python looks up to the blueprint, where the shared class variable lives.

__str__() — String Representation

__str__() defines what print(obj) and str(obj) show. Without it, print(fido) shows something like <__main__.Dog object at 0x...> — not very helpful.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Dog:
    def __init__(self, name, breed):
        self.name  = name
        self.breed = breed

    def __str__(self):
        return f"Dog({self.name}, {self.breed})"

fido = Dog("Fido", "Labrador")
print(fido)   # Dog(Fido, Labrador)

__repr__() is similar but meant for developers — it should ideally show code you could paste to recreate the object.

Class Variables vs Instance Variables

An instance variable (defined with self.) belongs to one specific object. A class variable (defined directly in the class body, not inside any method) is shared by all instances.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Counter:
    count = 0   # class variable — shared by all instances

    def __init__(self):
        Counter.count += 1   # increment the shared counter

a = Counter()
b = Counter()
c = Counter()
print(Counter.count)   # 3 — three instances created

Strings and Lists as OOP Examples

You've been using OOP all along — string and list methods are just methods on objects:

1
2
3
4
5
name = "monty"
name.upper()       # calls the upper() method on the string object

items = [3, 1, 2]
items.sort()       # calls the sort() method on the list object

Inheritance

Inheritance lets you create a new class (subclass) that inherits all the attributes and methods of an existing class (parent class).

Use class SubClass(ParentClass): to declare inheritance. The super() function calls the parent class's __init__() so you don't have to repeat all its setup code.


  

Method Overriding

A subclass can override a parent method by defining a method with the same name. The subclass version runs instead of the parent's.

1
2
3
class Dog(Animal):
    def speak(self):
        print(f"{self.name} barks loudly!")   # overrides Animal.speak

See It: Watch the Method Lookup Climb

When you call d.eat() on a Dog, Python quietly searches: first in class Dog, and if the method is not there, up in class Animal. Before you click anything in the explorer below, predict: which of the three calls — d.fetch(), d.speak(), d.eat() — needs to climb up to Animal?

Explore the Inheritance Explorer MicroSim

Click all three calls and watch the highlights climb. Notice what happens after d.speak(): Animal's own speak() is marked shadowed — Dog's version won, exactly like the override example above. The last button shows super(), the polite way for a child class to run the parent's version anyway.

Learning Check

Your Turn — Add a Method

Monty thinking The Rectangle class below has width and height attributes but is missing two methods. Add area() (returns width × height) and perimeter() (returns 2 × (width + height)).


  

Experiments

Try these changes. Predict what will happen first, then run it to check!

  1. Add a birthday() method to the Dog class that increments self.age by 1. Call it twice and print the age. You'll know it worked when fido.age increases by 2 after two birthday calls.

  2. Create a Square(Rectangle) subclass where __init__ only takes side and calls super().__init__(side, side). You'll know it worked when Square(5).area() returns 25.

  3. Add a __repr__() to Dog that returns f"Dog(name={self.name!r}, age={self.age})". You'll know it worked when using repr(fido) shows a reconstructable string.

  4. Create a class BankAccount with balance = 0, a deposit(n) method, and a withdraw(n) method that refuses if the balance would go negative. You'll know it worked when withdrawing more than the balance prints an error and balance stays unchanged.

  5. Create a list of three Student objects and sort them by score using sorted(students, key=lambda s: s.score). You'll know it worked when the students appear in ascending score order.

OOP Expert!

Monty celebrating You've learned to design your own objects with classes, inheritance, and special methods! OOP is the foundation of almost all professional Python code — from web frameworks to game engines. You're building real software skills. Let's keep coding!

Take the Chapter Review Quiz

See Annotated References