2.1.6 · D1OOP Fundamentals

Foundations — Encapsulation — hiding internal state, name mangling (`__name`)

1,718 words8 min readBack to topic

This page assumes you have seen none of the words in the parent note. We build each one, in order, so that by the end you can read OOP Fundamentals and the parent note without hitting an undefined term.


1. What is a "class"? What is an "object"?

The picture: think of a stamp and the ink marks it makes. The stamp is the class; each mark on paper is an object.

Figure — Encapsulation — hiding internal state, name mangling (`__name`)

Why the topic needs it: encapsulation is about hiding the internals of an object. You cannot talk about "internal state" until you know that an object is a container that has state. This is the foundation everything sits on. It leads into Abstraction — hiding complexity behind interfaces.


2. What is an "attribute"? (the data inside)

The picture: an object is a small filing cabinet; each attribute is a drawer with a label written on the front (balance, celsius) and a value inside (100, -40).

Example, in words: an Account object has an attribute balance holding the number 100. Reading it is account.balance; that dot means "go into this object and find the drawer named balance."

Why the topic needs it: the "internal state" we want to hide is the set of attributes. Name mangling is entirely about how the label on a drawer gets rewritten, so we must first know what a labelled drawer is.


3. What is a "method"? (the buttons)

The picture: on the front of the filing cabinet sit labelled buttons. Pressing deposit(50) is a controlled action the cabinet performs on its own balance drawer. You never open the drawer yourself — you press the button and the cabinet does it.

account.deposit(50)   # press the button; the object edits its own drawer

Why the topic needs it: encapsulation = data + methods bundled together, with methods as the only proper way in. The whole "interface vs internal" idea (a vending machine's buttons vs its coin box) is the split between methods (buttons) and attributes (drawers).


4. What is self? (the object talking about itself)

The picture: if the cabinet could talk, self is it pointing at itself and saying "my own balance drawer," written self.balance. If you own three cabinets and press deposit on the second one, self means that second cabinet only.

Why the topic needs it: every private attribute in the parent note is written self.__balance. To understand mangling you must first read self.__balance correctly as "the __balance drawer belonging to this object."


5. What is the underscore character _?

We now have to distinguish four underscore patterns, because the parent note's entire mangling rule is about counting underscores at the front and back.

Figure — Encapsulation — hiding internal state, name mangling (`__name`)

Why the topic needs it: these four cases are the parent's whole "HOW Python signals private" table. Counting leading vs trailing underscores decides which rule applies.


6. What is __init__? (the setup button)

The picture: the factory line that stamps a fresh cabinet and immediately writes the starting labels and values into its drawers, before you ever touch it.

class Account:
    def __init__(self, balance):
        self.__balance = 0    # create the drawer, set it to 0

Why the topic needs it: every worked example creates its private state inside __init__. Also — carefully — __init__ has two trailing underscores, so it is a dunder and is NOT mangled. This is exactly the "__init__ gets mangled too" myth the parent debunks.


7. What is "invalid state" and an "invariant"?

The picture: a fuel gauge that must live between Empty and Full. If someone reaches in and jams the needle below Empty, the gauge is now lying — an invalid state. The point of hiding the needle is so only the fuel-pump button can move it, keeping the rule true.

Why the topic needs it: "hiding internal state" only matters because it lets the object guard its invariants. Without this idea, hiding would look pointless.


8. What is "name mangling"? (Python rewriting the label)

Now every prerequisite is in place, so we can state the one real mechanism.

The picture: you write __balance on a drawer, but a machine at the factory secretly relabels it _Account__balance before shipping. Any code inside the blueprint that says __balance is also relabelled the same way, so it still finds the drawer. Code outside that types __balance finds nothing.

Figure — Encapsulation — hiding internal state, name mangling (`__name`)

Why it exists (not security!): if a Base class and a Child class both invent __data, mangling turns them into _Base__data and _Child__data — two different labels — so they never overwrite each other. This is collision avoidance, and it directly supports Inheritance — base and derived classes.


9. Two ways to control access (preview)

  • Name mangling (__name) → avoids clashes; not a lock.
  • Properties (@property) → the Pythonic way to run a check every time an attribute is read or written. Built fully in Properties and Descriptors.

Contrast with other languages: Java has a real private keyword the compiler enforces — see Access Modifiers (Java vs Python). Python instead trusts the programmer ("we're all consenting adults").


Prerequisite map

class = blueprint

object = built instance

attribute = data drawer

method = button

self = this object

underscore naming styles

__init__ setup dunder

invariant = rule kept true

name mangling

Encapsulation


Equipment checklist

Cover the right side and test yourself before reading the parent note.

A class is a
blueprint; an object is a real thing built from it (an instance).
An attribute is
a named piece of data stored on an object, reached with a dot.
A method is
a function living inside a class that operates on the object's data (a "button").
self means
"this particular object" — the specific instance the method was called on.
_name (one underscore) signals
"internal, please don't touch" — convention only, not enforced.
__name (two leading, ≤1 trailing) triggers
name mangling → rewritten to _ClassName__name.
__name__ (dunder) is
special/magic, and is NEVER mangled (two trailing underscores).
__init__ runs
automatically when an object is built, to set starting attributes.
An invariant is
a rule that must always stay true (e.g. balance ≥ 0).
The real purpose of name mangling is
avoiding accidental name clashes across inheritance, NOT security.