skip to content

What does Python's @property decorator do, and how do you add a setter to it?

level: juniorimportance: must knowfreq 78%

answer

  1. A method that reads like an attribute
  2. One name, up to three functions
  3. The decorator returns a new object
  4. fget, fset, fdel and a docstring
  5. Uniform access, promoted later

basics

~20 s

The @property decorator turns a method into a read-only attribute, so callers write obj.title rather than obj.title(). Decorating a second method of the same name with @title.setter makes assignment work and gives you one place to validate the value.

solid answer

~40 s

`@property` binds the class attribute `title` to a `property` object holding your function in its `fget` slot. Because `property` implements the descriptor protocol, reading `obj.title` calls that function and returns its result — the call moved behind attribute syntax. `@title.setter` is a method on the property object that returns a **new** property carrying the same getter plus your `fset`; the second method must reuse the name `title` so the new object rebinds it. `@title.deleter` does the same for `del obj.title`, and the whole thing is sugar over `title = property(fget, fset, fdel, doc)`. The point is the uniform access principle: ship a plain public attribute, and promote it to a property later — for validation, or a computed value — without touching a single caller.

code

python · 31 lines
python
class Report:
    def __init__(self, rows):
        self._rows = rows
        self._title = "untitled"

    @property
    def row_count(self):
        """Rows collected so far."""
        return len(self._rows)

    @property
    def title(self):
        return self._title

    @title.setter
    def title(self, value):
        if not value.strip():
            raise ValueError("title must not be blank")
        self._title = value.strip()

    @title.deleter
    def title(self):
        self._title = "untitled"


r = Report(["a", "b"])
r.title = "  Nightly totals  "
print(r.title, r.row_count)   # Nightly totals 2
del r.title
print(r.title)                # untitled
print(type(Report.title).__name__)   # property

go deeper

for a junior

Be ready to write the three-accessor pattern from memory and to say what callers type: obj.title, no parentheses. Know that the setter method must repeat the getter's name and that the stored value lives in a separate private attribute.

for a middle

Explain the mechanics: the decorator binds a property object at class level, .setter returns a new property rather than mutating one, and the read goes through the descriptor protocol. Be able to write the non-decorator property(...) equivalent.

for a senior

Show judgment about what belongs behind a property at all — cheap, repeatable, side-effect-free reads — and be able to argue against reflexive getter/setter pairs in review while pointing at the uniform access principle as the reason Python does not need them.

for a principal

Own the API-evolution angle: a public attribute that can be promoted to a property later is why Python codebases can start simple, and the tradeoff is that attribute access is no longer guaranteed cheap. Decide where your codebase draws that line and write it down.

