Data encapsulation is one of the three main pillars of object oriented programming, known as PIE (Polymorphism, Inheritance, Encapsulation). Encapsulation refers to providing controlled access to class members via functions. With encapsulation, you can restrict unauthorized access to class members and you can set conditions on the values you want associated with your class.

In this tutorial, we’re going to show you to implement encapsulation in Python.

Why Data Encapsulation is Important

Before we actually show you how to implement data encapsulation in Python, let’s first explain why data encapsulation is important and what issues can occur when you don’t encapsulate your data.

The following script defines a class named Product with three member attributes: name, price and category. We use a constructor to initialize these three member attributes.

class Product:
    def __init__(self, name, price, category):
        self.name = name
        self.price = price
        self.category = category

The script below creates two objects: prod1 and prod2 of the Product class. The name, price and category attributes are then printed on the console via the prod1 and prod2 objects.

prod1 = Product("Table", 500, "Furniture")
prod2 = Product("Computer", 1000, "Electronics")

print(prod1.name, prod1.price, prod1.category)
print(prod2.name, prod2.price, prod2.category)

Output:

Table 500 Furniture
Computer 1000 Electronics

If you allow unrestricted access to member variables, like our name, price and category variables, invalid values can be assigned to these member variables. For instance, the following script assigns a string value to the price attribute of the prod1 object. Similarly, a garbage string value “xyz” is assigned to the category.

prod1.price = "abcd"
prod2.category = "xyz"

The script below prints the member attributes of the prod1 and prod2 objects.

print(prod1.name, prod1.price, prod1.category)
print(prod2.name, prod2.price, prod2.category)

Output:

Table abcd Furniture
Computer 1000 xyz

What if you want only positive integers to be assigned to the price attribute? Similarly, what if you want that a product category attribute only accept certain predefined values? Or maybe you just don’t want your member attributes to be accessed directly, at all. In all theses scenarios, you need to hide your member attributes, which is known as data encapsulation. In data encapsulation, you restrict unauthorized and uncontrolled access to class member attributes or methods.

In Python, access modifiers, getters and setters are used to implement encapsulation. Let’s first check out what access modifiers are in Python.


Get Our Python Developer Kit for Free

I put together a Python Developer Kit with over 100 pre-built Python scripts covering data structures, Pandas, NumPy, Seaborn, machine learning, file processing, web scraping and a whole lot more - and I want you to have it for free. Enter your email address below and I'll send a copy your way.

Yes, I'll take a free Python Developer Kit

Understanding Python Access Modifiers

Access modifiers in Python are used to define the access level for class members. There are three access levels for class members:

  1. public
  2. protected
  3. private

To define a private member, you need to prefix double underscores with a class member, like __name. A private member can only be accessed within the class where it is declared.

A protected member is accessed within a class, by all the child classes and within the same package. To declare a protected member, you need to prefix a single underscore before a variable name, like _name.

Finally, a public member can be accessed everywhere. You don’t need to make any changes to the variable name while defining a public variable.

Let’s see an example. The script below defines a public attribute (name), a protected attribute (_price) and a private attribute (__category).

class Product:
    def __init__(self, name, price, category):
        self.name = name
        self._price = price
        self.__category = category

The script below creates an object of our new Product class.

prod1 = Product("Table", 500, "Furniture")

Now, let’s attempt to print the values of our name, _price and __category attributes.

print(prod1.name)
print(prod1._price)
print(prod1.__category)

From the output below, you can see that the public and protected members are printed on the console, but the private member can’t be accessed outside the Product class. The protected member was successfully accessed because we executed the code from within the same package.

Output:

Table
500

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-53-d4c45166502a> in <module>
      1 print(prod1.name)
      2 print(prod1._price)
----> 3 print(prod1.__category)

AttributeError: 'Product' object has no attribute '__category'

Notice that with a private member, once a class is defined, you can still access and edit the attribute directly, like we do in the following example, but you’re not actually changing the value associated with the Product class.

prod1 = Product("Table", 500, "Furniture")
prod1.__category = "Vehicle"
print(prod1.__category)

Output:

Vehicle

Now that you understand the concept of access modifiers, let’s show you how to implement encapsulation using getters and setters.

Using Getters and Setters for Encapsulation

Getters and setters are what we call functions that are used to respectively retrieve (get) and assign (set) values to member attributes. Instead of providing direct access to the member attributes, the getter and setter functions are used to provide restricted access to member variables.

The idea of encapsulation in Python is that:

  1. first you make member variables private and then
  2. using getters and setters, you specify conditions that must be followed with access member variables.

