How Encapsulation and Data Hiding Work in Python

Tarun SharmaTarun Sharma
13 min read

Understanding Encapsulation and Data Hiding

Encapsulation is a core concept in object-oriented programming (OOP) that involves bundling data and methods that operate on the data into a single unit called a class. This concept is designed to restrict direct access to some of the object's components, which can prevent the accidental modification of data. Encapsulation helps achieve data hiding, which is the practice of hiding the internal state of an object and requiring all interaction to be performed through an object's methods.

Data Hiding is a technique used within encapsulation to protect an object’s internal state from unintended or harmful modifications. By using private attributes and methods, a class can control how its data is accessed and modified. In Python, this is typically achieved using single or double underscores in attribute names.

Access Modifiers:

  • Public: Attributes and methods are accessible from outside the class.

  • Protected: Attributes and methods are intended to be accessible only within the class and its subclasses.

  • Private: Attributes and methods are intended to be accessible only within the class itself.

Internal Details:

  • Private Attributes: In Python, private attributes are indicated by prefixing the attribute name with double underscores (__). This triggers name mangling, which makes the attribute less accessible from outside the class but not completely inaccessible.

  • Public Methods: Public methods (getters and setters) are provided to interact with private data. These methods are used to get or set the values of private attributes, often including validation or business logic.

  • Name Mangling: Name mangling is the process of changing the name of a private attribute to make it harder to accidentally access from outside the class.

Name Mangling in Python

In Python, name mangling is a technique used to ensure that private attributes and methods are not accidentally accessed from outside the class. It involves prefixing the attribute or method name with two underscores (__), which triggers Python's name mangling process. This transforms the name of the attribute in a way that makes it harder to accidentally access it from outside the class.

How Name Mangling Works:

When an attribute or method is prefixed with two underscores, Python internally changes the name to include the class name. This makes it less likely for a subclass or external code to accidentally access it.

For example, if you have a class A with a private attribute __secret, Python will mangle the name to _A__secret.

Example of Name Mangling:

class MyClass:
    def __init__(self, value):
        self.__private_value = value

    def get_value(self):
        return self.__private_value

    def set_value(self, value):
        self.__private_value = value

# Usage
obj = MyClass(10)
print(obj.get_value())  # 10

# Trying to access the private attribute directly
print(obj.__private_value)  # AttributeError

# Accessing the mangled name
print(obj._MyClass__private_value)  # 10

In the example above:

  • __private_value is intended to be private and is name-mangled to _MyClass__private_value.

  • Direct access to obj.__private_value results in an AttributeError because the attribute is not directly accessible.

  • However, obj._MyClass__private_value can be used to access the mangled name, though it is not recommended to rely on this as it breaks encapsulation principles.

Important Notes:

  • Name mangling does not provide true access control but rather a convention to help avoid accidental access.

  • It is not a security feature; it merely helps in avoiding name conflicts and accidental misuse of private attributes.

