Object-oriented programming (OOP) is a style of programming that heavily relies on objects. These objects can have attributes and methods. While attributes store data, methods define behavior.

Like many other programming languages, Python supports both OOP and functional programming. However, OOP becomes valuable when writing large-sized and complex programs.

In this article, you will learn the benefits of OOP in Python, how to define a class, class and instance attributes, and instance methods. You will also learn the concept of encapsulation and how to implement inheritance between classes in Python.

To fully understand this article, you should have the following prerequisites:

  • Basic knowledge of the Python programming language.
  • Familiarity with the concepts of functions and objects in programming.
  • Experience working with a code editor or integrated development environment (IDE).
  • Basic understanding of how to use a command-line terminal or console.

Now let's dive in.

Why Use Object-Oriented Programming?

Object-oriented programming (OOP) offers several benefits when organizing and managing code. By grouping related data and functions into logical classes, OOP promotes code structure and simplifies maintenance, especially as programs grow in size and complexity. The modular approach makes it easier to understand, modify, and reuse code, thereby reducing development time.

Another benefit of OOP is its ability to provide a clear and relatable programming style, which can be more helpful for developers. The use of objects and the relationships between them mirror real-world concepts. This makes it easier to reason about code and design complex systems.

Finally, OOP's concepts such as encapsulation and inheritance, contribute to code robustness by promoting data protection and code reusability.

What is a Class in Python?

OOP in Python heavily relies on the concept of class. You can think of a class as a blueprint that is used to create objects. To illustrate this, imagine that you have a blueprint for a speaker. You can use this blueprint to build multiple speakers. Each speaker that is created using the blueprint is an instance of the blueprint. Also, each created speaker has its attributes such as color, model, and name. They will also have their methods showing a certain kind of behavior such as volume up and volume down.

How to Define a Class in Python

To define a class, you have to use the class keyword, provided by Python, then followed by the name of the class and a colon:

class ClassName:
    pass

Now using our earlier illustration, let’s create a class named Speaker. In your code editor, create a file named classes.py and copy the following lines of code into it:

class Speaker:
    pass

speaker_one = Speaker()
print(speaker_one)

In the preceding code, you defined a class named Speaker without any attributes or methods. You used the keyword pass in the class body. In Python, the pass keyword does nothing and is usually used as a placeholder.

In line four of the code, you created an instance of the Speaker class and assigned it to the variable speaker_one. This process is also known as instantiating an object from a class. You then printed speaker_one in line five.

Now run the code by running the command python classes.py in your terminal. You will get an output similar to this in your terminal:

<__main__.Speaker object at 0x10087f8e0>

The output shows that speaker_one is an object. 0x10087f8e0 in the output shows the memory address where Python stores the object on your computer.

The memory address in your terminal output will be different.

You can create several more instances from the Speaker class and they will all be different from one another. To verify this, let’s use the equality comparison (==) operator. In your classes.py file, delete print(speaker_one) and add the following to the bottom of your code:

speaker_two = Speaker()

if speaker_one == speaker_two:
    print("speaker one is the same as speaker two")
else:
    print("speaker one is different from speaker two")

Now run python classes.py in your terminal. You will get the following output:

speaker one is different from speaker two

Class and Instance Attributes

In this section, you'll add attributes to your Speaker class. You can see attributes as data stored within an object. While class attributes are common to all instances created from your class, instance attributes are unique to each instance.

Now modify your classes.py file by replacing your code with the following:

class Speaker:
    brand = "Beatpill" # class attribute

    def __init__(self, color, model):
        self.color = color # instance attribute
        self.model = model # instance attribute

You should already be familiar with line one of your new code. In line two, you defined a class attribute, named brand, and assigned it the value Beatpill. Class attributes are variables that belong to the class itself, rather than to individual instances of the class. The effect of class attributes is that all instances you create from your class will inherit and share that class attribute and its value. In this case, every instance you create from your Speaker class will share the same brand value.

Line four of your code defines an __init__ method, which takes in three parameters—self, color, and model. This method will be called anytime you create a new instance from your Speaker class. The self parameter is a reference to the Speaker class and it's a convention in Python to always have it as the first parameter in a __init__ method. Line five and six set instance attributes, color, and model to your Speaker class.

So, every time you create a new instance from your class, you have to provide arguments for your color and model attributes. Not doing this will result in an error.

Now add the following to the bottom of your code:

speaker_one = Speaker("black", "85XB5")
speaker_two = Speaker("red", "Y8F33")

print(f"speaker one is {speaker_one.color} while speaker two is {speaker_two.color}")

By adding the preceding code, you created two instances of the Speaker class—speaker_one and speaker_two. The arguments in each instance represent the values of their color and model attributes. You then printed a message that displays the color attribute of each object. Now, if you run the command python classes.py in your terminal, you will get the output:

speaker one is black while speaker two is red

To see that both instances share the same brand class attribute, modify your print() function like this:

print(
    f"speaker one is {speaker_one.color} while speaker two is {speaker_two.color}",
    speaker_one.brand,
    speaker_two.brand,
    sep="\n",
)

Now run python classes.py and you will get the following output:

speaker one is black and speaker two is red
Beatpill
Beatpill

Instance Methods

In addition to class and instance attributes, classes can also have methods, known as instance methods. Instance methods are functions defined within a class that operate on instances of the class. They use the class and instance attributes to provide behavior and functionality to the objects.

To add instance methods, modify your code file as follows:

