Encapsulation
On this page
Public AttributesInternal Attributes:_nameName-Mangled Attributes: __nameGetter and Setter MethodsManaged Attributes with propertyInternal Helper MethodsCoding Challenge 1: Encapsulation – BankAccountSavingsAccount Follow-upEncapsulation keeps data and its operations together and provides a clear public interface for using them. Python uses naming conventions and managed attributes rather than enforced public/protected/private access levels.
In this UML activity diagram, rounded boxes are actions and the diamond checks a condition. The accepted path changes state; the rejected path does not.
Public Attributes
class User:
def __init__(self, name):
self.name = name # public attribute
u = User("Anna")
print(u.name)Output
Anna
A public attribute can be read directly with dot notation.
Internal Attributes: _name
A single underscore means “internal use” by convention. It does not block access.
class Employee:
def __init__(self, name, salary):
self.name = name
self._salary = salary # protectedThe class definition creates no Employee objects yet.
Continue with that definition:
e = Employee("Mila", 50000)
print(e._salary) # allowed, but discouragedOutput
50000
The lookup is allowed, but ordinary client code should use the public interface.
Name-Mangled Attributes: __name
Two leading underscores, without two trailing underscores, trigger name mangling. This reduces accidental name clashes in subclasses; it is not a security boundary.
class Person:
def __init__(self, name, age):
self.name = name
self.__age = age # private
p = Person("Sam", 30)
# print(p.__age) # ErrorThe commented lookup would raise AttributeError. As written, the example creates p and prints nothing.
Getter and Setter Methods
A getter reads a value. A setter checks an update before storing it.
In the UML class view, - marks the intended private implementation and + marks public operations. Python’s double underscore uses name mangling; it does not enforce access security.
class Person:
def __init__(self, age):
self.__age = age
def get_age(self):
return self.__age
def set_age(self, value):
if value > 0:
self.__age = value
else:
print("Age must be positive")Defines Person; no visible output.
This initializer assumes a valid starting age; the setter checks later updates.
Continue with that Person definition:
p = Person(25)
print("Current age:", p.get_age())
p.set_age(30)
print("Updated age:", p.get_age())
p.set_age(-5) # invalid update
print("Final age:", p.get_age())Output
Current age: 25
Updated age: 30
Age must be positive
Final age: 30
The invalid update is rejected, so the final age remains 30.
Managed Attributes with property
@property makes a getter callable through attribute syntax. @celsius.setter handles assignment to that attribute.
The UML slash marks a value obtained through an attribute interface: /celsius reads the stored __celsius value. The private marker describes the design intent; the Python property controls reads and assignments.
class Temperature:
def __init__(self, celsius):
if celsius < -273.15:
raise ValueError("Temperature below absolute zero")
self.__celsius = celsius
@property
def celsius(self):
return self.__celsius
@celsius.setter
def celsius(self, value):
if value >= -273.15:
self.__celsius = value
else:
print("Temperature below absolute zero is not allowed.")Defines Temperature; no visible output.
The initializer rejects an impossible starting temperature. The setter reports invalid later updates without changing the stored value.
t = Temperature(20)
print(t.celsius)
t.celsius = -300 # validation kicks inOutput
20
Temperature below absolute zero is not allowed.
Reading t.celsius calls the getter; assigning -300 calls the setter and prints its rejection.
Internal Helper Methods
Name mangling also applies to methods:
class Calculator:
def __init__(self):
self.result = 0
def __validate(self, n):
return isinstance(n, (int, float))
def add(self, n):
if self.__validate(n): # internal use only
self.result += nDefines Calculator; no visible output.
add calls an internal helper. isinstance checks whether a value has one of the specified types; this small check also accepts Boolean values because bool is a subclass of int.
The mangled spelling remains accessible:
class Person:
def __init__(self, age):
self.__age = age
p = Person(30)
print(p._Person__age) # technically possible, but discouragedOutput
30
This deliberately demonstrates outside access. Do not treat mangling as secrecy or validation.
Coding Challenge 1: Encapsulation – BankAccount
Complete this class with methods to read the balance, deposit and withdraw. Reject a negative initial balance, non-positive amounts and withdrawals exceeding the balance. Rejected operations must leave the balance unchanged.
Exercise starter: complete before running.
class BankAccount:
def __init__(self, initial_balance=0):
self.__balance = initial_balance # private
# add methods hereStart at 1000, deposit 300, withdraw 200, then attempt to withdraw 2000.
Target output for your solution
Current balance: 1000
After deposit: 1300
After withdrawal: 1100
Cannot withdraw 2000: insufficient funds
Final balance: 1100
These are teaching amounts; production currency code needs explicit rounding rules.
SavingsAccount Follow-up
Complete the SavingsAccount exercise in Inheritance, after learning how one class extends another.
Once an object has clear public operations, other objects can use them to work together. This leads to composition.