10.5 Simulating “Private” Attributes

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.

IPython Auto-Completion Shows Only “Public” Attributes

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.

Demonstrating “Private” Attributes

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.

tick mark Self Check

  1. (Fill-In) Python mangles attribute names that begin with _________ underscore(s).
    Answer: two.

  2. (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

  3. (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'