Graeme
Elliott
HomeJournalArticlesPortfolio
HomeJournalArticlesPortfolio
HomeJournalArticlesPortfolio

© 2022–2026 Graeme Elliott

← Back to Articles
Misc

Object-Oriented Programming with Python: Introduction

Graeme Elliott
By Graeme Elliott26 April 2026 / 8 mins read
Object-Oriented Programming with Python: Introduction

Object-oriented programming (OOP) involves creating objects that can be referenced/represented in your application using a class or object.

Defining Classes and Objects

i

Classes: A blueprint for objects. Can contain methods (functions) and attributes (similar to keys in a dictionary)

Objects: An instance of a class blueprint that contains their class’s methods and properties.

OOP aims to encapsulate your code into logical hierarchical groupings using classes.

# Define a class named Car
class Car:
    # Class constructor to initialize the attributes
    def __init__(self, make, model, year):
        # Instance attributes
        self.make = make
        self.model = model
        self.year = year
    # Instance method
    def display_info(self):
        print(f"{self.year} {self.make} {self.model}")
# Create an object of the Car class
my_car = Car("Toyota", "Corolla", 2020)
# Use the object to call the method
my_car.display_info()  # Output: 2020 Toyota Corolla

When a new class is created, the def __init__ (self): method should also be created to initialise the class with a ‘self’ property. 

Instance Attributes

‘Self’ refers to the specific instance of the class you are working with. But you can include instance attributes within your class to define attributes that make up that class. Ensure you include a new parameter of that attribute in the init definition.

class User:
    def __init__(self, name):
        self.name = name

And then you can define an attribute within the class as a parameter.

class User:
    def __init__(self, name):
        self.name = name
user1 = User('John')
print(user1.name) # John

If there are other attributes required then provide data for each attribute, otherwise, you will get an error.

class User:
    def __init__(self, name, age, city):
        self.name = name
        self.age = age
        self.city = city
user1 = User('John', 18, 'Paris')
print(user1.name) # John
print(user1.age) # 18
print(user1.city) # Paris

How attributes and methods are defined in classes matters. If the attribute or method has a single underscore before it then this is a convention that means this is a private attribute or method. But it is not strictly private/secret as it can still be printed/displayed/operated. However, if there are two underscores before the property/method then this means that this is specific to a particular class based on inheritance. You cannot just print person.__msg as the name changes to being variable.class__nameOfProperty (person.Person__msg).

class Person:
    def __init__ (self):
        self.name = "Tony"
        self._secret = "hi!"
        self.__msg = "I like turtles"
p = Person()
print(p.name) # Tony
print(p._secret) # hi!
print(p.__msg) # AttributeError: 'Person' object has no attribute '__msg'
print(p._Person__msg) # I like turtles

Instance Methods

Classes can also contain instance methods that perform a function. They are called as any function would be called using the name of the function with parentheses afterwards.

class User:
    def __init__(self, firstname, surname, age, city):
        self.firstname = firstname
        self.surname = surname
        self.age = age
        self.city = city
    def full_name (self):
        return self.firstname + ' ' + self.surname
user1 = User('John', 'Doe', 18, 'Paris')
print(user1.full_name())

Class Attributes

You can also define attributes directly on a class once and they are shared by all instances of a class and the class itself.

Class attributes do not use ‘self’ and can be called/operated/updated but are linked specifically to the class itself. The below example updates the number of active users every time a user is created/initiated.

class User:
    # Class attribute
    active_users = 0
    def __init__(self, firstname, surname, age, city):
        # Instance attributes
        self.firstname = firstname
        self.surname = surname
        self.age = age
        self.city = city
        # Update the class attribute
        User.active_users += 1
    #Instance methods
    def full_name (self):
        return self.firstname + ' ' + self.surname
    def initials (self):
        return f"{self.firstname[0] + '.' + self.surname[0]+'.'}"
user1 = User('John', 'Doe', 18, 'Paris')
print(user1.full_name()) # John Doe
print(user1.initials()) # J.D.
print (User.active_users) # 1
user2 = User('Lucy', 'Bridge', 21, 'Paris')
print (User.active_users) # 2
class Pet:
    allowed = ['cat', 'dog', 'fish', 'bird']
    def __init__(self, name, species):
        if species not in Pet.allowed:
            raise ValueError(f"You can't have a {species} pet!")
        self.name = name
        self.species = species
    def set_species(self, species):
        if species not in Pet.allowed:
            raise ValueError(f"You can't have a {species} pet!")
        self.species = species
cat = Pet('Blue', 'cat')
dog = Pet('Wyatt', 'dog')
bird = Pet('Tweety', 'bird')
rat = Pet('Jerry', 'rat') # ValueError: You can't have a rat pet!

python-oop-class-attributes.png

Class Methods

Class methods are methods with the @classmethod decorator that are concerned with the class itself but not with instances.

