PyComplete Python Course
Advanced 70 min

Module 14 — Object-Oriented Programming

Modeling real things as classes and objects — the backbone of large Python programs.

Prerequisite: Module 13 — Generators

After this lesson, you will be able to

  • Define a class with an __init__ constructor and instance methods
  • Explain the difference between instance variables and class variables
  • Use inheritance to share behavior between related classes
  • Use @property and basic magic methods like __str__

Concept & Syntax

Object-oriented programming (OOP) organizes code around objects — bundles of data (attributes) and behavior (methods) modeled on real-world things. A class is the blueprint; an object (or instance) is one specific thing built from that blueprint. Every Dog object shares the same blueprint, but each has its own name, age, and so on.

self is simply how a method refers to "the particular object I was called on." It's always the first parameter of an instance method, and Python passes it automatically — you never supply it yourself when calling the method.

Syntax
class ClassName:
    def __init__(self, attribute):
        self.attribute = attribute
 
    def method(self):
        return self.attribute
 
obj = ClassName("value")

Examples

Your first class Very Easy

Your first class
class Dog:
    def __init__(self, name):
        self.name = name
 
    def bark(self):
        return self.name + " says Woof!"
 
rex = Dog("Rex")
print(rex.bark())
OutputRex says Woof!

__init__ runs automatically when Dog("Rex") is called, storing "Rex" as this object's name attribute.

Multiple objects, independent state Easy

Multiple objects, independent state
rex = Dog("Rex")
fido = Dog("Fido")
print(rex.bark())
print(fido.bark())
OutputRex says Woof! Fido says Woof!

Each object keeps its own separate copy of name — that's what 'instance' variables means.

Class variables vs instance variables Intermediate

Class variables vs instance variables
class Dog:
    species = "Canis familiaris"  # class variable — shared by all dogs
 
    def __init__(self, name):
        self.name = name  # instance variable — unique per dog
 
rex = Dog("Rex")
fido = Dog("Fido")
print(rex.species, fido.species)
print(rex.name, fido.name)
OutputCanis familiaris Canis familiaris Rex Fido

species is defined once on the class and shared by every instance; name is set per-object inside __init__.

Inheritance Real World

Inheritance
class Animal:
    def __init__(self, name):
        self.name = name
    def speak(self):
        return f"{self.name} makes a sound."
 
class Dog(Animal):
    def speak(self):
        return f"{self.name} barks."
 
rex = Dog("Rex")
print(rex.speak())
OutputRex barks.

class Dog(Animal) means Dog inherits everything from Animal, then overrides speak() with its own version — this is called method overriding.

@property and __str__ Challenge

@property and __str__
class Circle:
    def __init__(self, radius):
        self.radius = radius
 
    @property
    def area(self):
        return 3.14159 * self.radius ** 2
 
    def __str__(self):
        return f"Circle(radius={self.radius})"
 
c = Circle(4)
print(c)
print(c.area)
OutputCircle(radius=4) 50.26544

@property lets you call c.area like a plain attribute even though it's computed. __str__ controls what print() shows for an object.

Visual Explanation

AnimalDogGolden Retriever
Inheritance forms a chain: Dog inherits from Animal, and a more specific class could inherit from Dog.
🔍 What happens internally? (advanced)

Under the hood, Python compiles your source into bytecode and runs it on a virtual machine (the CPython interpreter). Variables are names in a namespace dictionary pointing at objects in memory — which is exactly what the memory panel in the tracer above is showing you.

Watch an object get created and used

Use the controls below to run this example one line at a time and watch the interpreter's memory update live.

Code

Code
class Dog:
    def __init__(self, name):
        self.name = name
 
    def bark(self):
        return self.name + " says Woof!"
 
rex = Dog("Rex")
print(rex.bark())

Interpreter state

Console output

Common Mistakes

✗ Common mistake 1

Forgetting self as the first parameter of a method is one of the most common OOP errors for beginners.

✗ Avoid this
This raises TypeError: bark() takes 0 positional arguments but 1 was given
class Dog:
    def bark():
        return "Woof!"
 
rex = Dog()
rex.bark()
✓ Better approach
Fixed
class Dog:
    def bark(self):
        return "Woof!"
 
rex = Dog()
rex.bark()
✗ Common mistake 2

Confusing a class variable with an instance variable can cause values to be unexpectedly shared across every object.

✗ Avoid this
This raises not a crash, but every Cart object ends up sharing the same items list
class Cart:
    items = []  # class variable — shared by ALL carts!
 
    def add(self, item):
        self.items.append(item)
✓ Better approach
Fixed
class Cart:
    def __init__(self):
        self.items = []  # instance variable — one per cart
 
    def add(self, item):
        self.items.append(item)
✓ Best practices
  • Name classes in CapitalizedWords (PascalCase): Dog, ShoppingCart, not dog or shopping_cart.
  • Set instance-specific data inside __init__ using self, not as a bare class-level variable.
  • Favor composition or simple inheritance chains over deep, multi-level inheritance hierarchies.
  • Add __str__ to classes you plan to print, so debugging output is readable.

Exercise & Challenge

Exercise 1 — Predict the output

Given this code
class Cat:
    def __init__(self, name, age):
        self.name = name
        self.age = age
 
whiskers = Cat("Whiskers", 3)
print(whiskers.age)

What does this print?

🏆 Challenge

Model a BankAccount class with a balance, a deposit(amount) method, and a withdraw(amount) method that refuses to overdraw the account.

  • Store balance as an instance variable, starting at 0
  • deposit() should increase balance
  • withdraw() should print an error message instead of allowing a negative balance
💡 Need a hint?

Check `if amount > self.balance:` before subtracting inside withdraw().

🔍 Show a sample solution
Python
class BankAccount:
    def __init__(self):
        self.balance = 0
 
    def deposit(self, amount):
        self.balance += amount
 
    def withdraw(self, amount):
        if amount > self.balance:
            print("Insufficient funds")
        else:
            self.balance -= amount
 
acc = BankAccount()
acc.deposit(100)
acc.withdraw(30)
print(acc.balance)

Quiz

Answer every question, then submit to see your score and explanations.

1. What is self inside an instance method?

A global variable
A reference to the specific object the method was called on
A required keyword
The class name
self refers to the current instance and is passed automatically by Python.

2. What is the difference between a class variable and an instance variable?

No difference
Class variables are shared across all instances; instance variables are unique per object
Instance variables are shared; class variables are unique
Class variables can't hold numbers
Instance variables (set via self.x = ...) are per-object; class variables are shared.

3. What does class Dog(Animal) mean?

Dog and Animal are unrelated
Dog inherits attributes and methods from Animal
Animal inherits from Dog
This is invalid syntax
Dog(Animal) means Dog is a subclass that inherits from the Animal superclass.

4. What does __init__ do?

Deletes an object
Runs automatically when an object is created, to set it up
Prints an object
Compares two objects
__init__ is the constructor, called automatically when you create a new object.

5. What does @property allow you to do?

Make a method callable like a plain attribute
Delete a class
Skip writing __init__
Create a global variable
@property lets a method be accessed without parentheses, as if it were an attribute.

Summary

  • A class is a blueprint; an object is one instance built from it.
  • self refers to the current object inside an instance method.
  • Instance variables are per-object; class variables are shared by all instances.
  • Inheritance (class Dog(Animal)) lets a subclass reuse and override a superclass's behavior.

Related lessons

Saved privately in your browser — no account needed.