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

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).
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