In programming languages such as C++, Java and C#, classes state explicitly which class members are publicly accessible. Class members that may not be accessed outside a class definition are private and visible only within the class that defines them. Python programmers often use “private” attributes for data or utility methods that are essential to a class’s inner workings but are not part of the class’s public interface.
As you’ve seen, Python objects’ attributes are always accessible. However, Python has a naming convention for “private” attributes. Suppose we want to create an object of class Time
and to prevent the following assignment statement:
wake_up._hour = 100
that would set the hour to an invalid value. Rather than _hour
, we can name the attribute __hour
with two leading underscores. This convention indicates that __hour
is “private” and should not be accessible to the class’s clients. To help prevent clients from accessing “private” attributes, Python renames them by preceding the attribute name with _ClassName, as in _Time__hour
. This is called name mangling. If you try assign to __hour
, as in
wake_up.__hour = 100
Python raises an AttributeError
, indicating that the class does not have an __hour
attribute. We’ll show this momentarily.
In addition, IPython does not show attributes with one or two leading underscores when you try to auto-complete an expression like
wake_up.
by pressing Tab. Only attributes that are part of the wake_up
object’s “public” interface are displayed in the IPython auto-completion list.
To demonstrate name mangling, consider class PrivateClass
with one “public” data attribute public_data
and one “private” data attribute __private_data
:
1 # private.py
2 """Class with public and private attributes."""
3
4 class PrivateClass:
5 """Class with public and private attributes."""
6
7 def __init__(self):
8 """Initialize the public and private attributes."""
9 self.public_data = "public" # public attribute
10 self.__private_data = "private" # private attribute
Let’s create an object of class PrivateData
to demonstrate these data attributes:
In [1]: from private import PrivateClass
In [2]: my_object = PrivateClass()
Snippet [3]
shows that we can access the public_data
attribute directly:
In [3]: my_object.public_data
Out[3]: 'public'
However, when we attempt to access __private_data
directly in snippet [4]
, we get an AttributeError
indicating that the class does not have an attribute by that name:
In [4]: my_object.__private_data
-------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-4-d896bfdf2053> in <module>()
----> 1 my_object.__private_data
AttributeError: 'PrivateClass' object has no attribute '__private_data'
This occurs because python changed the attribute’s name. Unfortunately, as you’ll see in one of this section’s Self Check exercises, __private_data
is still indirectly accessible.
(Fill-In) Python mangles attribute names that begin with _________ underscore(s).
Answer: two.
(True/False) An attribute that begins with a single underscore is a private attribute.
Answer: False. An attribute that begins with a single underscore simply conveys the convention that a client of the class should not access the attribute directly, but it does allow access. Again, “nothing in Python makes it possible to enforce data hiding—it is all based upon convention.”4
(IPython Session) Even with double-underscore (__
) naming, we can still access and modify __private_data
, because we know that Python renames attributes simply by prefixing their names with '_
ClassName'
. Demonstrate this for class PrivateData
’s data attribute __private_data
.
Answer:
In [5]: my_object._PrivateClass__private_data
Out[5]: 'private'
In [6]: my_object._PrivateClass__private_data = 'modified'
In [7]: my_object._PrivateClass__private_data
Out[7]: 'modified'