Properties: @property, getter and setter

· 5 min read · Updated March 14, 2026 · beginner
python oop properties decorators

The @property decorator lets you define methods that behave like attributes. This gives you control over how values are read, set, and deleted while keeping a clean, attribute-like interface.

Why Use Properties

In Python, you often want to add logic when getting or setting an attribute. Before properties, developers used getter and setter methods in other languages like Java:

class Circle:
    def get_radius(self):
        return self._radius
    
    def set_radius(self, value):
        if value < 0:
            raise ValueError("Radius cannot be negative")
        self._radius = value

With properties, you use a natural attribute syntax while still controlling access:

class Circle:
    @property
    def radius(self):
        return self._radius
    
    @radius.setter
    def radius(self, value):
        if value < 0:
            raise ValueError("Radius cannot be negative")
        self._radius = value

# Now it looks like a simple attribute
circle = Circle()
circle.radius = 5      # Uses the setter
print(circle.radius)    # Uses the getter

Python properties let you have the best of both worlds: the clean attribute syntax and the control of methods when you need it.

The Basics: Defining a Getter

The @property decorator converts a method into a getter. When you access obj.attribute, Python calls the decorated method instead of directly accessing the underlying attribute.

class Person:
    def __init__(self, name):
        self._name = name  # Private attribute (by convention)
    
    @property
    def name(self):
        """Getter for the name attribute."""
        return self._name

p = Person("Alice")
print(p.name)  # Alice

The key convention: private attributes use a leading underscore (_name), and the property provides controlled access via the public name (name). This signals to other developers that they should use the property, not the private attribute directly.

Adding a Setter

Use the @attribute.setter decorator to define what happens when someone assigns to the attribute:

class Person:
    def __init__(self, name):
        self._name = name
    
    @property
    def name(self):
        return self._name
    
    @name.setter
    def name(self, value):
        if not isinstance(value, str):
            raise TypeError("Name must be a string")
        if not value:
            raise ValueError("Name cannot be empty")
        self._name = value

p = Person("Alice")
p.name = "Bob"      # Calls the setter
print(p.name)        # Bob

p.name = ""         # Raises ValueError

The setter lets you validate input, transform values, or trigger other actions when an attribute changes. This is where properties shine, keeping validation logic close to the data it protects.

Adding a Deleter

Similarly, @attribute.deleter defines what happens when someone deletes the attribute with del:

class Person:
    def __init__(self, name):
        self._name = name
    
    @property
    def name(self):
        return self._name
    
    @name.setter
    def name(self, value):
        self._name = value
    
    @name.deleter
    def name(self):
        print(f"Deleting {self._name}")
        del self._name

p = Person("Alice")
del p.name  # Prints "Deleting Alice" and removes _name

Deleters are less common but useful for cleanup tasks, logging when attributes are removed, or enforcing deletion policies.

Computed Properties

Properties can compute values on the fly based on other attributes:

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    @property
    def area(self):
        return self.width * self.height
    
    @property
    def perimeter(self):
        return 2 * (self.width + self.height)

r = Rectangle(4, 5)
print(r.area)       # 20
print(r.perimeter)  # 18

These are read-only properties since no setter is defined. Attempting to assign r.area = 100 raises an AttributeError.

Computed properties are great for derived data. The values are always up-to-date because they are recalculated each time you access them.

Read-Only Properties

To make a property read-only, simply omit the setter:

class Circle:
    def __init__(self, radius):
        self.radius = radius  # Goes through setter
    
    @property
    def diameter(self):
        return self.radius * 2

c = Circle(5)
print(c.diameter)  # 10
c.diameter = 20    # AttributeError: property diameter has no setter

This prevents external code from modifying computed or derived values. The user cannot accidentally overwrite a value that should be calculated automatically.

Property as Decorator Factory

You can also use property() directly for more control:

class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius
    
    def _get_fahrenheit(self):
        return self._celsius * 9/5 + 32
    
    def _set_fahrenheit(self, value):
        self._celsius = (value - 32) * 5/9
    
    fahrenheit = property(_get_fahrenheit, _set_fahrenheit)

This approach is useful when you want more control over the property creation or need to assign it dynamically. It is equivalent to using decorators but sometimes clearer for complex cases.

When to Use Properties

Properties are ideal when you need to validate values, ensure data meets certain criteria before storing, compute attributes that derive values from other attributes, implement lazy evaluation for expensive values only when needed, hide internal implementation details through encapsulation, and maintain API stability by starting with simple attributes then adding logic later without changing the API.

A common pattern is to start with simple public attributes. When you need validation or computed values later, convert them to properties. External code does not need to change since obj.x = value works the same either way.

This differs from languages like Java, where you must explicitly define getters and setters from the start. Python properties let you evolve your API without breaking existing code.

Property vs Getter Setter Methods

Properties give you the best of both worlds: the clean attribute syntax and the control of methods. This is a key part of Python is philosophy. You should not need to guess whether something is a simple attribute or a method call.

The rule of thumb is to use attributes by default. Convert to properties when you need behavior. Your users will thank you for the clean API.

See Also