class User:
    # Class attribute
    active_users = 0
    def __init__(self, firstname, surname, age, city):
        # Instance attributes
        self.firstname = firstname
        self.surname = surname
        self.age = age
        self.city = city
        # Update the class attribute
        User.active_users += 1
    #Instance methods
    def full_name (self):
        return self.firstname + ' ' + self.surname
    def initials (self):
        return f"{self.firstname[0] + '.' + self.surname[0]+'.'}"
    # Class method
    @classmethod
    def display_active_users(cls):
        return f"There are currently {cls.active_users} active users"
user1 = User('John', 'Doe', 18, 'Paris')
print(user1.full_name()) # John Doe
print(user1.initials()) # J.D.
print (User.active_users) # 1
user2 = User('Lucy', 'Bridge', 21, 'Paris')
print (User.active_users) # 2
print(User.display_active_users()) # There are currently 2 active users

__repr__ Method

The __repr__ method is a string representation of the class. This type of string representation is more for internal use (for example, for developers).

class User:
    # Class attribute
    active_users = 0
    def __init__(self, firstname, surname, age, city):
        # Instance attributes
        self.firstname = firstname
        self.surname = surname
        self.age = age
        self.city = city
        # Update the class attribute
        User.active_users += 1
    def __repr__ (self):
        return f"<{self.firstname}, {self.age}>"
    def __str__ (self):
        return f"{self.firstname} is {self.age}"
user1 = User('Lucy', 'Bridge', 21, 'Paris')
print (user1) # Lucy is 21

Inheritance

A key feature of OOP is the ability to define a class which inherits from another class (a ‘base’ or ‘parent’ class). The below code block has a base class of Animal and derived classes for two animals (Dog and Cat) that uses the base class’s attributes but the attribute has been made specific to them.

# Base class
class Animal:
    def __init__(self, name):
        self.name = name
    def speak(self):
        raise NotImplementedError("Subclass must implement abstract method")
# Derived class
class Dog(Animal):
    def speak(self):
        return f"{self.name} says Woof!"
# Derived class
class Cat(Animal):
    def speak(self):
        return f"{self.name} says Meow!"
# Creating instances of the derived classes
dog = Dog("Buddy")
cat = Cat("Whiskers")
# Calling the speak method on the instances
print(dog.speak())  # Output: Buddy says Woof!
print(cat.speak())  # Output: Whiskers says Meow!

Properties

The @property decorator in Python is used to define methods in a class that behave like attributes. It allows you to define getter, setter, and deleter methods for a class attribute.

The Getter Method (@property decorator) is used to define a method that gets the value of an attribute. This method can be accessed like an attribute, without parentheses.

The Setter Method (@property_name.setter decorator) is used to define a method that sets the value of an attribute. This allows you to include validation or other logic when setting the attribute's value.

The Deleter Method (@property_name.deleter decorator) is used to define a method that deletes an attribute. This is less commonly used but provides a way to manage attribute deletion.

class Person:
    def __init__(self, name):
        self._name = name
    @property
    def name(self):
        return self._name
    @name.setter
    def name(self, value):
        if not value:
            raise ValueError("Name cannot be empty")
        self._name = value
    @name.deleter
    def name(self):
        del self._name
# Usage
p = Person("Alice")
print(p.name)  # Accessing the getter: Output: Alice
p.name = "Bob"  # Using the setter
print(p.name)  # Output: Bob
del p.name  # Using the deleter

super()

super() in a class refers to the base/parent class. The below code has a base class of Animal and a derived class of Cat:

class Animal:
    def __init__(self, name, species):
        self.name = name
        self.species = species
    def __repr__(self):
        return f"{self.name} is a {self.breed} and loves to play with {self.toy}."
class Cat(Animal):
    def __init__(self, name, species, breed, toy):
        self.name = name
        self.species = species
        self.breed = breed
        self.toy = toy
blue = Cat('Blue', 'Cat', 'Scottish Fold', 'Ball')
print(blue) # Blue is a Scottish Fold and loves to play with Ball.

However, when creating the Cat class, all of the attributes have to be passed from the Animal class and defined in the Cat class. super() can be used to reference the Animal class and pass attributes down that are not specific to the Cat class.

class Animal:
    def __init__(self, name, species):
        self.name = name
        self.species = species
    def __repr__(self):
        return f"{self.name} is a {self.species}"
class Cat(Animal):
    def __init__(self, name, species, breed, toy):
        super().__init__(name, species)
        self.breed = breed
        self.toy = toy
blue = Cat('Blue', 'Cat', 'Scottish Fold', 'Ball')
print(blue) # Blue is a Scottish Fold and loves to play with Ball.

Methods can also be used when super() is inheriting from the base class.

class Animal:
    def __init__(self, name, species):
        self.name = name
        self.species = species
    def __repr__(self):
        return f"{self.name} is a {self.species}"
class Cat(Animal):
    def __init__(self, name, breed, toy):
        super().__init__(name, species="Cat")
        self.breed = breed
        self.toy = toy
    def play(self):
        print(f"{self.name} plays with {self.toy}")
blue = Cat('Blue', 'Scottish Fold', 'Ball')
print(blue) # Blue is a Scottish Fold and loves to play with Ball.
blue.play() # Blue plays with Ball.