Python Programming from First Principles: Learn to Code, Think Computationally, and Solve Real-World Problems
PDFEPUB
BOOK 000 · CHAPTER 7 · Encapsulation

Encapsulation

On this pagePublic AttributesInternal Attributes: _nameName-Mangled Attributes: __nameGetter and Setter MethodsManaged Attributes with propertyInternal Helper MethodsCoding Challenge 1: Encapsulation – BankAccountSavingsAccount Follow-up

Encapsulation 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.

Request an update through a public operationValid under the class rules?yesnoUpdate stored stateReject the update

Public Attributes

class User:
    def __init__(self, name):
        self.name = name       # public attribute

u = User("Anna")
print(u.name)
Listing 296 — Public Attributes

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   # protected
Listing 297 — Internal Attributes: _name

The class definition creates no Employee objects yet.

Continue with that definition:

e = Employee("Mila", 50000)
print(e._salary)   # allowed, but discouraged
Listing 298 — Internal Attributes: _name

Output

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)   # Error
Listing 299 — Name-Mangled Attributes: __name

The 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.

Person-__age+get_age()+set_age(value)
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")
Listing 300 — Getter and Setter Methods

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())
Listing 301 — Getter and Setter Methods

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.

Temperature-__celsius+/celsiuscelsius reads the stored value;assignment uses the setter.
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.")
Listing 302 — Managed Attributes with property

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 in
Listing 303 — Managed Attributes with property

Output

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 += n
Listing 304 — Internal Helper Methods

Defines 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 discouraged
Listing 305 — Internal Helper Methods

Output

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 here
Listing 306 — Coding Challenge 1: Encapsulation – BankAccount

Start 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.