Understanding Double Underscores in Python Modules

Understanding Private Functions in Python

I have two files defined as follows:

File: a.py

__num = 1  

File: b.py

import a  
print(a.__num)  

When I run b.py, it prints out 1 without any exception. I was under the impression that using a double underscore (__) should make variables or functions private. Did I misunderstand how private functions in Python work? Is the documentation or guide I referred to incorrect?

Additionally, is there a proper way to define a module’s functions or variables as private in Python? How can I ensure they are not accessible from outside the module?

I’ve dealt with private functions in Python quite a bit, so I can shed some light here.

Use Single Underscore Convention In Python, a single underscore prefix (e.g., _function) is the standard way to indicate a function or variable is intended to be private. It’s not strictly enforced by the interpreter, but it sends a clear signal to other developers: “This is for internal use.”

Here’s an example:

File: a.py

_num = 1  # Treated as "private" by convention  

def _private_function():  
    return "This is a private function."  

File: b.py

import a  

# While it can be accessed, it's discouraged  
print(a._num)  # Accessing private data is not recommended  

So, while Python doesn’t have “true” private variables like some other languages, following this convention helps maintain clean code. The key here is discipline—anyone maintaining the code should respect these conventions.

That’s a great starting point, Yanisleidi! Let me add to this with a slightly more robust approach.

Use Double Underscore for Name Mangling If you want to make a variable or function harder to access, Python offers name mangling. Prefixing a variable or function with a double underscore (__) alters its name internally, making it more difficult (though not impossible) to access from outside the module.

Here’s an example:

File: a.py

__num = 1  # Name mangling applies here  

def __private_function():  
    return "This is a name-mangled private function."  

File: b.py

import a  

try:  
    print(a.__num)  # This will raise an AttributeError  
except AttributeError:  
    print("Access denied due to name mangling.")  

The double underscore makes it less likely for accidental access or name conflicts. However, it’s still accessible if you know the mangled name (e.g., _a__num), so it’s not foolproof.

This is especially useful when creating libraries where you want to hide implementation details from users who import your module.

Both of you covered excellent points! Let’s take this further and talk about controlling access explicitly.

Control Access with __all__ Python provides another mechanism for controlling what gets exposed when your module is imported: the __all__ attribute. By defining __all__, you specify the public API of your module, effectively “hiding” everything else.

Here’s how it works:

File: a.py

__all__ = ["public_function"]  # Only this function will be accessible when imported  

_num = 1  # Treated as private  

def public_function():  
    return "This is a public function."  

def _private_function():  
    return "This is a private function."  

File: b.py

from a import *  

print(public_function())  # Accessible  

try:  
    print(_private_function())  # This will raise a NameError  
except NameError:  
    print("Access denied.")  

Using __all__ ensures a clean API, particularly useful for larger projects or libraries. Combined with the single or double underscore conventions, this approach gives you a well-rounded way to handle private functions in Python.