Common Interview Questions

  1. Explain encapsulation and how it differs from data hiding.

    Answer: Encapsulation is the bundling of data and methods into a single unit (class) and restricting access to some of the object's components. Data hiding, a subset of encapsulation, focuses specifically on concealing the internal state of an object to protect it from unintended modifications. Encapsulation includes both data hiding and exposing functionality through methods.

  2. Why is encapsulation important in object-oriented programming?

    Answer: Encapsulation protects the internal state of an object, provides a clear interface for interacting with the object, supports abstraction, and enhances code security and maintainability by controlling data access and modification.

  3. How do you achieve encapsulation in Python?

    Answer: Encapsulation in Python is achieved by using private attributes (prefixing with double underscores) and providing public methods (getters and setters) to interact with these attributes, ensuring controlled access.

  4. What is the purpose of getter and setter methods?

    Answer: Getters provide read-only access to private attributes, while setters allow modification with validation or constraints. They help maintain encapsulation by controlling how data is accessed and changed.

  5. How can encapsulation improve code maintainability?

    Answer: Encapsulation improves maintainability by isolating changes, reducing complexity, and enhancing flexibility. Changes to the internal implementation do not affect other parts of the code, making the code easier to understand and manage.

  6. How does encapsulation relate to the principle of least privilege?

    Answer: Encapsulation supports the principle of least privilege by restricting access to the internal state of an object and exposing only the necessary functionality. This minimizes the impact of changes and protects the object’s integrity.

  7. In Python, what are the limitations of using single underscores (_) versus double underscores (__) for data hiding?

    Answer: Single underscores (_) are a convention indicating internal use but do not enforce access restrictions. Double underscores (__) trigger name mangling, which makes attributes harder to access but not completely private.

  8. Can you explain how private data can be accessed despite using double underscores?

    Answer: Private data can still be accessed using a specific naming convention, such as _ClassName__data. This is not recommended and should be avoided to maintain proper encapsulation.

  9. How would you implement encapsulation in a class representing a bank account?

    Answer: In a bank account class, encapsulation is implemented by making the balance attribute private and providing public methods to deposit, withdraw, and check the balance. This ensures controlled access and modification of the balance.

  10. What are the benefits and potential drawbacks of using encapsulation in a software project?

    Answer: Benefits include improved security, maintainability, and code organization. Drawbacks may include increased complexity and potential performance overhead.

  11. Describe a scenario where encapsulation might be misused.

    Answer: Encapsulation might be misused if it leads to overly complex code with excessive getters and setters, making the code harder to understand and maintain.

  12. How does encapsulation support the concept of abstraction in object-oriented design?

    Answer: Encapsulation supports abstraction by hiding implementation details and exposing only the necessary features and methods. This allows users to interact with objects through a simplified interface.

  13. In what situations might encapsulation be less effective or unnecessary?

    Answer: Encapsulation might be less effective for simple data structures with minimal behavior, such as data transfer objects (DTOs), where the overhead of encapsulation might not provide significant benefits.

  14. How does encapsulation differ from inheritance and polymorphism in object-oriented programming?

    Answer: Encapsulation hides internal details and provides controlled access. Inheritance allows a class to inherit attributes and methods from a parent class, enabling code reuse. Polymorphism allows different classes to be treated as instances of a common base class, supporting flexible method implementations.

  15. Can you provide an example where encapsulation helps to enforce business rules in a class?

    Answer: Encapsulation helps enforce business rules by using setter methods to validate data before modifying private attributes. For example, a Person class with a setter for age could ensure that age is always non-negative.

  16. How can encapsulation be used to control access to sensitive data in a class?

    Answer: Encapsulation controls access to sensitive data by making attributes private and providing controlled access through methods that include validation and business logic.

  17. What are the trade-offs between using encapsulation and exposing attributes directly in a class?

    Answer: Encapsulation provides better control, security, and maintainability, while direct access simplifies code but may expose internal details and reduce data integrity and control.

  18. How can you test encapsulated classes effectively?

    Answer: Test encapsulated classes by focusing on public methods and ensuring they behave correctly. Verify that private attributes are modified only through controlled methods and avoid direct access.

  19. How does encapsulation impact the design of a software system?

    Answer: Encapsulation promotes modularity and separation of concerns, leading to a more organized and maintainable codebase. It helps manage complexity and ensures that changes to one part of the system do not negatively impact other parts.

  20. How would you refactor code to improve encapsulation if a class exposes too many public attributes?

    Answer: Refactor by making attributes private with double underscores, providing public getter and setter methods, and ensuring that setter methods include validation to enforce constraints.


Coding Examples

1. Define a class Person with private attributes for name and age. Implement getter and setter methods for these attributes.

class Person:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    def get_name(self):
        return self.__name

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

    def get_age(self):
        return self.__age

    def set_age(self, age):
        if age >= 0:
            self.__age = age

# Usage
person = Person("Alice", 30)
print(person.get_name())  # Alice
person.set_age(31)
print(person.get_age())  # 31

2. Implement a class Movie with private attributes for title and year. Provide methods to get and set these attributes.

class Movie:
    def __init__(self, title, year):
        self.__title = title
        self.__year = year

    def get_title(self):
        return self.__title

    def set_title(self, title):
        self.__title = title

    def get_year(self):
        return self.__year

    def set_year(self, year):
        if year > 1800:
            self.__year = year

# Usage
movie = Movie("Inception", 2010)
print(movie.get_title())  # Inception
movie.set_year(2012)
print(movie.get_year())  # 2012

3. Write a class Dog with a private attribute for name and a method to make the dog bark.

class Dog:
    def __init__(self, name):
        self.__name = name

    def bark(self):
        print(f"{self.__name} says Woof!")

# Usage
dog = Dog("Buddy")
dog.bark()  # Buddy says Woof!

4. Implement a class Account with private attributes for account_number and balance. Provide methods to deposit and withdraw funds.

class Account:
    def __init__(self, account_number, balance):
        self.__account_number = account_number
        self.__balance = balance

    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount

    def withdraw(self, amount):
        if 0 < amount <= self.__balance:


            self.__balance -= amount

    def get_balance(self):
        return self.__balance

# Usage
account = Account("123456789", 1000)
account.deposit(500)
print(account.get_balance())  # 1500
account.withdraw(200)
print(account.get_balance())  # 1300

