Object-Oriented Programming

Python Intermediate — Object-Oriented Programming
Python Intermediate
Course 2 · Chapter 1 · Object-Oriented Programming

🏛️ Object-Oriented Programming

Course 1 worked entirely with Python's built-in types — strings, lists, dicts, and so on. This chapter starts Python Intermediate by defining your own types: classes, the __init__ constructor, the self parameter, and the difference between instance attributes and class attributes.

🧱 Defining a Class

class Dog: def __init__(self, name, breed): self.name = name self.breed = breed def bark(self): return f"{self.name} says Woof!" rex = Dog("Rex", "Labrador") print(rex.bark()) # Rex says Woof!

__init__ is the constructor — it runs automatically when Dog(...) is called, setting up whatever data a new object needs. By convention, class names use PascalCase, distinguishing them at a glance from the snake_case functions and variables you've used since Course 1.

👤 self Explained

self refers to the specific object a method is being called on — it's always the first parameter of every method, but you never pass it explicitly; Python fills it in automatically:

rex.bark() # Python actually calls this as: Dog.bark(rex) # bark(self) receives rex as self

The "this" Parameter: Go vs Kotlin vs Python

LanguageHow the current object is referenced
GoNo classes at all — a method has an explicit receiver, e.g. func (d Dog) Bark() string, conceptually similar to self but attached to the function signature differently, and structs/methods are separate constructs rather than one class.
Kotlinthis is implicit — never declared as a parameter, just used directly inside a method when needed, and usually omittable entirely (name instead of this.name).
Pythonself must be declared explicitly as the first parameter of every method, and used explicitly (self.name) to access instance attributes — nothing is implicit here.

Coming from Kotlin, writing out self everywhere feels verbose at first — it's a deliberate Python design choice ("explicit is better than implicit"), not an oversight.

📌 Instance Attributes vs Class Attributes

class Dog: species = "Canis familiaris" # CLASS attribute — shared by every Dog def __init__(self, name): self.name = name # INSTANCE attribute — unique per object rex = Dog("Rex") fido = Dog("Fido") print(rex.name, fido.name) # Rex Fido — different per instance print(rex.species, fido.species) # Canis familiaris Canis familiaris — same for both

An instance attribute (set via self.x = ..., usually in __init__) belongs to one specific object. A class attribute (defined directly in the class body) is shared by every instance of that class — change it via the class itself, and every instance sees the update.

⚠ Mutable Class Attributes Are Shared — And Mutating Them Leaks Across Instances
class Dog: tricks = [] # DANGER: one list, shared by every Dog def __init__(self, name): self.name = name def add_trick(self, trick): self.tricks.append(trick) # mutates the SHARED class list rex = Dog("Rex") fido = Dog("Fido") rex.add_trick("sit") print(fido.tricks) # ['sit'] — Fido "learned" Rex's trick!

This is the same root cause as Course 1's mutable-default-argument gotcha (Chapter 8) — a single mutable object gets shared where independent copies were expected. The fix: give each instance its own list inside __init__, with self.tricks = [], not as a class attribute.

class / __init__

class Name: defines a type; __init__ runs automatically on creation.

self

Explicit first parameter of every method — refers to the specific object being used.

Instance attributes

self.x = ... — unique data per object, usually set in __init__.

Class attributes

Defined in the class body — shared by every instance; avoid mutable ones.

💻 Coding Challenges

Challenge 1: Bank Account Class

Write a BankAccount class with an __init__ that takes owner and an optional balance (default 0), plus a deposit(amount) method that adds to the balance. Create an account and deposit twice, printing the final balance.

Goal: Practice defining a class with instance attributes and a method that modifies them.

→ Solution

Challenge 2: Class Attribute Counter

Write a Robot class with a class attribute count = 0 that increments by 1 every time a new Robot is created (inside __init__). Create three robots, then print the total count.

Goal: Practice a legitimate, safe use of a class attribute — one that's read/incremented as a number, not a shared mutable container.

→ Solution

Challenge 3: Fix the Shared-List Bug

Given the buggy Dog class from this chapter's warning box (with tricks = [] as a class attribute), rewrite it so each dog's tricks list is independent, and prove the fix by showing fido.tricks stays empty after rex.add_trick("sit").

Goal: Apply the fix for the shared-mutable-class-attribute gotcha directly.

→ Solution

🎯 What's Next

Next chapter: OOP Deep Dive — inheritance, polymorphism, and magic/dunder methods like __str__ and __eq__.