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.