If you're learning Python and working with classes, you’ve probably heard the terms private and semi-private attributes. These are concepts that help you control how data inside a class can be accessed or modified. But, what do these terms really mean, and how do they work in Python?

In this blog post, I’ll explain what private and semi-private attributes are, how they are used, when to use them, and why they are important.

What Are Private and Semi-Private Attributes?
In Python, attributes (or variables) inside a class can be categorized as public, private, or semi-private.

  • Public attributes can be accessed and modified freely from outside the class.

  • Private attributes are intended to be hidden from the outside world.

  • Semi-private attributes are a bit of a soft rule, Python doesn’t enforce them, but they signal that the attribute should be treated as internal.

How to Define Private Attributes in Python
Private attributes are defined with two leading underscores (__) before the attribute name. This tells Python that the attribute is meant to be hidden from external access, although it's not a foolproof method. Python uses this to mangle the attribute name and make it a little more difficult to access directly (though not impossible).

(Note: Private attributes should not be confused with Dunder attributes, which have two leading and two trailing underscores, such as __ init __.)

Example:

class Dog:
    def __init__(self, name, age):  # The __init__ is a Dunder Attribute
        self.__name = name  # Private attribute
        self.__age = age    # Private attribute

    def get_name(self):  # Public method to access private attribute
        return self.__name

    def get_age(self):   # Public method to access private attribute
        return self.__age

    def set_age(self, age):  # Public method to modify private attribute
        if age > 0:
            self.__age = age
        else:
            print("Age must be positive.")

In this example, __name and __age are private attributes. We use getter and setter methods (get_name, get_age, and set_age) to allow controlled access and modification of these attributes.

Why Use Private Attributes?
So, why go through the trouble of making attributes private in the first place? Here’s why:

  • Encapsulation: Private attributes help you hide the internal details of your class, keeping your data safe from accidental or malicious changes.

  • Control: By using private attributes, you can control how and when your data gets accessed or updated. For example, the set_age method ensures the age attribute can’t be set to an invalid value.

  • Security: By restricting access to an attribute, you reduce the risk of its value being changed in unexpected ways.

Trying to Access Private Attributes
If you try to directly access a private attribute, Python will raise an error:

dog = Dog("Max", 5)

# Trying to access the private attribute directly will raise an error
print(dog.__name)  # AttributeError: 'Dog' object has no attribute '__name'

Instead, you should use the public methods (get_name(), get_age()) to access private attributes.

Name Mangling
Python uses a feature called name mangling to make private attributes harder to access directly. When you define an attribute with two leading underscores (e.g., __name), Python internally changes its name by adding the class name prefix (e.g., _Dog __name). This is done to prevent accidental access or modification from outside the class.

While you can technically access the mangled attribute using its new name (e.g., dog._ Dog__name), it’s strongly discouraged (just don't). This breaks the encapsulation principle and defeats the purpose of making the attribute private in the first place.

Semi-Private Attributes
Semi-private attributes are defined with a single underscore (_) before the attribute name. This is a convention in Python indicating that the attribute is intended for internal use within the class. However, unlike private attributes, semi-private attributes can still be accessed from outside the class, and Python doesn’t enforce any restriction. That being said, the goal of defining the attribute as semi-private is to alert other developers not to modify the attribute directly from outside the class.

Example:

class Car:
    def __init__(self, make, model):
        self._make = make  # Semi-private attribute
        self._model = model  # Semi-private attribute

Even though you can access _make and _model directly, it’s best to think of them as internal details of the class and avoid modifying them from the outside.

When to Use Private and Semi-Private Attributes
Here’s when you should use private and semi-private attributes:

  • Private Attributes: These are useful when you need to protect certain data from being accessed or changed directly. For example, if you have an attribute like age, you might want to make it private to ensure no one messes with it directly. You can then create a method like set_age() to validate the input before updating the attribute.

  • Semi-Private Attributes: These are more like a suggestion that the attribute is for internal use. While you can technically access or modify them, it’s generally better to leave them alone. The single underscore is a "soft" rule, so don’t take it too literally, but it’s a good practice to respect the convention.

Private Methods vs. Private Attributes
Private methods and private attributes are similar in that they both follow the same naming convention with two leading underscores to signal they’re meant for internal use only. However, there’s an important distinction:

  • Private methods are functions that perform actions or calculations within the class.

  • Private attributes are variables that store data related to the class.

Even though they serve different purposes (actions vs. data), they both follow the same visibility rules.

But, remember, Python doesn't strictly enforce access control. You could still access private methods or attributes using name mangling (e.g., _ ClassName __ method or _ ClassName __ attribute). It’s better to rely on the conventions, though.

Core Takeaways:
In Python, private and semi-private attributes are useful tools to manage how data is accessed and modified within your classes.

Private attributes (with two leading underscores) are meant to be hidden from external access, and you should interact with them using public methods.

Semi-private attributes (with one leading underscore) are intended for internal use but are not strictly off-limits.

By using these conventions, you can create cleaner, safer, and more maintainable code!

Sources:
w3resources
python.docs:private variables
python.docs:private name mangling
geeksforgeeks