We have seen in our previous code examples that we have access to all class-level as well as instance-level attributes without any restrictions. Such an approach led us to a flat design, and the class will simply become a wrapper around the variables and methods. A better object-oriented design approach is to hide some of the instance attributes and make only the necessary attributes visible to the outside world. To discuss how this is achieved in Python, we introduce two terms: private and protected.
Private variables and methods
A private variable or attribute can be defined by using a double underscore as a prefix before a variable name. In Python, there is no keyword such as private, as we have in other programming languages. Both class and instance variables can be marked as private.
A private method can also be defined by using a double underscore before a method name. A private method can only be called within the class and is not available outside the class.
Whenever we define an attribute or a method as private, the Python interpreter doesn't allow access for such an attribute or a method outside of the class definition. The restriction also applies to subclasses; therefore, only the code within a class can access such attributes and methods.
Protected variables and methods
A protected variable or a method can be marked by adding a single underscore before the attribute name or the method name. A protected variable or method should be accessed or used by the code written within the class definition and within subclasses—for example, if we want to convert the i_color attribute from a public to a protected attribute, we just need to change its name to _i_color. The Python interpreter does not enforce this usage of the protected elements within a class or subclass. It is more to honor the naming convention and use or access the attribute or methods as per the definition of the protected variables and methods.
By using private and protected variables and methods, we can hide some of the details of the implementation of an object. This is helpful, enabling us to have a tight and clean source code inside a large-sized class without exposing everything to the outside world.
Another reason for hiding attributes is to control the way they can be accessed or updated. This is a topic for the next post. To conclude this post, we will discuss an updated version of our Car class with private and protected variables and a private method, which is shown next:
#carexample5.py
class Car:
c_mileage_units = "Mi"
__max_speed = 200
def __init__(self, color, miles, model):
self.i_color = color
self.i_mileage = miles
self.__no_doors = 4
self._model = model
def __str__(self):
return f"car with color {self.i_color}, mileage
{self.i_mileage}, model {self._model} and doors
{self.__doors()}"
def __doors(self):
return self.__no_doors
if __name__ == "__main__":
car = Car ("blue", 1000, "Camry")
print (car)
In this updated Car class, we have updated or added the following as per the previous example:
• A private __max_speed class variable with a default value
• A private __no_doors instance variable with a default value inside the __init__constructor method
• A _model protected instance variable, added for illustration purposes only
• A __doors() private instance method to get the number of doors
• The __str__ method is updated to get the door by using the __doors() private method
The console output of this program works as expected, but if we try to access any of the private methods or private variables from the main program, it is not available, and the Python interpreter will throw an error. This is as per the design, as the intended purpose of these private variables and private methods is to be only available within a class.
For the Car class example, we can access the private variables and private methods. Python provides access to these attributes and methods outside of the class definition with a different attribute name that is composed of a leading underscore, followed by the class name, and then a private attribute name. In the same way, we can access the private methods as well.
The following lines of codes are valid but not encouraged and are against the definition of private and protected:
print (Car._Car__max_speed)
print (car._Car__doors())
print (car._model)
As we can see, _Car is appended before the actual private variable name. This is done to minimize the conflicts with variables in inner classes as well.