Python 101Free
CLASSES AND OOP

Properties and Encapsulation

Controlling how attributes are read and written.

SECTION 01

@property as managed attributes

@property turns a method into a read-only attribute. Callers write obj.temperature instead of obj.temperature(). The class controls what gets returned, but the public interface looks like a plain attribute.

A matching @<name>.setter lets the class run code when the attribute is assigned. This is where you put validation: rejecting impossible values, converting input units, normalizing data. The caller still writes obj.temperature = -300, and the setter decides whether to accept it.

The pattern lets you start with a plain attribute and upgrade to a managed one later, without breaking any callers. That is the main reason Python avoids the explicit getter/setter culture you see in some other languages.

python
class Celsius:
    def __init__(self, t):
        self.temperature = t   # goes through the setter

    @property
    def temperature(self):
        return self._t

    @temperature.setter
    def temperature(self, value):
        if value < -273.15:
            raise ValueError("below absolute zero")
        self._t = value
SECTION 02

Name mangling and conventions

Python has no real private attributes. The conventions are social. A leading underscore (_name) signals "this is internal, do not touch", and other Python programmers will respect it. Nothing in the language stops anyone from reading or writing it.

A double underscore (__name) triggers name mangling. Python rewrites the attribute to _ClassName__name behind the scenes. The original name still works inside the class body, but outside it the mangled form is what is actually stored.

Mangling is not for privacy. It exists to prevent accidental collisions when subclasses define their own attributes with the same name. Use a single underscore for "internal" and reserve double underscores for the rare cases where mangling actually matters.

python
class Account:
    def __init__(self):
        self.name = "public"
        self._token = "internal"      # convention only
        self.__secret = "mangled"     # rewritten to _Account__secret

a = Account()
a._Account__secret   # the mangled name on the instance
← PREVIOUS
Dunder Methods