Understanding the Python @property Decorator: (Getters, Setters, and why you should be using them)

Dean DidionDean Didion
3 min read

Python’s all about clean, readable code, and it’s packed with features that make life easier for developers. One of those features is the @property decorator. It lets you control how attributes are accessed and modified without cluttering your code. In this article, we’ll break down how @property works, why it’s useful, and how you can use getters and setters to write more maintainable, Pythonic code.

What is the @property Decorator?

The @property decorator allows you to define methods in a class that can be accessed like attributes. This method keeps your data encapsulated while letting you interact with it as if it were a regular 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 value:
            raise ValueError("Name cannot be empty")
        self._name = value

    @name.deleter
    def name(self):
        print("Deleting name...")
        del self._name

# Usage
person = Person("Alice")
print(person.name)  # Getter

person.name = "Bob"  # Setter
print(person.name)

# Deleter
# del person.name

Why Use @property?

Encapsulation

Encapsulation means hiding an object’s data and controlling access through methods. With the @property decorator, you get the same control without sacrificing the simplicity of accessing attributes directly.

Cleaner Syntax

Instead of relying on verbose getter and setter methods like get_name() and set_name(), you can work with attributes directly. This keeps the code clean, readable, and in line with Python’s idiomatic style.

Backward Compatibility

If you originally defined an attribute directly (e.g., self.name = name) and later decide you need validation or additional logic, you can easily refactor it into a property without altering how the attribute is accessed.

Validation and Control

With the setter, you can introduce validation logic whenever an attribute is modified, helping to maintain data integrity effortlessly.

Advantages of Using @property

  • Improved Readability: Accessing person.name feels more natural and concise compared to person.get_name().

  • Encapsulation Made Simple: Safeguard the internal state while keeping the interface clean and straightforward.

  • Seamless Refactoring: Transition from direct attribute access to managed properties without breaking existing code.

  • Custom Logic: Effortlessly add validation, transformation, or computed properties when needed.

Example usage

class BankAccount:
    def __init__(self, balance):
        self._balance = balance

    @property
    def balance(self):
        return self._balance

    @balance.setter
    def balance(self, amount):
        if amount < 0:
            raise ValueError("Balance cannot be negative")
        self._balance = amount

    @balance.deleter
    def balance(self):
        print("Account closed.")
        del self._balance

# Usage
account = BankAccount(1000)
print(account.balance)  # Access balance

account.balance = 1500   # Update balance with validation
print(account.balance)

# account.balance = -500  # Uncommenting this will raise ValueError

# del account.balance     # Delete balance

Best Practices

  • Use a single underscore (_) prefix for internal variables to indicate they are intended for internal (private) use. Python doesn’t enforce this, but it’s a nice visual aid.

  • Avoid unnecessary getters and setters. Use properties only when you need additional logic (e.g., validation, computed attributes).

1
Subscribe to my newsletter

Read articles from Dean Didion directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Dean Didion
Dean Didion

Nerdy Grandpa with a love for mentoring and all things techy