Skip to main content

Python @property: Getters, Setters, and Computed Attributes

Intermediate20 min6 exercises90 XP
0/6 exercises

Imagine a thermostat on your wall. You set the temperature to 72°F and the system heats your house. But what if someone sets it to 5000°F? The house wouldn't literally reach that temperature, but your heating system would run forever trying. A smart thermostat would reject that value immediately.

Python's @property decorator gives your classes that same intelligence. It lets you control what happens when someone reads, changes, or deletes an attribute — without changing how the attribute looks from the outside.

By the end of this tutorial, you'll know how to validate data going into your objects, create computed attributes that update automatically, and write classes that are both easy to use and impossible to misuse.

Why Not Just Use Regular Attributes?

In Python, you can set any attribute to any value. There are no guardrails by default. That's fine for simple scripts, but it becomes a problem when bad data can break your program.

No validation on regular attributes
Loading editor...

One approach is to use getter and setter methods like get_temperature() and set_temperature(). This works, but it's clunky. Every user of your class has to remember to call methods instead of using simple attribute syntax.

Clunky getter/setter methods
class Thermostat:
    def __init__(self, temp):
        self._temp = temp

    def get_temperature(self):
        return self._temp

    def set_temperature(self, value):
        if value < -459 or value > 1000:
            raise ValueError('Invalid temp')
        self._temp = value

# Awkward to use
t = Thermostat(72)
print(t.get_temperature())
t.set_temperature(80)
Clean @property syntax
class Thermostat:
    def __init__(self, temp):
        self.temperature = temp

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

    @temperature.setter
    def temperature(self, value):
        if value < -459 or value > 1000:
            raise ValueError('Invalid temp')
        self._temp = value

# Natural to use
t = Thermostat(72)
print(t.temperature)
t.temperature = 80

With @property, your class users write t.temperature = 80 just like a normal attribute. But behind the scenes, your validation code runs automatically. Best of both worlds.

How Does the @property Decorator Work?

The @property decorator turns a method into a "getter" — code that runs whenever you read an attribute. The method name becomes the attribute name.

A basic @property getter
Loading editor...

Notice the naming convention. The actual data lives in self._radius (with an underscore). The property is named radius (without the underscore). The underscore signals "this is internal — don't touch it directly."

Without a setter, the property is read-only. If you try to assign to it, Python raises an AttributeError.

Read-only property raises an error on assignment
Loading editor...

How Do You Add a Setter to a Property?

To allow assignment, define a setter using @property_name.setter. The setter receives the new value and decides what to do with it — store it, validate it, transform it, or reject it.

A property with validation in the setter
Loading editor...

Notice that __init__ uses self.temperature = temp (without underscore). This means the setter runs during initialization too. Your object is validated from the very first moment it exists.

What Are Computed and Read-Only Properties?

Some attributes aren't stored anywhere. They're calculated on the fly from other data. Think of a rectangle: you store width and height, but the area is just width * height. There's no reason to store the area separately.

Computed properties update automatically
Loading editor...

Computed properties always return the latest value because they recalculate every time you access them. You never have stale data.

Here's a practical example: a BankAccount that derives its status from the balance.

Computed status based on balance
Loading editor...

When Should You Use @property?

Use @property when you need to add behavior to attribute access. Here are the most common scenarios:

Validation: Reject bad data before it enters your object. Great for ages, prices, email addresses, or any value with constraints.

Computed values: Derive one attribute from others (area from width/height, full name from first/last). No need to store redundant data.

Read-only attributes: Expose data that should never be changed from outside the class, like an object's creation timestamp.

Backward compatibility: If you started with a plain attribute and later need to add validation, @property lets you do it without changing the class's public interface.


Practice Exercises

Create a Read-Only Name Property
Write Code

Create a class Person with an __init__ that takes a name parameter and stores it in self._name. Add a read-only @property called name that returns self._name. Then create a Person with the name 'Alice' and print their name.

Loading editor...
Add Age Validation with a Setter
Write Code

Create a class Student with a name (regular attribute) and an age property. The age setter should raise a ValueError with the message 'Age must be between 0 and 150' if the value is outside that range. Use the setter inside __init__. Then create a Student('Bob', 20), print their age, update it to 21, and print again.

Loading editor...
Fix the Infinite Recursion
Fix the Bug

This class causes infinite recursion when you create an instance. Find and fix the bug so that creating Product('Widget', 9.99) prints the price correctly.

Loading editor...
Build a Computed Full Name
Write Code

Create a class Person with first_name and last_name as regular attributes. Add a read-only computed property full_name that returns them joined with a space. Create Person('John', 'Doe'), print full_name, then change last_name to 'Smith' and print full_name again.

Loading editor...
Predict the Temperature Output
Predict Output

Read the code carefully and predict exactly what gets printed. Think about which setter calls succeed and which raise errors.

Loading editor...
Refactor to Use @property
Refactor

Refactor this class to use @property instead of explicit getter/setter methods. The get_speed and set_speed methods should be replaced with a speed property. Keep the validation logic. Then create a Car(60), print speed, set it to 100, and print again.

Loading editor...