Properties & Descriptors

A property is a method dressed up as an attribute: you write obj.area with no parentheses, but a function runs behind the scenes to compute it, validate it, or guard it — giving you clean attribute syntax with full control.

Learn Properties & Descriptors in our free Python course — an interactive lesson with runnable examples, a practice exercise and a quick reference.

Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

Properties let you add computed values and validation without breaking the simple obj.x interface your callers expect. Descriptors take the idea one level deeper, letting you reuse that managed-attribute logic across many fields and classes.

Decorate a method with @property and you access it like an attribute — no parentheses. The value is computed fresh each time from the data you already store:

Same logic, friendlier interface. diameter always reflects the current radius because it's recomputed on every access.

Add a @<name>.setter method and you can run code — including validation — every time someone assigns to the attribute. The convention is to store the real value in a "private" field like _celsius :

Notice the getter returns self._celsius while the setter writes self._celsius . The public name celsius is the property; the underscore name holds the actual data.

A property can have all three hooks. The @<name>.deleter runs when someone uses del obj.attr — handy for cleanup or to forbid deletion entirely:

A property validates one attribute on one class. When you want to reuse the same validation across many attributes, write a descriptor : a class with __get__ and __set__ methods, stored as a class attribute. @property is itself a descriptor under the hood.

A plain @property recomputes on every access. For an expensive calculation that won't change, functools.cached_property runs the body once, stores the result in the instance, and returns the cached value thereafter:

Complete the property so celsius stays read-only and fahrenheit is computed. Replace each ___ , then run it.

✅ Store the real value under a different name and return that:

✅ A property is accessed without parentheses: c.area .

✅ Add a @diameter.setter if assignment should be allowed — otherwise it's read-only by design.

Build a Rectangle whose width and height reject non-positive values via setters, and whose area and perimeter are computed properties.

Lesson complete — your attributes are smart now!

You can expose computed values with @property , validate assignments with a setter, hook deletion with a deleter, reuse logic across fields with the descriptor protocol, and cache expensive results with cached_property — all while keeping the clean obj.x interface.

🚀 Up next: __slots__ & Memory Optimization — make objects smaller and faster.

Practice quiz

How do you access a method decorated with @property?

  • With parentheses, like obj.area()

A @property is accessed like an attribute (obj.area) — no parentheses — even though a function runs behind the scenes.

A @property like 'diameter' returning self.radius * 2 is recomputed when?

  • Every time you access it
  • Once, at object creation
  • Only when you call a refresh method
  • Never — it caches the first value

Answer: Every time you access it. A plain @property recomputes its value fresh on every access, so it always reflects the current source data.

What does adding a @<name>.setter let you do?

  • Make the property faster
  • Delete the attribute
  • Turn the property into a classmethod
  • Run code (e.g. validation) every time someone assigns to the attribute

Answer: Run code (e.g. validation) every time someone assigns to the attribute. A setter runs on assignment, letting you validate or transform the value before storing it.

Why does the getter return self._celsius instead of self.celsius?

  • Underscore names are faster
  • Returning self.celsius would call the property again, causing infinite recursion
  • self.celsius does not exist
  • It is just a style preference

Answer: Returning self.celsius would call the property again, causing infinite recursion. Returning self.celsius inside the getter re-invokes the property endlessly — a RecursionError. Store the real value under a different name like _celsius.

In a property, which hook must be defined first?

  • The getter (bare @property)
  • The setter
  • The deleter
  • Order does not matter

Answer: The getter (bare @property). The getter with bare @property must come first; the setter and deleter attach to it by the same name.

A descriptor is a class that defines which methods and lives where?

  • __init__, on the instance
  • __call__, in a module
  • __get__/__set__/__delete__, stored as a class attribute
  • __str__, on a metaclass

Answer: __get__/__set__/__delete__, stored as a class attribute. A descriptor defines __get__, __set__, or __delete__ and must be stored as a class attribute, not on the instance.

What does __set_name__ tell a descriptor?

  • The instance's id
  • Which attribute name it was assigned to
  • The owner module
  • Nothing — it is deprecated

Answer: Which attribute name it was assigned to. __set_name__(self, owner, name) tells the descriptor which attribute name it was bound to, so one descriptor class can guard many fields.

How does functools.cached_property differ from @property?

  • It recomputes more often
  • It cannot be read more than once
  • It only works on functions
  • It computes once on first access, then returns the stored value

Answer: It computes once on first access, then returns the stored value. cached_property computes the value once, stores it in the instance __dict__, and returns the cached value on every later access.

Which feature does cached_property NOT work with?

  • Classes with __init__
  • Classes using __slots__
  • Subclasses
  • Classes with other properties

Answer: Classes using __slots__. cached_property needs a writable __dict__ to store its result, so it does not work on classes that use __slots__.

What happens if you assign to a read-only property with no setter?

  • It silently succeeds
  • It creates a new instance attribute
  • It raises AttributeError
  • It calls the getter

Answer: It raises AttributeError. Assigning to a property that has no setter raises AttributeError — it is read-only by design until you add a setter.