### `property` is a type, not syntax `property` is a built-in type. Writing `@property` above a method named `title` binds the *class-level* name `title` to a `property` object whose `fget` slot holds your function. From then on, reading `obj.title` on an instance does not find a function in the class — it finds a `property`, and since `property` implements the descriptor protocol, `object.__getattribute__` invokes the property's `__get__`, which calls your function with the instance and hands back the result. The call still happens; it has simply moved behind attribute syntax. Callers write `obj.title`, never `obj.title()`. ### The three accessors A `property` object carries three optional functions — `fget`, `fset`, `fdel` — plus a docstring, and the decorator form fills them one at a time. ```python class Report: @property def title(self): # fget return self._title @title.setter def title(self, value): # fset self._title = value.strip() @title.deleter def title(self): # fdel self._title = "untitled" ``` `@title.setter` is the part that surprises people. `title.setter` is a method **on the property object** that returns a *new* property carrying the original `fget` and the new `fset`; it does not mutate the original. That is why the second method has to reuse the name `title` — the returned object is rebound to that class-level name, replacing the getter-only one. Give the second method a different name and you end up with two separate attributes: a read-only `title` and a useless write-only one. `@title.deleter` behaves identically for `del obj.title`, and `@title.getter` replaces the getter while keeping the other two accessors. Without decorators it is a plain call: `title = property(_get_title, _set_title, _del_title, "doc")`. The decorator form is sugar over exactly that; there is no other machinery. ### The three shapes that cover real code **Read-only / computed.** Only a getter. `row_count` derives from `self._rows`; there is nothing to store and nothing to set. Assignment raises `AttributeError` instead of quietly creating an instance attribute, which is what makes it genuinely read-only rather than merely undocumented. **Validated.** Getter plus setter. The setter is the single funnel every assignment passes through, so invariants — non-blank, in range, normalized, coerced — are enforced in one place, and the value lives in a private backing attribute (`self._title`) that the getter reads. Note the naming discipline: the property owns the public name, the backing attribute takes the underscore. Reusing the same name inside the setter (`self.title = value`) is infinite recursion. **Read-only outside, writable inside.** A public getter and no setter, while the class's own methods assign to `self._title` directly. The property guards the public name, not the object's internals. ### Why Python does not ask for getters up front In languages where turning a public field into an accessor pair is a source-compatibility break, the defensive habit is to write `getTitle()`/`setTitle()` for everything on day one. Python does not need that. Start with a plain public attribute `self.title = title`; if you later need validation, normalization, logging or a derived value, promote the name to a property and **every existing caller keeps working unchanged**. That is the uniform access principle, and it is why asking in review for a getter/setter pair around a field that only stores a value is asking for noise. Add the property when there is a reason, not in advance. ### Details worth carrying into the interview - Accessing the property on the **class** (`Report.title`) returns the `property` object itself, because `__get__` receives `obj=None` and returns `self`. That is exactly what makes `@Base.title.setter` reachable from a subclass body. - The docstring comes from the getter, so `help()` and editors still show it. - A property is one object per class, not per instance, but each read is a Python-level function call and is measurably slower than reading a plain attribute. That only matters in a hot loop. - Properties and `__slots__` coexist only if the names differ: putting `"title"` in `__slots__` *and* defining a property named `title` fails at class creation with `ValueError: 'title' in __slots__ conflicts with class variable`. Slot the backing name `_title` instead. - A property is a promise that reading is cheap and repeatable. Keep I/O, mutation and anything expensive out of a getter — that is what a method is for.

  • What do you get back when you access the property on the class instead of on an instance?
    The `property` object itself. `__get__` is called with `obj=None` and returns `self` rather than invoking the getter, so `Report.title` is a `property` and `Report.title.fget` is the underlying function. That is the hook a subclass uses when it writes `@Base.title.setter` to replace one accessor.
  • How would you write the same property without decorator syntax?
    As a plain call in the class body: `title = property(_get_title, _set_title, _del_title, "the report title")`, with the three functions defined above it under private names. The decorator form is sugar over that constructor — each of `@title.setter`, `@title.getter` and `@title.deleter` just returns a new `property` copying the accessors it is not replacing.
  • Why does assigning to `self.title` inside the setter blow up?
    Because `title` is the property, so the assignment re-enters the same setter and recurses until `RecursionError`. The setter must write to a different name — a private backing attribute such as `self._title`. The property owns the public name; the stored state lives under another one.
  • Should every attribute get a property for safety?
    No. That is the accessor-pair habit imported from languages where promoting a field is a breaking change. In Python a plain attribute can become a property later without changing callers, so a getter/setter that only reads and writes a value adds a call and a maintenance burden for nothing. Add one when there is a real invariant, a computation, or something to hide.

A property is a receptionist at a desk labelled with the attribute's name: visitors just say the name, and whether that fetches a stored file, computes a total or refuses a delivery is the receptionist's business, not theirs.

saying these in an interview costs you the question

  • Says @property makes an attribute private
  • Calls it with parentheses: obj.title()
  • Gives the setter method a different name from the getter
  • Assigns to self.title inside the setter, causing recursion
  • Claims every field should get getters and setters up front
  • Thinks @property stores or caches the computed result

context