Let’s see this in action. The script below defines a class Product with three private members: __name, __price and __category.

For each of the member attributes, two functions are defined for getting and setting values of these member attributes. For instance the get_name() and set_name() methods are defined to get and set the value for our __name attribute.

Similarly, the get_price() and set_price() functions are defined as getter and setter, respectively, for the __price attribute. The set_price() method defines that the value being assigned to the __price attribute should be numeric and a positive value. Similarly, the get_price() method only returns the value of the __price attribute if it is less or equal to 100.

Finally the set_category() method defines that the assigned value for the __category attribute must be either Furniture, Electronic, or Mobile.

class Product:
    def __init__(self, name, price, category):
        self.__name = name
        self.__price = price
        self.__category = category

    def get_name(self):
        return self.__name

    def set_name(self, name):
        self.__name = name

    def get_price(self):
        if self.__price > 100:
            print("please email us for price")
            return

        return self.__price

    def set_price(self, price):

        if  type(price) != int and type(price) != float:
            print("Price should be a numeric value")
            return

        if price < 0:
            print("Price should be a positive value")
            return

        self.__price = price

    def get_category(self):
        return self.__category

    def set_category(self, category):
        if category not in ["Furniture", "Electronic", "Mobile"]:
            print("Category should be Furniture, Electronic, or Mobile")
            return
        self.__category = category

From the above script, you can see how getters and setters are used to define different conditions for getting and setting values of member attributes.

The following script creates an object of the Product class containing private members that are accessed via getters and setters.

prod1 = Product("Table", 50, "Furniture")

Now if you try to directly access the value of the __name attribute, you’ll see an error, like the one below:

prod1.__name

Output:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-140-6fd3364d6636> in <module>
----> 1 prod1.__name

AttributeError: 'Product' object has no attribute '__name'

You can still access the __name attribute but now you have to use the getter function we created, get_name().

prod1.get_name()

Output:

'Table'

Similarly, you can set the value of the __name attribute via its setter function as shown below:

prod1.set_name("chair")
prod1.get_name()

Output:

'chair'

Just like we explained earlier, you can’t directly access the __price attribute because it’s private:

prod1.__price

Output:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-143-397f7c9b9d49> in <module>
----> 1 prod1.__price

AttributeError: 'Product' object has no attribute '__price'

Instead, the get_price() function must be used to get the value of the __price attribute.

prod1.get_price()

Output:

50

Let’s now try to assign -50 as the value for the __price attribute using the set_price() method.

prod1.set_price(-50)

Since the set_price() function restricts negative values, you’ll see the following output:

Output:

Price should be a positive value

Similarly, our setter function specifies you can’t assign a non-numeric value to the __price attribute, as we’ll demonstrate here:

prod1.set_price("fifty")

Output:

Price should be a numeric value

Finally, let’s assign a valid value, 200.0, to the __price attribute.

prod1.set_price(200.0)

Now, if you try to get the value of the __price attribute, you’ll see the following message.

prod1.get_price()

Output:

please email us for price

This is because our get_price() method specifies that if the price is greater than 100, the price isn’t directly returned to the console. The beauty of getters and setters is that you’re able to specify whatever conditions you want on your attributes!

For completeness, we’ll show you that you can’t directly access the __category attribute, either.

prod1.__category

Output:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-149-112d40622343> in <module>
----> 1 prod1.__category

AttributeError: 'Product' object has no attribute '__category'

Instead, the script below calls the get_category() method to access the value from our __category attribute.

prod1.get_category()

Output:

'Furniture'

Okay, now let’s try to assign a random value to the __category attribute using the set_category() method.

prod1.set_category("Vehicle")

Since the value assigned is not either Furniture, Electronic, or Mobile, you’ll get the following notification.

Output:

Category should be Furniture, Electronic, or Mobile

Let’s assign a valid value (Furniture, Electronic, or Mobile) to the __category attribute and then retrieve that value from our __category attribute.

prod1.set_category("Electronic")
prod1.get_category()

Output:

'Electronic'

That’s Python encapsulation in a nutshell! It’s really valuable because it lets you both protect and restrict access to the attributes of your classes. For more Python programming tips like this, I hope you’ll subscribe using the form below.


Get Our Python Developer Kit for Free

I put together a Python Developer Kit with over 100 pre-built Python scripts covering data structures, Pandas, NumPy, Seaborn, machine learning, file processing, web scraping and a whole lot more - and I want you to have it for free. Enter your email address below and I'll send a copy your way.

Yes, I'll take a free Python Developer Kit