skip to content

How do you override just the setter of a @property inherited from a base class?

level: middleimportance: should knowfreq 40%

answer

  1. One name, one object, three slots
  2. The bare decorator builds a new one
  3. Reach for the parent's object explicitly
  4. super() reads but cannot assign
  5. Call the parent's fset directly

basics

~20 s

Reach the base's property object explicitly and decorate with it: @Base.title.setter over a new method named title. That returns a new property keeping the inherited getter and deleter. Redefining the property with a bare @property in the subclass silently drops both.

solid answer

~40 s

A property is a single class attribute holding up to three functions, so a subclass that writes `@property def title` again creates a **brand-new** property with only a getter — the inherited setter and deleter are gone, and assignment then raises `AttributeError`. To replace one accessor, decorate with the base's property object: `@Base.title.setter` returns a copy carrying the base's `fget` plus your `fset`. You must qualify it as `Base.title`, because inside the subclass body the bare name `title` is not bound yet. If you need to extend both sides, redefine the property fully: `super().title` works in the getter, but `super().title = value` does **not** — `super()` proxies lookups, not assignment — so call the parent function directly with `Base.title.fset(self, value)`.

code

python · 33 lines
python
class Base:
    @property
    def title(self):
        return self._title

    @title.setter
    def title(self, value):
        self._title = value.strip()


class Loud(Base):
    @Base.title.setter          # keeps Base's getter, replaces the setter
    def title(self, value):
        Base.title.fset(self, value.upper())


class Broken(Base):
    @property                   # brand-new property: the setter is GONE
    def title(self):
        return super().title + "!"


loud = Loud()
loud.title = "  nightly  "
print(loud.title)               # NIGHTLY

broken = Broken()
broken._title = "nightly"
print(broken.title)             # nightly!
try:
    broken.title = "boom"
except AttributeError as exc:
    print(exc)                  # property 'title' of 'Broken' object has no setter

go deeper

for a junior

Know the trap: writing @property again in a subclass replaces the whole thing, so the inherited setter disappears and assignment starts raising AttributeError. Recognise that error message as the symptom.

for a middle

Explain that Base.x is the property object and that .setter returns a copy keeping the other accessors, why the Base. qualification is required in a class body, and why super().x reads fine but cannot be assigned to.

for a senior

Argue for the structural fix: a protected hook the subclass overrides beats replacing accessors, and a subclass that quietly removes a setter is a substitutability break that fails at runtime rather than at type-check time. Bring that up in review.

for a principal

Own the hierarchy question: repeated accessor surgery across a deep class tree is a smell pointing at composition or an explicit strategy object. Decide whether base classes in your codebase publish hooks, and make that a documented convention rather than case-by-case.