5. Create a class Rectangle with private attributes for length and width. Include methods to calculate the area and perimeter.

class Rectangle:
    def __init__(self, length, width):
        self.__length = length
        self.__width = width

    def get_area(self):
        return self.__length * self.__width

    def get_perimeter(self):
        return 2 * (self.__length + self.__width)

# Usage
rectangle = Rectangle(5, 3)
print(rectangle.get_area())  # 15
print(rectangle.get_perimeter())  # 16

6. Implement a class Temperature with a private attribute for celsius. Provide methods to convert between Celsius and Fahrenheit.

class Temperature:
    def __init__(self, celsius):
        self.__celsius = celsius

    def to_fahrenheit(self):
        return (self.__celsius * 9/5) + 32

    def from_fahrenheit(self, fahrenheit):
        self.__celsius = (fahrenheit - 32) * 5/9

# Usage
temp = Temperature(25)
print(temp.to_fahrenheit())  # 77.0
temp.from_fahrenheit(32)
print(temp._Temperature__celsius)  # 0.0 (Note: Accessing private attribute directly)

7. Write a class Student with private attributes for name and grades. Provide methods to add a grade and calculate the average grade.

class Student:
    def __init__(self, name):
        self.__name = name
        self.__grades = []

    def add_grade(self, grade):
        self.__grades.append(grade)

    def average_grade(self):
        return sum(self.__grades) / len(self.__grades) if self.__grades else 0

# Usage
student = Student("John")
student.add_grade(85)
student.add_grade(90)
print(student.average_grade())  # 87.5

8. Implement a class BankAccount with a private attribute for balance. Include methods for checking the balance and transferring funds between accounts.

class BankAccount:
    def __init__(self, balance):
        self.__balance = balance

    def check_balance(self):
        return self.__balance

    def transfer(self, amount, target_account):
        if 0 < amount <= self.__balance:
            self.__balance -= amount
            target_account.__balance += amount

# Usage
account1 = BankAccount(500)
account2 = BankAccount(300)
account1.transfer(100, account2)
print(account1.check_balance())  # 400
print(account2.check_balance())  # 400

9. Create a class Person with private attributes for first_name and last_name. Implement methods to get full name and set last name.

class Person:
    def __init__(self, first_name, last_name):
        self.__first_name = first_name
        self.__last_name = last_name

    def get_full_name(self):
        return f"{self.__first_name} {self.__last_name}"

    def set_last_name(self, last_name):
        self.__last_name = last_name

# Usage
person = Person("Jane", "Doe")
print(person.get_full_name())  # Jane Doe
person.set_last_name("Smith")
print(person.get_full_name())  # Jane Smith

10. Implement a class Circle with a private attribute for radius. Include methods to calculate the area and circumference of the circle.

import math

class Circle:
    def __init__(self, radius):
        self.__radius = radius

    def get_area(self):
        return math.pi * self.__radius ** 2

    def get_circumference(self):
        return 2 * math.pi * self.__radius

# Usage
circle = Circle(5)
print(circle.get_area())  # 78.53981633974483
print(circle.get_circumference())  # 31.41592653589793

11. Write a class LibraryBook with private attributes for title and author. Provide methods to borrow and return the book.

class LibraryBook:
    def __init__(self, title, author):
        self.__title = title
        self.__author = author
        self.__borrowed = False

    def borrow(self):
        if not self.__borrowed:
            self.__borrowed = True
            return True
        return False

    def return_book(self):
        if self.__borrowed:
            self.__borrowed = False
            return True
        return False

# Usage
book = LibraryBook("1984", "George Orwell")
print(book.borrow())  # True
print(book.return_book())  # True

12. Implement a class Invoice with private attributes for items and total_amount. Provide methods to add items and calculate the total amount.

class Invoice:
    def __init__(self):
        self.__items = []
        self.__total_amount = 0

    def add_item(self, item_name, amount):
        self.__items.append((item_name, amount))
        self.__total_amount += amount

    def get_total_amount(self):
        return self.__total_amount

# Usage
invoice = Invoice()
invoice.add_item("Item1", 25.50)
invoice.add_item("Item2", 15.75)
print(invoice.get_total_amount())  # 41.25

13. Create a class Flight with private attributes for flight_number and destination. Include methods to update the destination and get flight details.

class Flight:
    def __init__(self, flight_number, destination):
        self.__flight_number = flight_number
        self.__destination = destination

    def update_destination(self, destination):
        self.__destination = destination

    def get_flight_details(self):
        return f"Flight {self.__flight_number} to {self.__destination}"

