Skip to content

Mastering Private Class Members in Python with Leading Underscores

Updated: at 04:45 AM

In Python, there are several conventions used to indicate that a class member should be considered private. The most common way is to start the name with a single underscore (_). This guides developers to treat the member as being internal to the class, but does not actually prevent access to it.

Table of Contents

Open Table of Contents

Overview

Naming Convention to Indicate Private Members

The main convention used to indicate private members in Python is prefixing the name with a single underscore _.

For example:

class MyClass:

    def __init__(self):
        self._private_member = "Hello"

    def public_method(self):
        print(self._private_member)

Here _private_member is intended to be private to MyClass. Code outside MyClass should avoid directly accessing _private_member and instead use the public public_method() if access is needed.

This naming convention serves mainly as a hint to programmers that the member is internal to the class implementation and not intended for direct external use.

But Python does not actually prevent external code from accessing _private_member:

myclass = MyClass()
print(myclass._private_member) # Still accessible from outside class

So underscores in Python do not enforce privacy, they simply guide programmers to treat the member as private as a matter of convention.

Explicitly declaring a member name with a single leading underscore _ at the start is the standard way to indicate it should be considered private in Python.

Purpose of Private Members

Some of the reasons to designate internal class members as private in Python include:

So while Python cannot actively enforce true privacy, the naming convention serves an important purpose in guiding appropriate class design and use.

Double Underscore Name Mangling

Python utilizes a mechanism called name mangling when encountering member names with double leading underscores.

This converts the name to include the class name to avoid overriding by subclasses. For example:

class MyClass:

    def __init__(self):
        self.__private_member = "Hello"

print(MyClass._MyClass__private_member) # Name mangled

The double underscore __private_member is mangled to _MyClass__private_member when accessed externally.

Name mangling still does not guarantee privacy, but avoids naming collisions between superclass and subclasses using the same member names.

So while single underscores just signal privacy by convention, double underscores additionally invoke name mangling for collision avoidance.

Single Trailing Underscores

Sometimes a single trailing underscore is used on instance variables in class methods:

class MyClass:

    def my_method(self):
        my_var = "Hello"
        self._instance_var = "Hi" # Trailing underscore

This does not impact privacy, but helps distinguish instance variables from local variables defined in methods.

The single trailing underscore is just a coding convention some developers use for readability.

Accessing Private Members Externally

While Python conventions recommend against it, there is no actual restriction from accessing members prefixed with a single underscore externally:

class MyClass:
    def __init__(self):
        self._private = "Hello"

myobj = MyClass()

print(myobj._private) # Still accessible

Since underscores do not enforce privacy, external code can still directly access the _private member.

This breaks encapsulation and could cause issues later if internal details change. But Python leaves this responsibility up to the programmer.

For double underscore name mangling, the mangled name can still be used to access the member externally:

class MyClass:
    def __init__(self):
        self.__private = "Hello"

print(myobj._MyClass__private) # Accessed through mangled name

So while Python conventions recommend against accessing members starting with underscores externally, the language does not prevent it.

Getters and Setters for Private Members

A common way to allow controlled external access to private members is defining public getter and setter methods:

class MyClass:

    def __init__(self):
        self._private_member = "Hello"

    def get_private_member(self):
        return self._private_member

    def set_private_member(self, value):
        self._private_member = value

This allows external code to get or modify the value of _private_member in a controlled way, without directly accessing the private member itself.

Getters and setters define the public API for accessing private data. This maintains encapsulation and allows the underlying implementation to change without impacting external code.

Inheritance and Private Members

When inheriting from a parent class, private members prefixed with a single underscore _ are accessible in the child subclass:

class Parent:
    def __init__(self):
        self._private = "Hello"

class Child(Parent):
    def get_private(self):
        return self._private

The Child class can access _private directly because private members are not enforced.

But subclasses should still treat parent private members as private per conventions. Indirect access via public methods is better for maintenance.

For double underscore __ members, name mangling ensures the subclass cannot override the parent’s member. But the mangled name still allows the subclass to access the parent’s private member if needed.

Private Members vs. Protected Members

Some languages like C++ distinguish between private members (class only access) and protected members (class and subclass access).

But in Python, there is no language-level difference between private and protected. By convention, both private and protected members start with a single underscore _.

The distinction is based on intent - protected members are intended to be accessible in subclasses while private members are not. But Python itself does not enforce this.

Best Practices for Private Members

Here are some key best practices for using underscores and name mangling in Python:

Following Python conventions for private members and avoiding direct access from external code will result in better encapsulated and more maintainable code.

Example Class with Public and Private Members

Here is an example class BankAccount implementing public and private members using leading underscores:

class BankAccount:
    """Bank account class with private balance tracking."""

    def __init__(self, name, balance=0):
        """Create new bank account."""
        self.name = name  # Public attribute
        self._balance = balance  # Private attribute

    def deposit(self, amount):
        """Deposit funds into account."""
        self._balance += amount

    def withdraw(self, amount):
        """Withdraw funds from account if sufficient balance."""
        if self._balance >= amount:
            self._balance -= amount
            return True
        return False

    def get_balance(self):
        """Return current account balance."""
        return self._balance

BankAccount provides a simple public API with deposit(), withdraw(), and get_balance() methods that encapsulate the private _balance member.

Client code should not access _balance directly and instead use the public methods:

account = BankAccount("Sam", 500)
account.deposit(100)
print(account.get_balance()) # Access through public method

This class demonstrates proper encapsulation of private data using Python’s underscore naming convention.

Conclusion

In Python, a single leading underscore _ is the standard way to name internal class members that should be considered private. This convention serves an important purpose in marking members as internal implementation details not intended for direct external use.

Python does not actively enforce privacy of underscored members. But following this widely adopted naming convention signals programmer intent and promotes encapsulation.

Double leading underscores __ invoke name mangling which avoids accidental overriding by subclasses. Trailing underscores are sometimes used to distinguish instance variables from locals.

Underscores for indicating privacy in Python are a matter of idiomatic style. There are ways to access members marked private or mangled from external code. But this goes against conventions and loses the benefits of encapsulation.

Using Python’s namespace and naming conventions properly is an important part of API design and creating maintainable object-oriented programs. Understanding the purpose and proper use cases of underscores for marking class member privacy will lead to more robust and encapsulated code.