Please use a larger screen
This presentation is designed for desktop or projector displays.
← Back to curriculum
|
PYTHON OOP — COURSE 2
Encapsulation & Access Control
Information hiding, properties & Pythonic interfaces
Object
public
_protected
__private
← → to navigate
Course Curriculum
Where we are in the journey
1. Classes & Objects
2. Encapsulation & Access Control
3. Inheritance
4. Polymorphism & PEP 8
5. Composition & Aggregation
6. Exception Handling & Logging
7. SOLID Principles
8. UML & Design Patterns
9. Design Patterns (2nd part)
10. Testing & TDD
11. Libraries & Frameworks
12. Recap & Consultation
Agenda
Recap quiz — review of Lecture 01
OOP History — from Simula to Python
Core concepts — data, behavior & interaction
Abstraction — hiding complexity
Encapsulation — bundling data & methods
Access control — public, protected & private
Properties — @property decorator
Modules — organizing code
Recap — key takeaways
Quiz Time! (Lecture 01)
What is a class in Python?
A) A variable that holds data
B) A blueprint for creating objects
A class is a blueprint that defines attributes and methods for creating objects
Recap: Lecture 01
Classes = Blueprints
A class defines attributes (data) and methods (behavior)
Objects are instances created from a class
Each object has its own copy of the data
class Book :
def __init__ (self , title, author):
self .title = title
self .author = author
b1 = Book ("Clean Code" , "Martin" )
b2 = Book ("Refactoring" , "Fowler" )
BLUEPRINT
title, author
__init__()
b1
b2
Quiz Time! (Lecture 01)
What does self refer to inside a method?
self refers to the specific object instance that called the method
The self Parameter
A way for an object to refer to itself
self is the first parameter in every instance method
It lets the method access the instance's attributes and other methods
Python passes it automatically when you call a method
class Person :
def __init__ (self , name, age):
self .name = name
self .age = age
def greet (self ):
print (f"Hi, I'm {self.name}" )
p = Person ("Alice" , 30 )
p.greet ()
# Hi, I'm Alice
Quiz Time! (Lecture 01)
What is the process of creating an object from a class called?
Creating an object from a class is called instantiation — the object is an instance of the class
Quiz Time! (Lecture 01)
Which magic method is called when you use len() on an object?
Python calls __len__ when you use the built-in len() function on an object
Quiz Time! (Lecture 01)
What's the output?
class Dog :
def __init__ (self , name):
self .name = name
def __str__ (self ):
return f"Dog: {self.name}"
buddy = Dog ("Buddy" )
print (buddy)
print() calls __str__, which returns "Dog: Buddy"
OOP Timeline
The evolution of object-oriented programming
60s
Simula
First OOP language
70s
Smalltalk
Everything is an object
80-90s
C++ / Java
OOP goes mainstream
00s
Python
Multi-paradigm
[1960s] Simula
Created by Ole-Johan Dahl & Kristen Nygaard
Developed at the Norwegian Computing Center
Originally designed for ship simulations
Introduced classes , objects , inheritance and virtual methods
The first language to support object-oriented concepts
Simula
1967 — Norway
classes + objects + inheritance
Dahl & Nygaard
Simula Code Example
A BankAccount in Simula syntax (1967)
Class BankAccount;
Begin
Real balance;
Procedure Deposit(amount);
Real amount;
Begin
balance := balance + amount;
End;
Procedure Withdraw(amount);
Real amount;
Begin
balance := balance - amount;
End;
End;
Already looks familiar — classes, methods, attributes!
[1970s] Smalltalk
Created by Alan Kay at Xerox PARC
Coined the term "object-oriented programming"
Everything is an object — even numbers, booleans, classes
Introduced message passing between objects
Influenced virtually every modern OOP language
Smalltalk
1972 — Xerox PARC
"everything is an object"
Alan Kay
Smalltalk Code Example
A BankAccount in Smalltalk syntax
Object subclass: #BankAccount
instanceVariableNames: 'balance'.
BankAccount >> initialize
balance := 0.
BankAccount >> deposit: amount
balance := balance + amount.
BankAccount >> withdraw: amount
balance := balance - amount.
Message passing: account deposit: 100 sends the message "deposit:" to the object
[1980s] OOP Goes Mainstream
C++ (1979/1983) — Bjarne Stroustrup, "C with Classes"
Objective-C (1984) — Smalltalk-inspired, later adopted by Apple
Eiffel (1986) — Bertrand Meyer, "Design by Contract"
OOP becomes the dominant paradigm in industry software development
[1990s] Standardization
Java (1995) — Sun Microsystems, "Write Once, Run Anywhere"
UML (1997) — Unified Modeling Language for OOP design
Design Patterns (1994) — Gang of Four book
Python (1991) — Guido van Rossum, multi-paradigm from the start
OOP matures with formal standards and best practices
[2000s+] Modern Multi-Paradigm
C# (2000) — Microsoft's answer to Java
Swift (2014) — Apple, protocol-oriented programming
Python 3 (2008) — everything inherits from object
Multi-paradigm — blend OOP with functional & procedural
Modern languages are multi-paradigm — OOP is one tool among many
SECTION
So... what's OOP?
Motivation & core concepts
Data — The Nouns of Software
Data represents things in your program
A user's name, age, email
A product's price, stock count
In OOP: data lives as attributes inside objects
DATA
name = "Alice"
age = 30
email = "a@b.com"
Behavior — The Verbs of Software
Behavior represents actions your program can perform
Login, send email, calculate total
In OOP: behavior lives as methods inside objects
Methods operate on the object's data
BEHAVIOR
login()
send_email()
calculate()
Data + Behavior = Object
OOP bundles data and behavior together
An object knows its data and knows how to act on it
This bundling is called encapsulation
OBJECT
Data (attributes)
name, age, email
Behavior (methods)
login(), send_email()
Four Pillars of OOP
The core characteristics of object-oriented programming
🛡
Abstraction
Hide complexity, show essentials
🔒
Encapsulation
Bundle data & restrict access
🔁
Polymorphism
One interface, many forms
📂
Inheritance
Reuse & extend existing code
Abstraction: Car Example
🚗
What you see
Steering wheel
Gas pedal
Dashboard
⚙
What's hidden
Engine internals
Fuel injection system
Transmission gears
Abstraction = expose a simple interface , hide the complex implementation
Abstraction in Python
class Car :
def __init__ (self , model, fuel):
self .model = model
self ._fuel = fuel
def drive (self , distance):
consumption = self ._calculate_fuel_consumption (distance)
self ._fuel -= consumption
print (f"Drove {distance}km, fuel left: {self._fuel}L" )
def _calculate_fuel_consumption (self , distance):
return distance * 0.08
car = Car ("Tesla" , 50 )
car.drive (100 ) # Simple public interface
drive() is public — _calculate_fuel_consumption() is an internal detail
Abstraction: TV Example
📺
Remote Control (Interface)
Power on/off
Change channel
Adjust volume
🔌
Internal Circuitry (Hidden)
Signal processing
LED matrix control
Power regulation
Abstraction: Bank Account
🏦
Simple Interface
deposit()
withdraw()
get_balance()
🔒
Complex Backend
Fraud detection
Transaction logging
Interest calculations
Users don't need to know how it works — just what it does
Encapsulation: Information Hiding
Encapsulation = bundling data + methods that operate on that data
Information hiding = restricting direct access to internal state
External code interacts only through public methods
Internal data is protected from accidental modification
ENCAPSULATED OBJECT
public methods()
_protected data
__private
Access Control: House Analogy
Three levels of access in Python
Public
🏠
self.name
Front Yard
Protected
🛋
self._name
Living Room
Private
🛌
self.__name
Bedroom
Access Control in Python
Python uses naming conventions , not strict enforcement
Prefix
Level
Meaning
name
Public
Accessible from anywhere
_name
Protected
Convention: internal use only
__name
Private
Name mangling applied
Public Attributes
class Car :
def __init__ (self , model):
self .model = model
self .engine_status = "off"
def start (self ):
self .engine_status = "running"
car = Car ("Toyota" )
print (car.engine_status) # "off"
car.start ()
print (car.engine_status) # "running"
car.engine_status = "broken" # Anyone can modify!
Public attributes can be accessed and modified by anyone — no restrictions
Protected Attributes
class Car :
def __init__ (self , model, fuel):
self .model = model
self ._fuel_level = fuel
def drive (self , distance):
self ._fuel_level -= distance * 0.08
def get_fuel (self ):
return self ._fuel_level
car = Car ("Toyota" , 50 )
print (car._fuel_level) # Works, but convention says: don't!
Single underscore _ = "I'm internal, please don't touch me directly"
Private Attributes
class Car :
def __init__ (self , model, owner):
self .model = model
self .__owner = owner
def get_owner (self ):
return self .__owner
car = Car ("Toyota" , "Alice" )
print (car.get_owner ()) # "Alice"
print (car.__owner) # AttributeError!
print (car._Car__owner) # "Alice" (name mangling)
Double underscore __ triggers name mangling : __owner becomes _Car__owner
Access Control Comparison
Feature
Public
Protected _
Private __
Access from outside
✓
✓ (discouraged)
✗ (mangled)
Access from subclass
✓
✓
✗ (mangled)
Enforced by Python
N/A
No (convention)
Partially
Use case
API / interface
Internal logic
Avoid name clashes
Getters & Setters
The traditional approach
class MyClass :
def __init__ (self , value):
self ._value = value
def get_value (self ):
return self ._value
def set_value (self , new_value):
if new_value >= 0 :
self ._value = new_value
obj = MyClass (10 )
print (obj.get_value ()) # 10
obj.set_value (20 )
print (obj.get_value ()) # 20
Works, but not very Pythonic
The @property Decorator
Same thing, but Pythonic
class MyClass :
def __init__ (self , value):
self ._value = value
@property
def value (self ):
return self ._value
@value .setter
def value (self , new_value):
if new_value >= 0 :
self ._value = new_value
obj = MyClass (10 )
print (obj.value) # 10 (uses getter)
obj.value = 20 # uses setter
Looks like attribute access, but runs validation logic behind the scenes
@property: Circle Example
import math
class Circle :
def __init__ (self , radius):
self ._radius = radius
@property
def radius (self ):
return self ._radius
@radius .setter
def radius (self , value):
if value <= 0 :
raise ValueError ("Radius must be positive" )
self ._radius = value
@property
def area (self ):
return math.pi * self ._radius ** 2
c = Circle (5 )
print (c.area) # 78.54 (computed property)
@property: BankAccount
class BankAccount :
def __init__ (self , owner, balance=0 ):
self .owner = owner
self ._balance = balance
@property
def balance (self ):
return self ._balance
def deposit (self , amount):
if amount > 0 :
self ._balance += amount
def withdraw (self , amount):
if 0 < amount <= self ._balance:
self ._balance -= amount
acc = BankAccount ("Alice" , 1000 )
print (acc.balance) # 1000 (read-only property)
acc.deposit (500 )
print (acc.balance) # 1500
⚠ Bypassing Protected Access
class BankAccount :
def __init__ (self , balance):
self ._balance = balance
@property
def balance (self ):
return self ._balance
acc = BankAccount (1000 )
# BAD: bypassing the interface
acc._balance = 999999
print (acc.balance) # 999999 — no validation!
Python trusts developers — the underscore is a social contract , not a wall
Proper Usage Pattern
class Temperature :
def __init__ (self , celsius):
self .celsius = celsius
@property
def celsius (self ):
return self ._celsius
@celsius .setter
def celsius (self , value):
if value < -273.15 :
raise ValueError ("Below absolute zero!" )
self ._celsius = value
@property
def fahrenheit (self ):
return self ._celsius * 9 /5 + 32
t = Temperature (100 )
print (t.fahrenheit) # 212.0
Validation in the setter + computed properties = clean, safe interface
SECTION
Modules
Organizing code into reusable files
Calculator Module Structure
A module = a single .py file
A package = a directory with __init__.py
Modules help organize and reuse code
calculator/
__init__.py
addition.py
subtraction.py
multiplication.py
division.py
main.py
Module Files
addition.py
def add (a, b):
return a + b
subtraction.py
def subtract (a, b):
return a - b
multiplication.py
def multiply (a, b):
return a * b
division.py
def divide (a, b):
if b == 0 :
raise ValueError ("Cannot divide by zero" )
return a / b
Using Modules: main.py
from calculator.addition import add
from calculator.subtraction import subtract
from calculator.multiplication import multiply
from calculator.division import divide
print (add (10 , 5 )) # 15
print (subtract (10 , 5 )) # 5
print (multiply (10 , 5 )) # 50
print (divide (10 , 5 )) # 2.0
Each operation lives in its own module — clean separation of concerns
Benefits of Modules
✓ Organization
Related code lives together in logical units
✓ Reusability
Import and reuse across multiple projects
✓ Namespace
Avoid naming conflicts between different parts of code
✓ Maintainability
Smaller files are easier to understand and debug
Python Standard Library
Built-in modules you'll use often
math
import math
print (math.pi) # 3.14159...
print (math.sqrt (16 )) # 4.0
datetime
from datetime import date
today = date.today ()
print (today) # 2026-03-12
os
import os
print (os.getcwd ())
print (os.listdir ("." ))
random
import random
print (random.randint (1 , 10 ))
print (random.choice (["a" , "b" ]))
OOP Drawbacks
⚠ Complexity
Class hierarchies can become deeply nested and hard to follow
⚠ Performance
Object creation and method dispatch add overhead
⚠ Development Time
Designing good class structures takes more upfront planning
⚠ Over-Engineering
Not every problem needs classes — sometimes a function is enough
Quiz Time! (Lecture 02)
What is the convention for a protected attribute in Python?
A single leading underscore _name signals that the attribute is protected (internal use)
Quiz Time! (Lecture 02)
What does the @property decorator provide?
A) Private attribute creation
B) A getter/setter interface
D) Static method declaration
@property lets you define getter/setter methods that look like attribute access
Quiz Time! (Lecture 02)
What happens when you use __var in a class?
A) Python raises an error
B) Python renames it to _ClassName__var
D) It's deleted at runtime
Python applies name mangling : __var becomes _ClassName__var to avoid accidental overrides
Key Takeaways
Abstraction — hide complexity, expose simple interfaces
Encapsulation — bundle data + methods, restrict access
Access control — public, _protected, __private
@property — Pythonic getters/setters with validation
Name mangling — __var becomes _Class__var
Modules — organize code into reusable files & packages
Convention over enforcement — Python trusts developers
Course Curriculum
What's next
✓ 1. Classes & Objects
✓ 2. Encapsulation & Access Control
3. Inheritance
4. Polymorphism & PEP 8
5. Composition & Aggregation
6. Exception Handling & Logging
7. SOLID Principles
8. UML & Design Patterns
9. Design Patterns (2nd part)
10. Testing & TDD
11. Libraries & Frameworks
12. Recap & Consultation
Next up: Inheritance
Thank You!
Questions?
See you next lecture for Inheritance
Skip Quiz ▶
☼
‹
›