30 · Encapsulation: properties and protected names
@property turns a method into an attribute — perfect for computed values and validated writes. Python trusts you with convention over enforcement.
One idea, one try. At your own pace.
LEARN BY DOINGPut your intuition to work3 steps
30 · Encapsulation: properties and protected names
Use the lesson’s examples and explanation to check your idea.
Personal reflection. Nothing is sent to an AI service.Python has **no enforced access control** — `_single_underscore` is a convention meaning "internal, please don't touch", and `__double_underscore` triggers name-mangling to avoid subclass collisions. `@property` lets you expose a method as a read-only (or validated-write) attribute — callers use `obj.value` syntax while the class controls what actually happens.
Without this:
Without `@property`, computed values (Fahrenheit from Celsius) require an explicit method call — `temp.to_fahrenheit()`. With `@property`, the API reads `temp.fahrenheit` — more natural and compatible with code that expects plain attributes.
Java developers often reach for private fields and getter/setter methods as a reflex. Python's philosophy is different: start with a plain public attribute. If, later, you need validation or a computed value, you can replace the attribute with a @property without changing the caller's code — because obj.x and obj.x = v look exactly the same whether x is a plain attribute or a property.
@property defines a getter that is called when you read obj.attr. @attr.setter defines a setter called when you write obj.attr = value. @attr.deleter is rare — called when you del obj.attr.
Name conventions:
_name— single leading underscore. Signals "internal, treat as private by convention". Nothing prevents access from outside — Python simply trusts you.__name— double leading underscore. Python rewrites this to_ClassName__nameat parse time (name mangling). This prevents a subclass accidentally shadowing an attribute with the same name — it is not truly private.
The Pythonic rule: don't write Java in Python. Most attributes should just be public. Add @property only when you genuinely need validation, a computed value, or lazy initialisation.
Python (in browser)
From the caller's perspective, `t.fahrenheit` looks exactly like a plain attribute — no `()` needed, assignment syntax works. The class transparently handles the conversion. If you later add the setter to a class that previously had only a getter, existing callers need no code changes.
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
Python (in browser)
Python does not prevent `t._celsius` access — the single underscore is a social contract, not a lock. The class author signals "this is internal plumbing" and trusts callers to respect it. In practice, 95 % of the time you should read and write through the public interface (`t.fahrenheit`, `t.celsius`).
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
Name mangling's primary purpose is **collision avoidance in subclasses**, not true privacy. `_Foo__secret` is still accessible with full knowledge of the mangling scheme. Use `__double_underscore` for attributes that must not be accidentally shadowed in subclasses — not as a security measure.
In plain English, what does `@property` do?
- Python has no enforced access control. `_protected` is a convention; `__mangled` prevents subclass name collisions via name mangling (not true privacy).
- `@property` exposes a getter as `obj.attr`. `@attr.setter` adds a validated write. Callers see plain attribute syntax either way — the API surface is unchanged.
- Start with plain public attributes. Only add `@property` when you need validation, a computed value, or lazy loading.
Sklearn estimators expose hyperparameters as plain public attributes (`model.C`, `model.n_estimators`) but use `@property` for computed stats set after `.fit()` — `n_features_in_`, `classes_`, `feature_importances_`. PyTorch `nn.Module` exposes `.training` as a property that controls batch-norm and dropout behaviour; calling `.train()` or `.eval()` flips it. Pydantic models use descriptor magic similar to `@property` for field validation — the same "attribute access syntax hides computation" principle.
If you remove it: Without `@property`, every computed attribute would need to be a method call — `model.get_feature_importances()` instead of `model.feature_importances_`. The sklearn convention of attribute access for fitted parameters would be impossible to implement cleanly.