property
Property
- A property is an attribute whose value is computed dynamically at access time.
propertyenables dynamic behavior for what appears to be a static attribute.
Problem with Static Attributes
class Person:
def __init__(self, first, last):
self.first = first
self.last = last
self.name = f"{self.first} {self.last}"
- Changing
firstorlastwon't updatename, causing inconsistency.
Non-Pythonic Fixes
-
Manual setters
def set_first(self, first): ... def set_last(self, last): ... -
Method-based getter
def get_name(self): return ...
Pythonic Solution: Using @property
@property
def name(self):
return f"{self.first} {self.last}"
- Dynamically computes the name when accessed.
- Keeps value always up to date with
firstandlast.
Common Use Case: Dynamic Attribute Calculation
- Example:
Person.agedepending onbirthdateand current date
@property
def age(self):
today = dt.date.today()
will_celebrate = self.birthdate.replace(year=today.year) > today
return today.year - self.birthdate.year - will_celebrate
- Example 2: Property for RGB to Hex Conversion
class Colour:
def __init__(self, r, g, b):
self.r = r
self.g = g
self.b = b
@property
def hex(self):
return f"#{self.r:02x}{self.g:02x}{self.b:02x}"
Usage:
>>> c = Colour(146, 255, 0)
>>> c.hex
'#92ff00'
Read-only Attributes with Properties
@property
def first(self):
return self._first
- Prevents setting:
john.first = "..."→AttributeError - Underscore-prefixed attributes can still be changed manually
# Standard Library Example: `fractions.Fraction
@property
def numerator(a):
return a._numerator
Adding a Setter with @<prop>.setter
@name.setter
def name(self, full_name):
self.first, self.last = full_name.split()
Usage:
john.name = "Charles Smith"
Example: Colour.hex**
@hex.setter
def hex(self, hexstr):
hexstr = hexstr.lstrip("#")
self.r, self.g, self.b = tuple(int(hexstr[i:i+2], 16) for i in (0, 2, 4))
>>> c.hex = "#ff00ff"
>>> c.r, c.g, c.b
(255, 0, 255)
When You See property 'x' of 'Y' object has no setter
- Indicates a property is read-only
- Or a setter was not defined
Property Deleters with @<prop>.deleter
@first.deleter
def first(self):
del self._first
Usage:
del john.first
- Deleting the property does not remove the property itself—only internal data.
# Standard Library Example: `Request.full_url`
@full_url.deleter
def full_url(self):
self._full_url = None
self.fragment = None
self.selector = ''
Using property() Directly (Without Decorators)
x = property(getx, setx, delx, "Docstring")
- Less common, but useful if method access is also desired
- Example from
calendar.Calendar:firstweekday = property(getfirstweekday, setfirstweekday)
But why not just function call?
✅ Properties make your object’s interface feel more like working with data, not with machinery.
It’s not just about () vs not — it’s about design intention, readability, and future flexibility.
🔍 So when should you use a method vs a property?
| Use a method when... | Use a property when... |
|---|---|
| It’s expensive or has side effects | It’s lightweight, fast, and safe to compute |
| It requires parameters | It’s based only on internal state |
| The action is more like a command | The result feels like an attribute (e.g., .age) |
| You want to make it clear: "this is a process" | You want to make it look like data on the object |
🧠 Think like an API designer
Let’s say you have:
person.age()
It works, but what’s the first impression?
This feels like it does something non-trivial or maybe needs arguments.
Now:
person.age
Now it’s clear:
“Oh! This is just some attribute about the person.”
Even if it's calculated, it feels like part of their state, not an action you command.
🔧 Real-World Flexibility: Migration Path
Let’s say today you do this:
class Product:
def __init__(self, price):
self.price = price
self.tax_rate = 0.1
def total_price(self):
return self.price * (1 + self.tax_rate)
All your code has p.total_price() everywhere.
Tomorrow, you decide to make it a property:
@property
def total_price(self):
...
Now you have to update every call to drop the ().
But if you start with a property, and later need to refactor to use caching or more logic inside — the outside world doesn’t need to change a thing.
So in summary:
- using
@propertyis about cleaner, intention-driven design, and lets you hide implementation details while keeping the interface consistent.