When to Use Underscores in Python: A Guide for Clearer Object-Oriented Code

2024-04-17

Single Leading Underscore (_):

  • Convention for Internal Use: In Python, a single leading underscore preceding a variable or method name (_name) signifies that it's intended for internal use within a module or class. While Python doesn't enforce this, it's a common practice to discourage direct access from outside the module/class.
  • Example:
    class MyClass:
        def __init__(self):
            self._internal_data = "This is for internal use only"
    
  • Example:

    class MyClass:
        def __init__(self):
            self.__private_attribute = "This is private (mangled)"
    

    Here, the actual name stored might be something like _MyClass__private_attribute.

  • Special Names: A double leading and trailing underscore (name) is reserved for special methods and attributes defined by the Python language itself. These methods have specific purposes within the object system. It's generally recommended to avoid this naming convention for your own attributes to prevent conflicts with built-in features.
  • Example:
    class MyClass:
        def __init__(self):
            self.__dict__ = {}  # This is a special attribute, not recommended for your own use
    

Key Points:

  • Single leading underscore is a suggestion for internal use.
  • Double leading underscore is for name mangling to avoid conflicts.
  • Double leading and trailing underscore is for special Python methods.

By following these naming conventions, you can improve code readability, maintainability, and reduce the likelihood of naming conflicts in your Python programs, especially when working with OOP concepts like classes and inheritance.




class MyClass:
    def __init__(self):
        self.public_data = "Anyone can access this"
        self._internal_data = "Intended for internal use within the class"

# Creating an instance
my_object = MyClass()

# Accessing public data
print(my_object.public_data)  # Output: Anyone can access this

# Trying to access internal data directly (not recommended)
print(my_object._internal_data)  # Output: This will work, but avoid direct access from outside

In this example:

  • public_data is a regular attribute without underscores, accessible from anywhere in your code.
  • _internal_data has a single leading underscore, suggesting it's meant for internal use within the MyClass class. While you can still access it directly using my_object._internal_data, it's a convention to discourage this to maintain encapsulation.
class MyClass:
    def __init__(self):
        self.__private_attribute = "This is considered private (name mangling)"

    def get_private_attribute(self):  # Public method to access the private attribute
        return self.__private_attribute

# Creating an instance
my_object = MyClass()

# Attempting to access directly (will result in an error)
print(my_object.__private_attribute)  # Output: AttributeError: 'MyClass' object has no attribute '__private_attribute'

# Accessing through the public method (recommended)
print(my_object.get_private_attribute())  # Output: This is considered private (name mangling)

Here:

  • __private_attribute has a double leading underscore, triggering name mangling. The actual attribute name might be something like _MyClass__private_attribute.
  • Trying to access it directly using my_object.__private_attribute will raise an AttributeError because the mangled name is not directly exposed.
  • The get_private_attribute method provides a controlled way to access the "private" attribute from within the class.

Special Methods (Double Leading and Trailing Underscore: Not Recommended for Custom Use)

class MyClass:
    def __init__(self):
        self.__dict__ = {}  # This is a special attribute, avoid using it for your own purposes

# Creating an instance
my_object = MyClass()

# Accessing the special attribute (not recommended)
print(my_object.__dict__)  # This might print internal details about the object, not for general use

This example shows a special attribute (__dict__) used internally by Python. It's generally not recommended to use double leading and trailing underscores for your own attributes to avoid conflicts with built-in features.




Docstrings:

  • Use clear and concise docstrings to document the intended usage of attributes and methods within a class. This helps developers understand the purpose of these members and discourages direct access from outside the class.
class MyClass:
    def __init__(self):
        self._internal_data = "This is for internal use only"

    """
    This method retrieves the internal data (intended for use within the class).
    """
    def get_internal_data(self):
        return self._internal_data

Name Mangling (Manual):

  • You could manually mangle names by prepending a string like _cls_ (where cls represents the class name) to internal attributes. However, this is less common and requires more manual effort compared to the double underscore convention.
class MyClass:
    def __init__(self):
        self._cls_internal_data = "This is for internal use only"

    def get_internal_data(self):
        return self._cls_internal_data

Encapsulation (Getter and Setter Methods):

  • Implement getter and setter methods to control access to attributes. This provides more granular control over how internal data can be accessed and modified. It can be useful when you want to perform additional logic or validation during access.
class MyClass:
    def __init__(self):
        self.__internal_data = None  # Private attribute (not recommended for direct access)

    def get_internal_data(self):
        return self.__internal_data

    def set_internal_data(self, value):
        # Perform validation or logic here before setting the value
        self.__internal_data = value

Private Modules:

  • If you have a set of functions or variables that are truly meant to be private and not used outside a specific module, consider creating a private module within your package. Python doesn't have a built-in mechanism for private modules, but you can achieve a similar effect by using a leading underscore for the module name (_my_private_module.py). However, this practice relies on convention and doesn't enforce privacy.

Remember:

  • Single and double underscores are widely used conventions that improve code readability and maintainability. They are not strict enforcements, but following these conventions is generally recommended.
  • Docstrings, manual name mangling, and getter/setter methods offer alternative approaches, but they may have trade-offs in terms of complexity and clarity.
  • Choose the approach that best aligns with your coding style and project requirements.

python oop naming-conventions


Understanding and Preventing SQLAlchemy DetachedInstanceError

I'd be glad to explain the "SQLAlchemy DetachedInstanceError with regular attribute (not a relation)" error in Python's SQLAlchemy library:...


Fixing 'InstrumentedList' Object Has No Attribute 'filter' Error in SQLAlchemy

Understanding the Error:This error arises when you attempt to use the . filter() method on an InstrumentedList object in SQLAlchemy...


Beyond the Asterisk: Alternative Techniques for Element-Wise Multiplication in NumPy

Here are two common approaches:Element-wise multiplication using the asterisk (*) operator:This is the most straightforward method for multiplying corresponding elements between two arrays...


Banishing the "Unnamed: 0" Intruder: Techniques for a Clean pandas DataFrame

Understanding the "Unnamed: 0" ColumnWhen you read a CSV file into a pandas DataFrame using pd. read_csv(), pandas might add an "Unnamed: 0" column by default...


python oop naming conventions