class Speaker:
    brand = "Beatpill"

    def __init__(self, color, model):
        self.color = color
        self.model = model

    def power_on(self):
        print(f"Powering on {self.color} {self.model} speaker.")

    def power_off(self):
        print(f"Powering off {self.color} {self.model} speaker.")


speaker_one = Speaker("black", "85XB5")
speaker_two = Speaker("red", "Y8F33")

In the example above, we've added two instance methods—power_on() and power_off(). These methods, just like the __init__ method, take self as the first argument.

The power_on() method prints a message indicating that the speaker of the given color and model is being powered on. Similarly, the power_off() method prints a message about powering off the speaker.

To call these methods, add the following to the bottom of your file:

speaker_one.power_on()
speaker_two.power_off()

Now run python classes.py in your terminal and you will get the following output:

Powering on black 85XB5 speaker.
Powering off red Y8F33 speaker.

Encapsulation in Python

Encapsulation is one of the core concepts of OOP. It refers to the bundling of data (attributes) and methods within a class. Encapsulation provides data protection and control over how the code interacts with an object's internal state.

You can achieve encapsulation in Python by defining private attributes and methods within a class. By convention, private attributes and methods are prefixed with a single underscore (_). While Python does not have strict private modifiers like some other languages, the underscore prefix serves as a warning to other developers not to access or modify the attributes and methods directly from outside the class.

Here's an example of encapsulation in Python:

class Speaker:
    brand = "Beatpill"

    def __init__(self, color, model):
        self._color = color  # Private attribute
        self._model = model  # Private attribute

    def power_on(self):
        print(f"Powering on {self._color} {self._model} speaker.")

    def power_off(self):
        print(f"Powering off {self._color} {self._model} speaker.")

    def get_color(self):
        return self._color

    def set_color(self, new_color):
        self._color = new_color


speaker_one = Speaker("black", "85XB5")
speaker_one.power_on()

# Accessing a private attribute directly (not recommended)
print(speaker_one._color)

# Accessing a private attribute using the getter method (recommended)
print(speaker_one.get_color())

# Modifying a private attribute using the setter method (recommended)
speaker_one.set_color("white")
print(speaker_one.get_color())

In the preceding example, the color and model attributes are private attributes of the Speaker class. Although it is possible to access and modify these attributes directly from outside the class by using, for example, print(speaker_one._color), this practice is discouraged. Doing so violates encapsulation and can lead to unintended behavior or data corruption.

Instead, the class provides getter get_color() and setter set_color() methods to access and modify the private attributes in a controlled manner. These methods act as an interface for interacting with the object's internal state, ensuring data integrity and enabling additional validation.

Encapsulation promotes code modularity, maintainability, and data protection by separating the internal state from the public interface (methods). It allows you to change the internal state without affecting the code that uses the class, as long as the public interface remains the same.

Inheritance in Python

Inheritance is another core concept of OOP. It allows classes to inherit attributes and methods from other classes. The new class inherits attributes and methods from the existing class, known as the parent or base class. The new class is called the child or derived class.

Inheritance promotes code reuse by allowing the child class to inherit and extend the functionality of the parent class. This helps in creating hierarchical relationships between classes and organizing code in a more structured and logical manner.

In Python, inheritance is implemented using the following syntax:

class DerivedClass(BaseClass):
    # Derived class methods and attributes

To see how inheritance works, modify your code as follows:

class Speaker:
    brand = "Beatpill"

    def __init__(self, color, model):
        self._color = color
        self._model = model

    def power_on(self):
        print(f"Powering on {self._color} {self._model} speaker.")

    def power_off(self):
        print(f"Powering off {self._color} {self._model} speaker.")

# Add a SmartSpeaker class and make it inherit from the Speaker class
class SmartSpeaker(Speaker):
    def __init__(self, color, model, voice_assistant):
        super().__init__(color, model)
        self._voice_assistant = voice_assistant

    def say_hello(self):
        print(f"Hello, I am {self._voice_assistant}")


# Create an instance of the SmartSpeaker class
smart_speaker = SmartSpeaker("black", "XYZ123", "Alexa")
smart_speaker.power_on()  # Inherited from Speaker
smart_speaker.say_hello()

In the preceding code, the SmartSpeaker class is derived from the Speaker class. The SmartSpeaker class inherits the attributes and methods of the Speaker class.

The __init__ method of the SmartSpeaker class calls the __init__ method of the Speaker class using super().__init__(color, model). This ensures that the inherited _color and _model attributes are properly initialized. Also, the SmartSpeaker class has its own attribute _voice_assistant, and a new method say_hello.

Now run python classes.py in your terminal and you will get the following output:

Powering on black XYZ123 speaker.
Hello, I am Alexa

Conclusion

Throughout this article, we highlighted the benefits of Object-Oriented Programming (OOP) and demonstrated how to define classes, create and use instance attributes and methods. We provided practical examples to illustrate the implementation of classes in Python, as well as key OOP concepts such as encapsulation and inheritance.

Applying the lessons covered in this article will help you improve your Python programming experience and increase the efficiency and maintainability of your code.

References and Further Reading

  • https://en.wikipedia.org/wiki/Object-oriented_programming
  • https://realpython.com/python3-object-oriented-programming/
  • https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object-oriented_programming
  • https://docs.python.org/3/tutorial/classes.html
  • https://realpython.com/python-classes/
  • https://realpython.com/python-double-underscore/