Cristhian Villegas
Backend11 min read1 views

OOP in Python: Classes, Objects and Inheritance for Beginners — Python Course #8

OOP in Python: Classes, Objects and Inheritance for Beginners — Python Course #8

Introduction: What Is OOP and Why Does It Matter

Python Logo

Welcome to part 8 of 10 of our Python course for beginners. Until now we have used functions and modules to organize our code. Now we are going to take an important leap: Object-Oriented Programming (OOP).

OOP is a way of thinking about and structuring your code based on objects. But do not worry, it is simpler than it sounds. Let us use an analogy:

📌 Analogy: Think of a cookie cutter. The cutter is the class: it defines the shape, size, and characteristics. Each cookie you make with that cutter is an object. All cookies have the same shape, but you can decorate each one differently (chocolate, vanilla, strawberry). The class is the blueprint, the object is the real thing.

Why is OOP important?

  • Better code organization: groups related data and functions in one place
  • Code reuse: you can create new classes based on ones that already exist
  • Models the real world: you can represent things like users, products, vehicles
  • It is a standard: almost all modern languages use OOP (Java, C#, JavaScript, etc.)

Create Your First Class with class

A class is created with the class keyword. By convention, class names are written in PascalCase (each word starts with a capital letter).

python
1# The simplest possible class
2class Dog:
3    pass  # "pass" means "do nothing for now"
4
5# Create an object (an instance of the class)
6my_dog = Dog()
7print(my_dog)        # <__main__.Dog object at 0x...>
8print(type(my_dog))  # <class '__main__.Dog'>

We just created an empty Dog class and a my_dog object. Now let us give it characteristics and behaviors.

Add attributes directly

python
1class Dog:
2    pass
3
4my_dog = Dog()
5
6# We can add attributes on the fly
7my_dog.name = "Rex"
8my_dog.breed = "Labrador"
9my_dog.age = 3
10
11print(f"{my_dog.name} is a {my_dog.breed}, {my_dog.age} years old")
12# Rex is a Labrador, 3 years old

This works, but it is not the correct way to do it. That is what the __init__ method is for.

The __init__ Method (Constructor): Initializing Attributes

The __init__ method is the constructor of the class. It runs automatically every time you create a new object. This is where you define the initial attributes.

python
1class Dog:
2    def __init__(self, name, breed, age):
3        self.name = name
4        self.breed = breed
5        self.age = age
6
7# Now when creating the object, we pass the data
8my_dog = Dog("Rex", "Labrador", 3)
9
10print(f"{my_dog.name} is a {my_dog.breed}, {my_dog.age} years old")
11# Rex is a Labrador, 3 years old
12
13# We can create many objects with the same class
14another_dog = Dog("Luna", "Golden Retriever", 5)
15print(f"{another_dog.name} is a {another_dog.breed}, {another_dog.age} years old")
16# Luna is a Golden Retriever, 5 years old

Let us break down what happened:

  • __init__ runs automatically when you call Dog("Rex", "Labrador", 3)
  • self is a reference to the object being created (we will explain this in depth in the next section)
  • self.name = name saves the value "Rex" as an attribute of the object
💡 Tip: You can assign default values to constructor parameters, just like with regular functions:
python
1class Dog:
2    def __init__(self, name, breed="Mixed", age=1):
3        self.name = name
4        self.breed = breed
5        self.age = age
6
7# Just with the name
8dog1 = Dog("Buddy")
9print(f"{dog1.name}, {dog1.breed}, {dog1.age} year old")
10# Buddy, Mixed, 1 year old

self: The Reference to the Current Object

The self parameter is probably what confuses beginners the most. But it is simple:

self is a reference to the object that is using the method. When you write self.name, you are saying "the name attribute of this particular object".

python
1class Cat:
2    def __init__(self, name):
3        self.name = name   # self.name = attribute of the object
4                            # name = parameter we passed
5
6    def introduce(self):
7        # Here self refers to the specific cat that calls the method
8        print(f"Meow, I am {self.name}")
9
10# Create two cats
11cat1 = Cat("Whiskers")
12cat2 = Cat("Fluffy")
13
14cat1.introduce()  # Meow, I am Whiskers
15cat2.introduce()  # Meow, I am Fluffy

When you call cat1.introduce(), Python automatically passes cat1 as self. That is why you do not need to write cat1.introduce(cat1).

📌 Important rule: self must always be the first parameter of all methods in a class. Python passes it automatically; you never write it when calling the method.

Methods: Functions Inside a Class

Methods are functions that belong to a class. They define what an object can do.

python
1class BankAccount:
2    def __init__(self, owner, balance=0):
3        self.owner = owner
4        self.balance = balance
5
6    def deposit(self, amount):
7        """Add money to the account."""
8        if amount > 0:
9            self.balance += amount
10            print(f"Deposited {amount}. New balance: {self.balance}")
11        else:
12            print("Amount must be positive")
13
14    def withdraw(self, amount):
15        """Withdraw money from the account."""
16        if amount > self.balance:
17            print(f"Insufficient funds. You have {self.balance}")
18        elif amount <= 0:
19            print("Amount must be positive")
20        else:
21            self.balance -= amount
22            print(f"Withdrew {amount}. New balance: {self.balance}")
23
24    def check_balance(self):
25        """Show the current balance."""
26        print(f"Owner: {self.owner}")
27        print(f"Current balance: {self.balance}")
28
29# Create an account and use it
30account = BankAccount("Ana Lopez", 1000)
31account.check_balance()
32# Owner: Ana Lopez
33# Current balance: $1000
34
35account.deposit(500)
36# Deposited $500. New balance: $1500
37
38account.withdraw(200)
39# Withdrew $200. New balance: $1300
40
41account.withdraw(5000)
42# Insufficient funds. You have $1300

Notice how each method uses self.balance to access and modify the balance of the specific object. If you create another account, it will have its own independent balance.

Inheritance: Create a Class from Another

Inheritance lets you create a new class that inherits all the attributes and methods from another class. It is like saying: "A Dog is an Animal, but with additional features".

python
1class Animal:
2    """Base (parent) class for all animals."""
3
4    def __init__(self, name, species):
5        self.name = name
6        self.species = species
7        self.energy = 100
8
9    def eat(self):
10        self.energy += 10
11        print(f"{self.name} is eating. Energy: {self.energy}")
12
13    def sleep(self):
14        self.energy += 20
15        print(f"{self.name} is sleeping. Energy: {self.energy}")
16
17    def info(self):
18        print(f"{self.name} ({self.species}) - Energy: {self.energy}")
19
20
21class Dog(Animal):
22    """Child class that inherits from Animal."""
23
24    def __init__(self, name, breed):
25        # super() calls the __init__ of the parent class (Animal)
26        super().__init__(name, species="Canine")
27        self.breed = breed
28
29    def bark(self):
30        self.energy -= 5
31        print(f"{self.name} says: Woof woof!")
32
33    def fetch_ball(self):
34        self.energy -= 15
35        print(f"{self.name} fetches the ball. Energy: {self.energy}")
36
37
38class Cat(Animal):
39    """Another child class that inherits from Animal."""
40
41    def __init__(self, name, color):
42        super().__init__(name, species="Feline")
43        self.color = color
44
45    def meow(self):
46        self.energy -= 3
47        print(f"{self.name} says: Meow!")
48
49    def purr(self):
50        print(f"{self.name} is purring... prrr")
51
52
53# Use the classes
54rex = Dog("Rex", "Labrador")
55rex.info()          # Rex (Canine) - Energy: 100  (inherited from Animal)
56rex.eat()           # Rex is eating. Energy: 110  (inherited from Animal)
57rex.bark()          # Rex says: Woof woof!  (Dog's own method)
58rex.fetch_ball()    # Rex fetches the ball. Energy: 90
59
60whiskers = Cat("Whiskers", "orange")
61whiskers.info()     # Whiskers (Feline) - Energy: 100
62whiskers.sleep()    # Whiskers is sleeping. Energy: 120
63whiskers.meow()     # Whiskers says: Meow!
64whiskers.purr()     # Whiskers is purring... prrr

Key points about inheritance:

  • class Dog(Animal) — the parentheses indicate who it inherits from
  • super().__init__(...) — calls the parent constructor to initialize inherited attributes
  • The child class has everything from the parent plus its own attributes and methods
  • The child class can override parent methods if it needs different behavior
💡 Tip: Use inheritance when there is a clear "is a" relationship: a Dog is an Animal, a Student is a Person. If the relationship is "has a" (a Car has an Engine), use composition instead (an attribute that is another object).

Basic Encapsulation: Public and Private Attributes

Encapsulation is the idea of protecting an object's internal data so it cannot be accidentally modified from outside. In Python, this is done by convention (not by language restriction).

ConventionMeaningExample
namePublic: anyone can accessself.name = "Rex"
_nameProtected: should not be accessed from outside (convention)self._balance = 1000
__namePrivate: Python changes the name internally (name mangling)self.__password = "123"
python
1class User:
2    def __init__(self, name, email, password):
3        self.name = name            # Public
4        self._email = email         # Protected (by convention)
5        self.__password = password  # Private (name mangling)
6
7    def verify_password(self, attempt):
8        """Verify the password without exposing the actual value."""
9        return self.__password == attempt
10
11    def show_info(self):
12        # Inside the class we CAN access private attributes
13        print(f"Name: {self.name}")
14        print(f"Email: {self._email}")
15        print(f"Password: {'*' * len(self.__password)}")
16
17
18user = User("Ana", "[email protected]", "myKey123")
19
20# Public: free access
21print(user.name)     # Ana
22
23# Protected: works, but you SHOULD NOT use it outside the class
24print(user._email)   # [email protected]
25
26# Private: gives an error if you try to access directly
27# print(user.__password)  # AttributeError!
28
29# The correct way is to use a method
30print(user.verify_password("myKey123"))   # True
31print(user.verify_password("wrongKey"))   # False
⚠️ Note: Python does not have truly private attributes like Java or C#. The underscore is a convention that says "please do not touch this from outside". Good programmers respect that convention.

Special Methods: __str__, __repr__, __len__

Python has special methods (also called "magic methods" or "dunder methods") that you can define to customize how your objects behave.

python
1class Product:
2    def __init__(self, name, price, stock):
3        self.name = name
4        self.price = price
5        self.stock = stock
6
7    def __str__(self):
8        """Called when you use print() or str()."""
9        return f"{self.name} - {self.price} ({self.stock} units)"
10
11    def __repr__(self):
12        """Called in the interactive console or when debugging."""
13        return f"Product('{self.name}', {self.price}, {self.stock})"
14
15    def __len__(self):
16        """Called when you use len()."""
17        return self.stock
18
19    def __eq__(self, other):
20        """Called when you compare with ==."""
21        if not isinstance(other, Product):
22            return False
23        return self.name == other.name and self.price == other.price
24
25
26# Without __str__, print would show something like: <__main__.Product object at 0x...>
27product = Product("Laptop", 999.99, 15)
28
29print(product)        # Laptop - $999.99 (15 units)  (__str__)
30print(repr(product))  # Product('Laptop', 999.99, 15)  (__repr__)
31print(len(product))   # 15  (__len__)
32
33# Compare products
34p1 = Product("Mouse", 25.00, 100)
35p2 = Product("Mouse", 25.00, 50)
36p3 = Product("Keyboard", 45.00, 30)
37
38print(p1 == p2)  # True  (same name and price)
39print(p1 == p3)  # False (different name)

The most common special methods:

MethodTriggered byUse
__str__print(obj), str(obj)Human-readable representation
__repr__Interactive console, repr(obj)Technical representation for developers
__len__len(obj)Return a length/size
__eq__obj1 == obj2Compare equality
__lt__obj1 < obj2Compare less than
__add__obj1 + obj2Add objects

Practical Example: Library Management System

Let us combine everything we learned in a complete example: a system to manage books and loans for a library.

python
1from datetime import date, timedelta
2
3
4class Book:
5    """Represents a book in the library."""
6
7    def __init__(self, title, author, isbn, copies=1):
8        self.title = title
9        self.author = author
10        self.isbn = isbn
11        self.copies = copies
12        self._available = copies
13
14    def is_available(self):
15        """Check if there are copies available."""
16        return self._available > 0
17
18    def lend(self):
19        """Lend a copy of the book."""
20        if self.is_available():
21            self._available -= 1
22            return True
23        return False
24
25    def return_book(self):
26        """Return a copy of the book."""
27        if self._available < self.copies:
28            self._available += 1
29            return True
30        return False
31
32    def __str__(self):
33        return f"'{self.title}' by {self.author} ({self._available}/{self.copies} available)"
34
35    def __repr__(self):
36        return f"Book('{self.title}', '{self.author}', '{self.isbn}')"
37
38
39class Member:
40    """Represents a library member."""
41
42    def __init__(self, name, member_id):
43        self.name = name
44        self.member_id = member_id
45        self.borrowed_books = []
46
47    def has_book(self, book):
48        """Check if the member has a borrowed book."""
49        return any(b["book"].isbn == book.isbn for b in self.borrowed_books)
50
51    def __str__(self):
52        return f"Member: {self.name} (ID: {self.member_id}) - {len(self.borrowed_books)} books borrowed"
53
54
55class Library:
56    """Library management system."""
57
58    def __init__(self, name):
59        self.name = name
60        self._books = []
61        self._members = []
62        self._history = []
63
64    def add_book(self, book):
65        """Add a book to the catalog."""
66        self._books.append(book)
67        print(f"Book added: {book.title}")
68
69    def register_member(self, member):
70        """Register a new member."""
71        self._members.append(member)
72        print(f"Member registered: {member.name}")
73
74    def lend_book(self, member, book):
75        """Lend a book to a member."""
76        if not book.is_available():
77            print(f"'{book.title}' is not available")
78            return False
79
80        if member.has_book(book):
81            print(f"{member.name} already has '{book.title}'")
82            return False
83
84        book.lend()
85        due_date = date.today() + timedelta(days=14)
86        loan = {
87            "book": book,
88            "loan_date": date.today(),
89            "due_date": due_date,
90        }
91        member.borrowed_books.append(loan)
92        self._history.append({
93            "type": "loan",
94            "member": member.name,
95            "book": book.title,
96            "date": date.today(),
97        })
98        print(f"Loan successful: '{book.title}' to {member.name}")
99        print(f"  Return before: {due_date}")
100        return True
101
102    def return_book(self, member, book):
103        """Return a borrowed book."""
104        for i, loan in enumerate(member.borrowed_books):
105            if loan["book"].isbn == book.isbn:
106                book.return_book()
107                member.borrowed_books.pop(i)
108                self._history.append({
109                    "type": "return",
110                    "member": member.name,
111                    "book": book.title,
112                    "date": date.today(),
113                })
114                print(f"Return successful: '{book.title}' from {member.name}")
115                return True
116
117        print(f"{member.name} does not have '{book.title}'")
118        return False
119
120    def search_book(self, term):
121        """Search books by title or author."""
122        results = [
123            book for book in self._books
124            if term.lower() in book.title.lower()
125            or term.lower() in book.author.lower()
126        ]
127        return results
128
129    def show_catalog(self):
130        """Show all books in the catalog."""
131        print(f"\n=== {self.name} Catalog ===")
132        for book in self._books:
133            status = "Available" if book.is_available() else "Not available"
134            print(f"  {book} - {status}")
135        print()
136
137
138# === Testing the system ===
139
140# Create the library
141library = Library("Central Library")
142
143# Add books
144book1 = Book("One Hundred Years of Solitude", "Gabriel Garcia Marquez", "978-0307474728", copies=3)
145book2 = Book("The Little Prince", "Antoine de Saint-Exupery", "978-0156012195", copies=2)
146book3 = Book("Don Quixote", "Miguel de Cervantes", "978-0060934347", copies=1)
147
148library.add_book(book1)
149library.add_book(book2)
150library.add_book(book3)
151
152# Register members
153member1 = Member("Maria Garcia", "M001")
154member2 = Member("Juan Lopez", "M002")
155
156library.register_member(member1)
157library.register_member(member2)
158
159# Show catalog
160library.show_catalog()
161
162# Lend books
163library.lend_book(member1, book1)
164library.lend_book(member2, book1)
165library.lend_book(member1, book2)
166
167# Check status
168library.show_catalog()
169print(member1)
170print(member2)
171
172# Return a book
173library.return_book(member1, book1)
174library.show_catalog()
175
176# Search books
177print("Search 'quixote':")
178for book in library.search_book("quixote"):
179    print(f"  {book}")
💡 Tip: This example uses the concepts of classes, methods, encapsulation (attributes with _), and relationships between objects. In a real project you would also save the data in a database, but the class structure would be very similar.

Summary and Next Article

In this article you learned the fundamentals of Object-Oriented Programming in Python:

  • Classes: blueprints for creating objects, defined with class
  • __init__: the constructor that initializes the object's attributes
  • self: the reference to the current object inside methods
  • Methods: functions that belong to a class and define its behavior
  • Inheritance: creating new classes based on existing classes
  • Encapsulation: protecting internal data with the _name convention
  • Special methods: __str__, __repr__, __len__ to customize behavior

In the next article (part 9 of 10) we will learn about file handling and exceptions: how to read and write files, handle errors gracefully with try/except, and create more robust programs. Do not miss it!

Share:
CV

Cristhian Villegas

Software Engineer specializing in Java, Spring Boot, Angular & AWS. Building scalable distributed systems with clean architecture.

Comments

Sign in to leave a comment

No comments yet. Be the first!

Related Articles