# Usage
flight = Flight("AB123", "New York")
print(flight.get_flight_details())  # Flight AB123 to New York
flight.update_destination("Los Angeles")
print(flight.get_flight_details())  # Flight AB123 to Los Angeles

14. Implement a class Employee with private attributes for name and salary. Provide methods to set salary and give a raise.

class Employee:
    def __init__(self, name, salary):
        self.__name = name
        self.__salary = salary

    def set_salary(self, salary):
        self.__salary = salary

    def give_raise(self, percentage):
        if percentage > 0:
            self.__salary += self.__salary * (percentage / 100)

# Usage
employee = Employee("Alice", 50000)
employee.give_raise(10)
print(employee._Employee__salary)  # 55000 (Note: Accessing private attribute directly)

15. Write a class Product with private attributes for product_id and price. Implement methods to apply discount and get the final price.

class Product:
    def __init__(self, product_id, price):
        self.__product_id = product_id
        self.__price = price

    def apply_discount(self, discount_percentage):
        if 0 <= discount_percentage <= 100:
            self.__price -= self.__price * (discount_percentage / 100)

    def get_final_price(self):
        return self.__price

# Usage
product = Product("P123", 100)
product.apply_discount(20)
print(product.get_final_price())  # 80.0

16. Create a class Account with private attributes for account_number and balance. Include methods to deposit, withdraw, and get balance.

class Account:
    def __init__(self, account_number, balance):
        self.__account_number = account_number
        self.__balance = balance

    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount

    def withdraw(self, amount):
        if 0 < amount <= self.__balance:
            self.__balance -= amount

    def get_balance(self):
        return self.__balance

# Usage
account = Account("A001", 1000)
account.deposit(500)
print(account.get_balance())  # 1500
account.withdraw(200)
print(account.get_balance())  # 1300

17. Implement a class Temperature with private attributes for celsius. Provide methods to convert between Celsius and Fahrenheit.

class Temperature:
    def __init__(self, celsius):
        self.__celsius = celsius

    def to_fahrenheit(self):
        return (self.__celsius * 9/5) + 32

    def from_fahrenheit(self, fahrenheit):
        self.__celsius = (fahrenheit - 32) * 5/9

# Usage
temp = Temperature(30)
print(temp.to_fahrenheit())  # 86.0
temp.from_fahrenheit(32)
print(temp._Temperature__celsius)  # 0

.0 (Note: Accessing private attribute directly)

18. Write a class Book with private attributes for title and author. Include methods to borrow and return the book.

class Book:
    def __init__(self, title, author):
        self.__title = title
        self.__author = author
        self.__borrowed = False

    def borrow(self):
        if not self.__borrowed:
            self.__borrowed = True
            return True
        return False

    def return_book(self):
        if self.__borrowed:
            self.__borrowed = False
            return True
        return False

# Usage
book = Book("To Kill a Mockingbird", "Harper Lee")
print(book.borrow())  # True
print(book.return_book())  # True

19. Create a class Library with private attributes for name and books. Include methods to add books and list all available books.

class Library:
    def __init__(self, name):
        self.__name = name
        self.__books = []

    def add_book(self, book):
        self.__books.append(book)

    def list_books(self):
        return [book.get_title() for book in self.__books]

# Usage
class Book:
    def __init__(self, title):
        self.__title = title

    def get_title(self):
        return self.__title

library = Library("City Library")
book1 = Book("1984")
book2 = Book("To Kill a Mockingbird")
library.add_book(book1)
library.add_book(book2)
print(library.list_books())  # ['1984', 'To Kill a Mockingbird']

20. Implement a class Person with private attributes for name and age. Provide methods to update age and get the name and age.

class Person:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    def update_age(self, age):
        if age >= 0:
            self.__age = age

    def get_name_and_age(self):
        return self.__name, self.__age

# Usage
person = Person("John Doe", 30)
print(person.get_name_and_age())  # ('John Doe', 30)
person.update_age(31)
print(person.get_name_and_age())  # ('John Doe', 31)
0
Subscribe to my newsletter

Read articles from Tarun Sharma directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Tarun Sharma
Tarun Sharma

Hi there! I’m Tarun, a Senior Software Engineer with a passion for technology and coding. With experience in Python, Java, and various backend development practices, I’ve spent years honing my skills and working on exciting projects. On this blog, you’ll find insights, tips, and tutorials on topics ranging from object-oriented programming to tech trends and interview prep. My goal is to share valuable knowledge and practical advice to help fellow developers grow and succeed. When I’m not coding, you can find me exploring new tech trends, working on personal projects, or enjoying a good cup of coffee. Thanks for stopping by, and I hope you find my content helpful!