Encapsulation Done Right

Encapsulation is a crucial concept in object-oriented programming, aiming to control access to a class’s members. While this principle isn’t tied to a specific programming language, its application in Python sometimes falls short. Despite the “We’re all adults here” mindset, there’s room for improvement in our coding practices. After all, we all aspire to create top-notch software, right?

A common pattern I’ve noticed among Python developers involves exposing class members directly, as shown in the following snippet:

class File(object):

    def __init__(self):

        self.file = None
 
    def read(self):

        with open(self.file, 'r') as file:

            return file.read()

At first glance, it seems fine. You can create an instance of the class and set the file attribute as needed. However, a closer look reveals a potential issue. Consider the following sequence:

file = File()

file.file = <filePath>

print(file.read())


file.file = <fileThatDoesNotExist>

print(file.read()) # BOOM

In this case, attempting to read a non-existent file triggers an IOError exception. While this is expected, it doesn’t change the fact that we’re not truly leveraging encapsulation.

Let’s enhance the implementation:

import os
import customExceptions

class File(object):

    def __init__(self):

        self._file = None
        
    @file.setter
    def file(self, file):

        if os.path.isfile(file):

            raise customExceptions.CustomException('File does not exist: {}'.format(file))

        self._file = file
    
    def read(self):

        with open(self._file, 'r') as file:

            return file.read()

This improved version introduces a protected member _file and a setter method to ensure the file being set actually exists. This not only enhances encapsulation but also promotes better coding practices.

However, to truly embrace encapsulation, we should avoid direct access to the protected member, like this:

print(file._file)

Instead, we can provide a getter method:

@property
def file(self):

    return self._file

Now, users can retrieve the file attribute in a clean and encapsulated way. If you’re working with multiple languages and frameworks, consider a more general implementation for broader consistency:

import os
import customExceptions

class File(object):

    def __init__(self):

        self._file = None

    def file(self):

        return self._file

    def setFile(self, file):
        
        if os.path.isfile(file):

            raise customExceptions.CustomException('File does not exist: {}'.format(file))

        self._file = file

    def read(self):

        with open(self._file, 'r') as file:

            return file.read()

In summary, encapsulation is a must for limiting external access to a class’s members. Always start with protected or private members and implement methods for controlled access. This ensures clean, secure, and effective code.

If you found this article insightful, you may also like:

About the author

As a seasoned software engineer, I've navigated through various roles across multiple departments, taking on diverse responsibilities ranging from team leadership to senior supervisory roles.

My approach to problem-solving is anything but conventional, and it's what sets me apart. I have a knack for enhancing the software development process, making it not only more productive and reliable but also an enjoyable journey for all involved. My track record includes delivering software products on time and within budget while consistently achieving remarkable increases in productivity.

If you're looking to fortify and streamline your development pipeline, I invite you to reach out to me without hesitation. Together, we can make your operations more robust and efficient, leaving a trail of satisfied stakeholders in our wake.

Contact Me
https://www.safakoner.com
https://twitter.com/safakoner
https://github.com/safakoner
Example API Reference