### Why the naive override loses half the property Inheritance in Python works on names, and a property is *one* name bound to *one* object that happens to hold up to three functions. When a subclass writes: ```python class Child(Base): @property def title(self): return super().title + "!" ``` the decorator constructs a fresh `property` from that single function and binds it to `Child.title`. That new object has `fset = None` and `fdel = None`. Attribute lookup finds `Child.title` first in the MRO and never reaches `Base.title`, so the base's perfectly good setter is unreachable: `child.title = "x"` now raises `AttributeError: property 'title' of 'Child' object has no setter`. Nothing warns you. The class defines, on the face of it, exactly what it meant to change, and it silently deleted behaviour it never mentioned. This is the single most common property bug in inheritance-heavy code, and it is what the interview question is really about. ### The targeted fix: decorate with the base's property `Base.title` evaluates to the `property` object itself (class access returns `self` from `__get__`), and `property.setter`, `property.getter` and `property.deleter` each return a **new** property copying the accessors they are not replacing. So: ```python class Loud(Base): @Base.title.setter def title(self, value): Base.title.fset(self, value.upper()) ``` `Loud.title` now has the base's getter, the base's deleter and your setter. The qualification `Base.title` is mandatory: inside the class body of `Loud`, the name `title` is not bound until this statement completes, so a bare `@title.setter` raises `NameError`. Symmetrically, `@Base.title.getter` swaps only the getter and keeps the inherited setter — the exact inverse of the bug above. ### Extending rather than replacing If the subclass wants to *wrap* the base logic instead of discarding it, it has to call the parent's function. For the getter, `super()` works normally: ```python @property def title(self): return super().title.upper() ``` because `super()` performs a normal attribute lookup in the MRO after `Child`, finds `Base.title`, and invokes its `__get__` with the instance. For the **setter**, `super()` does not help: `super().title = value` fails with `AttributeError: 'super' object has no attribute 'title' and no __dict__ for setting new attributes`. The `super` proxy implements delegated *lookup* only — assignment falls through to `object.__setattr__` on the proxy itself, which has nowhere to put anything. The workable forms are: - `Base.title.fset(self, value)` — call the parent's setter function directly. Explicit, obvious, and the usual choice. - `super(Child, type(self)).title.__set__(self, value)` — invoke the descriptor protocol by hand. Correct, cooperative with multiple inheritance, and unreadable enough that most codebases avoid it. ### Design notes an interviewer listens for **Prefer overriding the hook, not the property.** If the base's getter is written as `return self._format(self._title)`, a subclass overrides `_format` and never touches the property at all. Template-method beats accessor surgery: the property stays one object in one place, and no accessor can be dropped by accident. **A subclass that turns a read-write attribute read-only is a substitutability problem.** Replacing an inherited read-write property with a getter-only one means code written against the base breaks on the subclass — a runtime failure at the assignment, not a type error. If a subtype genuinely cannot support the setter, that is a signal the hierarchy is wrong, not a licence to raise. **Multiple inheritance offers no merge.** A property is looked up by MRO like any other class attribute, so a mixin defining the same name simply shadows, or is shadowed by, the other one depending on class ordering — there is no accessor-by-accessor combination. If two bases each contribute part of the same attribute, one of them silently wins the whole name, and which one depends on the MRO rather than on anything visible at the point of use. When a name genuinely needs contributions from several places, give the property one owner and have it call hooks the mixins override, so the composition is explicit and the property object stays singular. ### The checklist 1. Never redefine an inherited property with a bare `@property` unless you intend to drop the other accessors. 2. To replace one accessor, decorate with the base's property object: `@Base.x.setter` / `.getter` / `.deleter`. 3. To extend the getter, `super().x` is fine. To extend the setter, call `Base.x.fset(self, value)`; `super().x = value` never works. 4. Better still, give the base a protected hook method and override that instead.

  • Why can't the subclass simply write `@title.setter` without naming the base class?
    Because the class body is an ordinary namespace being built top-down, and `title` is not bound in it yet — the bare name raises `NameError` rather than resolving to the inherited attribute. Class bodies do not consult base classes for name lookup; only attribute access on the finished class does. Qualifying it as `Base.title` fetches the inherited property object explicitly.
  • Why does `super().title = value` fail inside a subclass setter?
    The `super` object implements delegated attribute *lookup* through `__getattr__`/the descriptor protocol; it has no `__setattr__` delegation, so the assignment tries to set an attribute on the proxy itself and fails with `'super' object has no attribute 'title' and no __dict__ for setting new attributes`. Call the inherited function directly instead: `Base.title.fset(self, value)`.
  • A subclass needs the parent's setter to keep running plus one extra check. What's the cleanest structure?
    Usually not accessor surgery at all: have the base setter delegate its real work to a protected hook (`self._validate(value)`) and let the subclass override that. It keeps the property as one object in one place and removes any chance of dropping an accessor. If the hook does not exist, `@Base.x.setter` with a `Base.x.fset(self, value)` call at the end is the explicit second choice.

saying these in an interview costs you the question

  • Redefines the property and expects the setter to be inherited
  • Thinks a second @property in the subclass extends the parent's
  • Uses super().attr = value to reach the parent setter
  • Copies the parent's getter body instead of delegating
  • Says properties cannot be overridden at all
  • Makes an inherited read-write attribute read-only without noticing callers break

context