Python
decorators
object-oriented programming
getters and setters
@property

Using property versus getters and setters

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In Python, @property is usually the preferred way to add validation or computed access while keeping attribute-style syntax. Traditional getter and setter methods still exist and are sometimes useful, but they are less idiomatic when the thing being exposed is conceptually just an attribute. The choice depends on the meaning of the API, not just on syntax preference.

Use @property when the value behaves like an attribute

A property lets client code write obj.name while the class still controls how that value is computed or validated.

python
1class User:
2    def __init__(self, age):
3        self._age = None
4        self.age = age
5
6    @property
7    def age(self):
8        return self._age
9
10    @age.setter
11    def age(self, value):
12        if value < 0:
13            raise ValueError("age cannot be negative")
14        self._age = value

Client code stays clean:

python
user = User(30)
print(user.age)
user.age = 31

That is more natural in Python than forcing callers to write get_age() and set_age(...) for ordinary state.

Properties help you evolve an API safely

One major advantage of @property is that you can start with a plain public attribute and later convert it into a managed property without changing how callers access it.

That makes refactoring easier. You can add:

  • validation
  • lazy computation
  • deprecation warnings
  • derived values

without breaking every caller that already uses attribute syntax.

Getters and setters are still reasonable for action-like behavior

Traditional methods make more sense when the operation is not really just attribute access.

For example, this is often clearer as a method:

python
class Report:
    def refresh_data(self):
        print("Refreshing from external system")

Calling report.refresh_data() makes it obvious that work is happening. Hiding network calls, file I/O, or expensive computations behind a simple-looking property can make code harder to reason about.

So a useful rule is:

  • use properties for attribute-like access
  • use methods for actions, expensive work, or behavior with side effects

Do not write Java-style getters by habit

Developers coming from Java or C# often write Python like this:

python
1class User:
2    def __init__(self, name):
3        self._name = name
4
5    def get_name(self):
6        return self._name

That is valid Python, but it is usually not the most Pythonic interface. If the value is conceptually just a field, a normal attribute or property is more natural.

Python does not require encapsulation boilerplate for every field. Use it only when you have a real reason.

Choose the interface based on meaning

Ask what the caller should think is happening.

If the caller should think "I am reading a characteristic of this object," a property often fits. If the caller should think "I am asking the object to perform an operation," a method is usually better.

That distinction improves readability and avoids APIs that are technically correct but semantically awkward.

Common Pitfalls

The most common mistake is writing trivial getter and setter methods for every field out of habit from another language.

Another common issue is using a property for something expensive or side-effectful, which makes ordinary-looking attribute access surprisingly costly.

People also create properties without a private backing attribute and accidentally trigger recursion inside the setter or getter.

Finally, if no validation or logic is needed at all, a plain attribute may be better than either a property or explicit getter/setter methods.

Summary

  • In Python, @property is usually the right tool for managed attribute-style access.
  • Use methods when the operation is really an action, not a simple attribute.
  • Properties make it easier to evolve a public API without changing caller syntax.
  • Avoid Java-style getter and setter boilerplate unless the design truly needs it.
  • The best choice depends on the meaning of the interface, not just stylistic preference.

Course illustration
Course illustration

All Rights Reserved.