Slide 1: What is Encapsulation?
Encapsulation is a fundamental concept in object-oriented programming that bundles data and the methods that operate on that data within a single unit or object. It provides data hiding and access control, allowing you to restrict direct access to an object's internal state.
class BankAccount:
def __init__(self):
self.__balance = 0 # Private attribute
def deposit(self, amount):
if amount > 0:
self.__balance += amount
def get_balance(self):
return self.__balance
account = BankAccount()
account.deposit(100)
print(account.get_balance()) # Output: 100
# print(account.__balance) # This would raise an AttributeErrorSlide 2: Public vs. Private Attributes
In Python, we use naming conventions to indicate the accessibility of attributes. Public attributes are directly accessible, while private attributes are prefixed with double underscores.
class Car:
def __init__(self):
self.color = "red" # Public attribute
self.__mileage = 0 # Private attribute
def drive(self, distance):
self.__mileage += distance
def get_mileage(self):
return self.__mileage
car = Car()
print(car.color) # Output: red
car.drive(100)
print(car.get_mileage()) # Output: 100
# print(car.__mileage) # This would raise an AttributeErrorSlide 3: Name Mangling
Python uses name mangling for private attributes. It prefixes the attribute name with _ClassName to make it harder to access from outside the class.
class Example:
def __init__(self):
self.__private = "I'm private"
obj = Example()
print(dir(obj)) # Output: [..., '_Example__private', ...]
print(obj._Example__private) # Output: I'm privateSlide 4: Property Decorators
Property decorators provide a way to use methods as attributes, allowing for controlled access to private attributes.
class Temperature:
def __init__(self):
self.__celsius = 0
@property
def celsius(self):
return self.__celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("Temperature below absolute zero is not possible")
self.__celsius = value
@property
def fahrenheit(self):
return (self.__celsius * 9/5) + 32
temp = Temperature()
temp.celsius = 25
print(temp.celsius) # Output: 25
print(temp.fahrenheit) # Output: 77.0Slide 5: Getters and Setters
Getters and setters are methods used to access and modify private attributes, providing an additional layer of control.
class Circle:
def __init__(self, radius):
self.__radius = radius
def get_radius(self):
return self.__radius
def set_radius(self, value):
if value > 0:
self.__radius = value
else:
raise ValueError("Radius must be positive")
def area(self):
return 3.14 * self.__radius ** 2
circle = Circle(5)
print(circle.get_radius()) # Output: 5
circle.set_radius(7)
print(circle.area()) # Output: 153.86Slide 6: Encapsulation in Inheritance
Encapsulation also plays a role in inheritance. Private attributes are not directly accessible in child classes, but protected attributes (prefixed with a single underscore) are.
class Animal:
def __init__(self):
self.__private_attr = "I'm private"
self._protected_attr = "I'm protected"
class Dog(Animal):
def access_attributes(self):
# print(self.__private_attr) # This would raise an AttributeError
print(self._protected_attr) # This works
dog = Dog()
dog.access_attributes() # Output: I'm protectedSlide 7: Real-life Example: Smart Home System
A smart home system demonstrates encapsulation by hiding complex operations and providing a simple interface.
class SmartHome:
def __init__(self):
self.__temperature = 20
self.__lights_on = False
def set_temperature(self, temp):
if 15 <= temp <= 30:
self.__temperature = temp
self.__adjust_hvac()
else:
print("Temperature out of range")
def __adjust_hvac(self):
print(f"Adjusting HVAC to {self.__temperature}°C")
def toggle_lights(self):
self.__lights_on = not self.__lights_on
print(f"Lights are {'on' if self.__lights_on else 'off'}")
home = SmartHome()
home.set_temperature(23) # Output: Adjusting HVAC to 23°C
home.toggle_lights() # Output: Lights are onSlide 8: Real-life Example: Library Management System
A library management system showcases encapsulation by managing book information and lending processes internally.
class Library:
def __init__(self):
self.__books = {}
def add_book(self, title, author):
if title not in self.__books:
self.__books[title] = {"author": author, "available": True}
print(f"Added: {title} by {author}")
else:
print("Book already exists")
def borrow_book(self, title):
if title in self.__books and self.__books[title]["available"]:
self.__books[title]["available"] = False
print(f"Borrowed: {title}")
else:
print("Book not available")
def return_book(self, title):
if title in self.__books:
self.__books[title]["available"] = True
print(f"Returned: {title}")
else:
print("This book doesn't belong to our library")
library = Library()
library.add_book("1984", "George Orwell")
library.borrow_book("1984")
library.return_book("1984")Slide 9: Encapsulation and Data Validation
Encapsulation allows for data validation, ensuring that object attributes maintain valid states.
class Person:
def __init__(self, name, age):
self.__name = name
self.__age = age
@property
def age(self):
return self.__age
@age.setter
def age(self, value):
if 0 <= value <= 150:
self.__age = value
else:
raise ValueError("Invalid age")
def __str__(self):
return f"{self.__name} is {self.__age} years old"
person = Person("Alice", 30)
print(person) # Output: Alice is 30 years old
person.age = 35
print(person) # Output: Alice is 35 years old
# person.age = 200 # This would raise a ValueErrorSlide 10: Encapsulation in Context Managers
Context managers use encapsulation to manage resources, ensuring proper setup and cleanup.
class FileManager:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
# Usage
with FileManager('example.txt', 'w') as f:
f.write('Hello, World!')
# File is automatically closed after the with blockSlide 11: Encapsulation and Method Chaining
Encapsulation can facilitate method chaining, allowing for more fluent and readable code.
class StringBuilder:
def __init__(self):
self.__string = ""
def append(self, text):
self.__string += text
return self
def remove(self, text):
self.__string = self.__string.replace(text, "")
return self
def upper(self):
self.__string = self.__string.upper()
return self
def __str__(self):
return self.__string
result = (StringBuilder()
.append("Hello")
.append(" ")
.append("World")
.remove("o")
.upper())
print(result) # Output: HELL WRLDSlide 12: Encapsulation and Decorators
Decorators can be used to add encapsulation-like behavior to functions and methods.
def validate_args(func):
def wrapper(self, *args):
if len(args) != 2 or not all(isinstance(arg, (int, float)) for arg in args):
raise ValueError("Two numeric arguments are required")
return func(self, *args)
return wrapper
class Calculator:
@validate_args
def add(self, x, y):
return x + y
calc = Calculator()
print(calc.add(5, 3)) # Output: 8
# print(calc.add("5", "3")) # This would raise a ValueErrorSlide 13: Encapsulation and Abstraction
Encapsulation supports abstraction by hiding implementation details and exposing only necessary interfaces.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Square(Shape):
def __init__(self, side):
self.__side = side
def area(self):
return self.__side ** 2
class Circle(Shape):
def __init__(self, radius):
self.__radius = radius
def area(self):
return 3.14 * self.__radius ** 2
shapes = [Square(5), Circle(3)]
for shape in shapes:
print(f"Area: {shape.area()}")
# Output:
# Area: 25
# Area: 28.26Slide 14: Encapsulation and Design Patterns
Encapsulation plays a crucial role in various design patterns, such as the Singleton pattern, which ensures only one instance of a class exists.
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.__initialized = False
return cls._instance
def __init__(self):
if not self.__initialized:
self.__initialized = True
self.__data = []
def add_data(self, item):
self.__data.append(item)
def get_data(self):
return self.__data.()
s1 = Singleton()
s2 = Singleton()
s1.add_data("Item 1")
print(s2.get_data()) # Output: ['Item 1']
print(s1 is s2) # Output: TrueSlide 15: Encapsulation in Asynchronous Programming
Encapsulation can be applied in asynchronous programming to manage shared state and ensure thread safety.
import asyncio
class AsyncCounter:
def __init__(self):
self.__count = 0
self.__lock = asyncio.Lock()
async def increment(self):
async with self.__lock:
self.__count += 1
await asyncio.sleep(0.1) # Simulate some work
return self.__count
async def get_count(self):
async with self.__lock:
return self.__count
async def worker(counter, name):
for _ in range(3):
value = await counter.increment()
print(f"{name}: {value}")
async def main():
counter = AsyncCounter()
await asyncio.gather(
worker(counter, "Worker 1"),
worker(counter, "Worker 2")
)
print(f"Final count: {await counter.get_count()}")
asyncio.run(main())
# Sample Output:
# Worker 1: 1
# Worker 2: 2
# Worker 1: 3
# Worker 2: 4
# Worker 1: 5
# Worker 2: 6
# Final count: 6Slide 16: Additional Resources
For further exploration of encapsulation and related topics in Python:
- "Data Encapsulation in Python" - arXiv:1803.04644 [cs.PL] https://arxiv.org/abs/1803.04644
- "Design Patterns: Elements of Reusable Object-Oriented Software" by Erich Gamma et al. This book, while not Python-specific, provides valuable insights into design patterns that leverage encapsulation.
- "Fluent Python" by Luciano Ramalho This book offers an in-depth look at Python's object model and how to effectively use encapsulation in Python.
- "Python in Practice" by Mark Summerfield This book includes practical examples of design patterns and best practices in Python, including proper use of encapsulation.