This is an archived version of the course and is no longer updated. Please find the latest version of the course on the main webpage.

Encapsulation and Abstraction

So far all the attributes in our examples are publicly accessible. So any other object can directly view and modify the attributes of an object.

But should our objects really expose everything, including their internal states, for the whole world to see and manipulate?

For example, should our Character class really allow others to change their health directly? Someone random might suddenly just pop by one day and say “Ok, character, I want you to set your health to zero and your attack to be zero”. So this someone is basically just asking the character to drop dead without even fighting.

Encapsulation is another key concept in OOP, which means to restrict access to an object’s attributes and methods such that the object has better control over their own data, rather than allowing some other classes to dictate it. The classes only exposes the amount of data necessary for it to be useful.

This allows users to focus on their code rather than your code.

In OOP, there are typically three main levels of access:

  • public: attributes can be freely used inside or outside a class definition.
  • protected: attributes should not be used outside the class definition unless they are used inside a subclass definition.
  • private: attributes can only be read or modified inside the class definition, not visible outside the class definition.

Protected attributes should be prefixed with a single underscore: Character._health

Private attributes should be prefixed with two underscores: Character.__health

Here is an example of encapsulation. Here, we cannot modify the course directly because __course is a private attribute. To change the value, the class Student needs to provide a setter function set_course() which will be able to access __course. You may perform some checks inside this method, for example, to verify where the student is allowed to change their course.

class Student:
    def __init__(self):
        self.__course = "MSc Adv"

    def enrolled(self):
        print(f"Enrolled in: {self.__course}")

    # getter method
    def get_course(self):
        return self.__course
    
    # setter method
    def set_course(self, course):
        self.__course = course

s = Student()
s.enrolled()   # Enrolled in: MSc Adv

s.__course = "MSc AI"   # Not modified because __course is private
s.enrolled()   # Enrolled in: MSc Adv

s.set_course("MSc AI")
s.enrolled()   # Enrolled in: MSc AI

The recommended way to implement getters and setters is to use Python’s @property decorators to expose the private attributes as properties, in a controlled manner.

class Student:
    def __init__(self):
        self.__course = "MSc Adv"

    @property 
    def course(self):
        return self.__course
    
    @course.setter
    def course(self, course):
        print("I've done some checks here. Passed!")
        self.__course = course

s = Student()
print(s.course)   # Enrolled in: MSc Adv

s.course = "MSc AI"
print(s.course)   # Enrolled in: MSc AI

We will cover more about the property decorator towards the end of our course. For an overview now, see this article for more details about the property decorator.

Abstraction

The final ‘pillar’ of OOP is abstraction.

Abstraction is essentially a higher-level version of encapsulation, which concerns exposing only a high-level interface of using your classes. Any implementation details are hidden from the users, with only enough necessary details revealed.

It is like driving a car. You do not need to know about the details about how the engines work. You just need to step on the accelerator pedal and steer the wheel!