Encapsulation is a fundamental concept in object-oriented programming (OOP) that involves bundling the data (attributes) and methods (functions) that operate on the data into a single unit or class. It also restricts direct access to some of an object's components, which can prevent the accidental modification of data. This tutorial will cover how to use public, protected, and private access modifiers in Python, including name mangling and using properties for controlled access.
Encapsulation is crucial because it helps maintain the integrity of your code by preventing unintended changes to the internal state of an object. By controlling access to attributes and methods, you can ensure that objects are used correctly and that their behavior remains consistent. This makes your code more robust and easier to manage.
In Python, encapsulation is achieved through naming conventions for class members:
Public attributes can be accessed from any part of your program. They do not have any special naming convention.
1class Car:2def __init__(self, make, model):3self.make = make # public attribute4self.model = model # public attribute56def display_info(self):7print(f"Car: {self.make} {self.model}")89# Creating an instance of the Car class10my_car = Car("Toyota", "Corolla")11my_car.display_info()
Car: Toyota Corolla
Protected attributes are indicated by a single underscore prefix. They should not be accessed from outside the class or its subclasses, although Python does not enforce this strictly.
1class Car:2def __init__(self, make, model):3self._make = make # protected attribute4self._model = model # protected attribute56def display_info(self):7print(f"Car: {_make} {_model}")89# Creating an instance of the Car class10my_car = Car("Toyota", "Corolla")11print(my_car._make) # Accessing a protected attribute (not recommended)
Car: Toyota Corolla Toyota
Tip
Private attributes are indicated by a double underscore prefix. They are intended to be used only within the class itself. Python uses name mangling to make these attributes harder to access from outside the class.
1class Car:2def __init__(self, make, model):3self.__make = make # private attribute4self.__model = model # private attribute56def display_info(self):7print(f"Car: {self.__make} {self.__model}")89# Creating an instance of the Car class10my_car = Car("Toyota", "Corolla")11my_car.display_info()12# print(my_car.__make) # This will raise an AttributeError
Car: Toyota Corolla AttributeError: 'Car' object has no attribute '__make'
Warning
Python automatically renames private attributes by adding an underscore followed by the class name to the beginning of the attribute name. This makes it harder to access these attributes from outside the class.
1class Car:2def __init__(self, make, model):3self.__make = make4self.__model = model56# Creating an instance of the Car class7my_car = Car("Toyota", "Corolla")8print(my_car._Car__make) # Accessing a private attribute using name mangling (not recommended)
Toyota
Note
Properties provide a way to control access to attributes by defining getter, setter, and deleter methods.
1class Car:2def __init__(self, make, model):3self._make = make4self._model = model56@property7def make(self):8return self._make910@make.setter11def make(self, value):12if isinstance(value, str) and value.isalpha():13self._make = value14else:15raise ValueError("Make must be a string containing only alphabetic characters.")1617@property18def model(self):19return self._model2021@model.setter22def model(self, value):23if isinstance(value, str):24self._model = value25else:26raise ValueError("Model must be a string.")2728# Creating an instance of the Car class29my_car = Car("Toyota", "Corolla")30print(my_car.make) # Accessing using property getter3132my_car.make = "Honda" # Setting using property setter33print(my_car.make)3435# my_car.make = 12345 # This will raise a ValueError
Toyota Honda ValueError: Make must be a string containing only alphabetic characters.
Tip
Let's create a practical example that combines encapsulation concepts. We'll define a BankAccount class with private attributes for the account balance and methods to deposit, withdraw, and display the balance.
1class BankAccount:2def __init__(self, owner, initial_balance=0):3self._owner = owner4self.__balance = initial_balance56@property7def balance(self):8return self.__balance910def deposit(self, amount):11if amount > 0:12self.__balance += amount13print(f"Deposited ${amount}. New balance: ${self.__balance}")14else:15raise ValueError("Deposit amount must be positive.")1617def withdraw(self, amount):18if 0 < amount <= self.__balance:19self.__balance -= amount20print(f"Withdrew ${amount}. Remaining balance: ${self.__balance}")21else:22raise ValueError("Invalid withdrawal amount.")2324# Creating an instance of the BankAccount class25account = BankAccount("Alice", 100)26print(account.balance) # Accessing using property getter2728account.deposit(50)29account.withdraw(30)3031# account.__balance = -100 # This will raise an AttributeError
100 Deposited $50. New balance: $150 Withdrew $30. Remaining balance: $120 AttributeError: 'BankAccount' object has no attribute '__balance'
| Concept | Description |
|---|---|
| Public | Accessible from anywhere. |
| Protected | Intended to be accessed within the class and subclasses (single underscore). |
| Private | Intended to be used only within the class itself (double underscore). |
| Name Mangling | Python renames private attributes to make them harder to access externally. |
| Properties | Control access to attributes with getter, setter, and deleter methods. |
Now that you have a solid understanding of encapsulation in Python, the next topic will cover list comprehensions. List comprehensions provide a concise way to create lists based on existing lists. This powerful feature is essential for writing clean and efficient code. Stay tuned!