- What is @property ā a decorator that makes methods look like attributes
- Why use it ā cleaner code, validation, computed values
- Getter ā how to read values
- Setter ā how to set values with validation
- Deleter ā how to delete values
- Computed properties ā values calculated on the fly
What is the @property Decorator?
The @property decorator is a built-in Python decorator that lets you define methods that can be accessed like attributes. It's a clean and Pythonic way to implement getters, setters, and deleters.
Think of @property like a receptionist. When you want to talk to someone in a company (access an attribute), you go through the receptionist. The receptionist controls who you can talk to, checks your ID (validation), and can even transfer you to someone else (computed values).
Instead of calling obj.get_value(), you can just use obj.value. And instead of obj.set_value(10), you use obj.value = 10. Much cleaner!
š” Key concept: @property lets you add logic to attribute access while keeping the simple attribute syntax.
Why Use @property?
The Benefits of @property
Let's see why @property makes your code better.
# Why Use @property?
print("=" * 50)
print("WHY USE @property?")
print("=" * 50)
# ============================================================
# WITHOUT @property ā Getters and Setters (Verbose)
# ============================================================
print("\nā WITHOUT @property:")
class PersonOld:
def __init__(self, name, age):
self._name = name
self._age = age
# Getter method
def get_name(self):
return self._name
# Setter method
def set_name(self, name):
if not name:
raise ValueError("Name cannot be empty")
self._name = name
# Getter method
def get_age(self):
return self._age
# Setter method
def set_age(self, age):
if age < 0:
raise ValueError("Age cannot be negative")
self._age = age
person = PersonOld("Alice", 30)
print(f" Name: {person.get_name()}") # Have to call methods
print(f" Age: {person.get_age()}")
person.set_name("Bob")
person.set_age(25)
print(" ā Too many method calls for simple attribute access")
# ============================================================
# WITH @property ā Clean and Simple
# ============================================================
print("\nā
WITH @property:")
class Person:
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
"""Get the name"""
return self._name
@name.setter
def name(self, value):
"""Set the name with validation"""
if not value:
raise ValueError("Name cannot be empty")
self._name = value
@property
def age(self):
"""Get the age"""
return self._age
@age.setter
def age(self, value):
"""Set the age with validation"""
if value < 0:
raise ValueError("Age cannot be negative")
self._age = value
person = Person("Alice", 30)
print(f" Name: {person.name}") # Looks like an attribute!
print(f" Age: {person.age}")
person.name = "Bob"
person.age = 25
print(" ā
Clean syntax ā looks like normal attributes")
print(" ā
Validation is automatic ā without extra method calls")
# ============================================================
# THE BENEFITS
# ============================================================
print("\n" + "-" * 30)
print("BENEFITS OF @property")
print("-" * 30)
print("""
āāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā BENEFIT ā WHAT IT MEANS FOR YOU ā
āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Clean syntax ā obj.property instead of obj.get_property()ā
ā ā ā
ā Validation ā Add validation when setting values ā
ā ā ā
ā Computed values ā Calculate values on the fly ā
ā ā ā
ā Read-only ā Create properties that can't be changed ā
ā ā ā
ā Backward compatible ā Change attributes to properties without ā
ā ā breaking existing code ā
ā ā ā
ā Encapsulation ā Hide implementation details ā
āāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
š @property gives you the best of both worlds: attribute syntax + method logic.
""")
Benefits of @property:
- Clean syntax ā attribute-like access
- Validation ā add logic when setting values
- Computed values ā calculate on the fly
- Read-only ā prevent changes
- Backward compatible ā change from attribute to property
- Encapsulation ā hide implementation details
Quick Check: What's the main advantage of @property over traditional getters and setters? (Answer: Cleaner syntax ā you use dot notation instead of method calls)
The Getter - Reading Values
How to Read Values with @property
The getter is the simplest part. You just put @property above a method that returns a value.
# The Getter - Reading Values
print("=" * 50)
print("THE GETTER")
print("=" * 50)
# ============================================================
# BASIC GETTER
# ============================================================
print("\n1. BASIC GETTER")
class Student:
def __init__(self, name, grade):
self._name = name
self._grade = grade
@property
def name(self):
"""Getter for name"""
return self._name
@property
def grade(self):
"""Getter for grade"""
return self._grade
student = Student("Alice", 95)
print(f" Name: {student.name}") # Looks like an attribute
print(f" Grade: {student.grade}")
# ============================================================
# GETTER WITH FORMATTING
# ============================================================
print("\n2. GETTER WITH FORMATTING")
class Product:
def __init__(self, name, price):
self._name = name
self._price = price
@property
def name(self):
return self._name.title() # Capitalize the name
@property
def price(self):
return f"${self._price:.2f}" # Format as currency
product = Product("python book", 29.99)
print(f" Product: {product.name}")
print(f" Price: {product.price}")
# ============================================================
# READ-ONLY PROPERTIES
# ============================================================
print("\n3. READ-ONLY PROPERTIES")
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@property
def area(self):
"""Read-only property - calculated on the fly"""
import math
return math.pi * self._radius ** 2
@property
def circumference(self):
"""Read-only property - calculated on the fly"""
import math
return 2 * math.pi * self._radius
circle = Circle(5)
print(f" Radius: {circle.radius}")
print(f" Area: {circle.area:.2f}")
print(f" Circumference: {circle.circumference:.2f}")
# Can't set read-only properties
try:
circle.area = 100
except AttributeError as e:
print(f" ā Can't set area: {e}")
# ============================================================
# PROPERTIES WITH PROTECTED DATA
# ============================================================
print("\n4. PROPERTIES WITH PROTECTED DATA")
class BankAccount:
def __init__(self, owner, balance):
self._owner = owner
self._balance = balance
self._transactions = []
@property
def owner(self):
return self._owner
@property
def balance(self):
return self._balance
@property
def transaction_count(self):
return len(self._transactions)
def deposit(self, amount):
self._balance += amount
self._transactions.append(f"Deposit: +${amount}")
def withdraw(self, amount):
if amount <= self._balance:
self._balance -= amount
self._transactions.append(f"Withdraw: -${amount}")
else:
raise ValueError("Insufficient funds")
account = BankAccount("Alice", 1000)
account.deposit(500)
account.withdraw(200)
print(f" Owner: {account.owner}")
print(f" Balance: ${account.balance}")
print(f" Transactions: {account.transaction_count}")
Getter key points:
- @property ā turns a method into a read-only attribute
- Return value ā the method returns the value
- Formatting ā can format or transform the value
- Read-only ā properties without setters are read-only
- Computed ā can calculate values on the fly
Quick Check: What decorator do you use for a getter? (Answer: @property)
The Setter - Setting Values
How to Set Values with Validation
The setter lets you control how values are set. You can add validation, logging, or any other logic.
# The Setter - Setting Values
print("=" * 50)
print("THE SETTER")
print("=" * 50)
# ============================================================
# BASIC SETTER
# ============================================================
print("\n1. BASIC SETTER")
class Person:
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
return self._name
@name.setter
def name(self, value):
if not value or not value.strip():
raise ValueError("Name cannot be empty")
self._name = value.strip()
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if not isinstance(value, int):
raise TypeError("Age must be an integer")
if value < 0 or value > 150:
raise ValueError("Age must be between 0 and 150")
self._age = value
person = Person("Alice", 30)
print(f" Initial: {person.name}, {person.age}")
person.name = "Bob"
person.age = 35
print(f" Updated: {person.name}, {person.age}")
# Try invalid values
try:
person.name = ""
except ValueError as e:
print(f" ā {e}")
try:
person.age = 200
except ValueError as e:
print(f" ā {e}")
# ============================================================
# SETTER WITH LOGGING
# ============================================================
print("\n2. SETTER WITH LOGGING")
class Config:
def __init__(self):
self._settings = {}
self._log = []
@property
def settings(self):
return self._settings
@settings.setter
def settings(self, value):
if not isinstance(value, dict):
raise TypeError("Settings must be a dictionary")
self._log.append(f"Settings updated: {value}")
self._settings = value
@property
def log(self):
return self._log
config = Config()
config.settings = {"theme": "dark", "language": "en"}
config.settings = {"theme": "light"}
print(f" Settings: {config.settings}")
print(f" Log: {config.log}")
# ============================================================
# SETTER WITH TRANSFORMATION
# ============================================================
print("\n3. SETTER WITH TRANSFORMATION")
class User:
def __init__(self, email):
self._email = email
@property
def email(self):
return self._email
@email.setter
def email(self, value):
# Convert to lowercase and strip whitespace
clean_email = value.strip().lower()
if '@' not in clean_email:
raise ValueError("Invalid email format")
self._email = clean_email
user = User("Alice@Example.com")
print(f" Original email: {user.email}")
user.email = "BOB@TEST.COM"
print(f" After update: {user.email}") # Converted to lowercase
# ============================================================
# COMPLEX SETTER WITH MULTIPLE RULES
# ============================================================
print("\n4. COMPLEX SETTER WITH MULTIPLE RULES")
class Product:
def __init__(self, name, price, quantity):
self._name = name
self._price = price
self._quantity = quantity
@property
def name(self):
return self._name
@name.setter
def name(self, value):
if not value or len(value) < 2:
raise ValueError("Name must be at least 2 characters")
self._name = value
@property
def price(self):
return self._price
@price.setter
def price(self, value):
if value <= 0:
raise ValueError("Price must be positive")
if value > 10000:
raise ValueError("Price cannot exceed $10,000")
self._price = value
@property
def quantity(self):
return self._quantity
@quantity.setter
def quantity(self, value):
if value < 0:
raise ValueError("Quantity cannot be negative")
self._quantity = value
@property
def total_value(self):
return self._price * self._quantity
product = Product("Laptop", 999.99, 10)
print(f" {product.name}: ${product.price} x {product.quantity} = ${product.total_value}")
product.quantity = 5
print(f" Updated: ${product.total_value}")
try:
product.price = -100
except ValueError as e:
print(f" ā {e}")
Setter key points:
- @name.setter ā defines a setter for the property
- Validation ā check values before setting
- Transformation ā modify values (e.g., lowercase)
- Logging ā track changes
- Multiple rules ā can enforce complex constraints
Quick Check: How do you define a setter for a property called 'name'? (Answer: @name.setter above the setter method)
The Deleter - Deleting Values
How to Delete Values with @property
The deleter lets you control what happens when someone tries to delete a property.
# The Deleter - Deleting Values
print("=" * 50)
print("THE DELETER")
print("=" * 50)
# ============================================================
# BASIC DELETER
# ============================================================
print("\n1. BASIC DELETER")
class Data:
def __init__(self):
self._value = "Secret"
@property
def value(self):
return self._value
@value.setter
def value(self, new_value):
self._value = new_value
@value.deleter
def value(self):
"""Delete the value"""
print(" Deleting value...")
self._value = None
data = Data()
print(f" Value: {data.value}")
data.value = "New secret"
print(f" Updated: {data.value}")
del data.value
print(f" After delete: {data.value}")
# ============================================================
# DELETER WITH CLEANUP
# ============================================================
print("\n2. DELETER WITH CLEANUP")
class FileResource:
def __init__(self, filename):
self.filename = filename
self._content = None
self._is_open = False
@property
def content(self):
if not self._is_open:
self._open_file()
return self._content
def _open_file(self):
print(f" Opening file: {self.filename}")
self._is_open = True
self._content = f"Content of {self.filename}"
def _close_file(self):
print(f" Closing file: {self.filename}")
self._is_open = False
@content.deleter
def content(self):
"""Close the file and delete content"""
print(" Deleting content...")
self._close_file()
self._content = None
print(f" File {self.filename} closed")
file = FileResource("data.txt")
print(f" Content: {file.content}")
del file.content
print(" File resource cleaned up")
# ============================================================
# DELETER WITH PERMISSION CHECK
# ============================================================
print("\n3. DELETER WITH PERMISSION CHECK")
class SecureData:
def __init__(self, data):
self._data = data
self._deleted = False
@property
def data(self):
if self._deleted:
raise AttributeError("Data has been deleted")
return self._data
@data.deleter
def data(self):
"""Delete data with confirmation"""
print(" ā ļø Deleting sensitive data...")
# In real code, you'd check permissions here
self._deleted = True
self._data = None
print(" ā
Data deleted")
secure = SecureData("Sensitive information")
print(f" Data: {secure.data}")
del secure.data
try:
print(f" Data: {secure.data}")
except AttributeError as e:
print(f" ā {e}")
# ============================================================
# COMPLETE EXAMPLE
# ============================================================
print("\n4. COMPLETE EXAMPLE")
class Employee:
def __init__(self, name, salary):
self._name = name
self._salary = salary
self._terminated = False
@property
def name(self):
if self._terminated:
raise AttributeError("Employee has been terminated")
return self._name
@name.setter
def name(self, value):
if self._terminated:
raise AttributeError("Cannot modify terminated employee")
if not value:
raise ValueError("Name cannot be empty")
self._name = value
@property
def salary(self):
if self._terminated:
raise AttributeError("Employee has been terminated")
return self._salary
@salary.setter
def salary(self, value):
if self._terminated:
raise AttributeError("Cannot modify terminated employee")
if value < 0:
raise ValueError("Salary cannot be negative")
self._salary = value
@salary.deleter
def salary(self):
"""Delete salary (terminate employee)"""
print(f" ā Terminating {self._name}")
self._terminated = True
self._salary = 0
del self._name
emp = Employee("Alice", 50000)
print(f" Employee: {emp.name}, Salary: ${emp.salary}")
try:
emp.salary = -1000
except ValueError as e:
print(f" ā {e}")
del emp.salary # Terminate employee
try:
print(f" Name: {emp.name}")
except AttributeError as e:
print(f" ā {e}")
Deleter key points:
- @name.deleter ā defines a deleter for the property
- Cleanup ā release resources or reset state
- Permission checks ā verify before deleting
- State management ā update internal state
- Exception handling ā raise errors for invalid operations
Quick Check: What decorator is used for a deleter? (Answer: @name.deleter)
Computed Properties
Values Calculated on the Fly
Computed properties are values that are calculated when you access them. They're not stored, they're calculated each time.
# Computed Properties
print("=" * 50)
print("COMPUTED PROPERTIES")
print("=" * 50)
# ============================================================
# BASIC COMPUTED PROPERTIES
# ============================================================
print("\n1. BASIC COMPUTED PROPERTIES")
class Rectangle:
def __init__(self, width, height):
self._width = width
self._height = height
@property
def width(self):
return self._width
@width.setter
def width(self, value):
if value <= 0:
raise ValueError("Width must be positive")
self._width = value
@property
def height(self):
return self._height
@height.setter
def height(self, value):
if value <= 0:
raise ValueError("Height must be positive")
self._height = value
@property
def area(self):
"""Computed property"""
return self._width * self._height
@property
def perimeter(self):
"""Computed property"""
return 2 * (self._width + self._height)
rect = Rectangle(5, 3)
print(f" Width: {rect.width}, Height: {rect.height}")
print(f" Area: {rect.area}")
print(f" Perimeter: {rect.perimeter}")
rect.width = 10
print(f" After update - Area: {rect.area}")
# ============================================================
# COMPUTED PROPERTIES WITH CACHING
# ============================================================
print("\n2. COMPUTED PROPERTIES WITH CACHING")
class ExpensiveCalculation:
def __init__(self, value):
self._value = value
self._cached_result = None
self._cache_valid = False
@property
def value(self):
return self._value
@value.setter
def value(self, new_value):
self._value = new_value
self._cache_valid = False # Invalidate cache
@property
def result(self):
"""Expensive calculation with caching"""
if not self._cache_valid:
print(" š» Calculating... (expensive operation)")
import time
time.sleep(0.5) # Simulate expensive calculation
self._cached_result = self._value ** 2
self._cache_valid = True
else:
print(" š¾ Using cached result")
return self._cached_result
calc = ExpensiveCalculation(5)
print(f" First access: {calc.result}") # Calculates
print(f" Second access: {calc.result}") # Uses cache
calc.value = 10
print(f" After value change: {calc.result}") # Recalculates
# ============================================================
# COMPUTED PROPERTIES WITH DEPENDENCIES
# ============================================================
print("\n3. COMPUTED PROPERTIES WITH DEPENDENCIES")
class Order:
def __init__(self):
self._items = []
self._tax_rate = 0.08
def add_item(self, name, price, quantity=1):
self._items.append({"name": name, "price": price, "quantity": quantity})
@property
def subtotal(self):
return sum(item["price"] * item["quantity"] for item in self._items)
@property
def tax(self):
return self.subtotal * self._tax_rate
@property
def total(self):
return self.subtotal + self.tax
@property
def item_count(self):
return len(self._items)
@property
def total_items(self):
return sum(item["quantity"] for item in self._items)
order = Order()
order.add_item("Laptop", 999.99)
order.add_item("Mouse", 29.99, 2)
print(f" Items: {order.item_count} items")
print(f" Total items: {order.total_items}")
print(f" Subtotal: ${order.subtotal:.2f}")
print(f" Tax: ${order.tax:.2f}")
print(f" Total: ${order.total:.2f}")
# ============================================================
# READ-ONLY COMPUTED PROPERTIES
# ============================================================
print("\n4. READ-ONLY COMPUTED PROPERTIES")
class Temperature:
def __init__(self, celsius):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
self._celsius = value
@property
def fahrenheit(self):
"""Read-only computed property"""
return self._celsius * 9/5 + 32
@property
def kelvin(self):
"""Read-only computed property"""
return self._celsius + 273.15
temp = Temperature(25)
print(f" Celsius: {temp.celsius}°C")
print(f" Fahrenheit: {temp.fahrenheit}°F")
print(f" Kelvin: {temp.kelvin}K")
temp.celsius = 0
print(f" After update:")
print(f" Fahrenheit: {temp.fahrenheit}°F")
Computed properties key points:
- On-the-fly ā calculated when accessed
- Caching ā store results for performance
- Dependencies ā update when dependencies change
- Read-only ā computed values often don't have setters
- Convenience ā provide derived values easily
Quick Check: What is a computed property? (Answer: A property whose value is calculated when accessed, not stored)
Real-World Example
Building a User Profile System
# Real-World Example: User Profile System
from datetime import datetime
import re
print("=" * 60)
print("USER PROFILE SYSTEM")
print("=" * 60)
# ============================================================
# USER PROFILE WITH @property
# ============================================================
class UserProfile:
"""Complete user profile with property-based encapsulation"""
def __init__(self, username, email, birth_date):
self._username = None
self._email = None
self._birth_date = None
self._last_login = None
self._login_count = 0
self._is_active = True
# Use setters for validation
self.username = username
self.email = email
self.birth_date = birth_date
# ----- USERNAME -----
@property
def username(self):
return self._username
@username.setter
def username(self, value):
if not value or len(value) < 3:
raise ValueError("Username must be at least 3 characters")
if not re.match(r'^[a-zA-Z0-9_]+$', value):
raise ValueError("Username can only contain letters, numbers, and underscores")
self._username = value.lower()
# ----- EMAIL -----
@property
def email(self):
return self._email
@email.setter
def email(self, value):
if '@' not in value or '.' not in value:
raise ValueError("Invalid email format")
self._email = value.strip().lower()
# ----- BIRTH DATE -----
@property
def birth_date(self):
return self._birth_date
@birth_date.setter
def birth_date(self, value):
try:
# Try to parse the date
if isinstance(value, str):
date_obj = datetime.strptime(value, "%Y-%m-%d")
else:
date_obj = value
except:
raise ValueError("Invalid date format. Use YYYY-MM-DD")
# Check age
today = datetime.now()
age = today.year - date_obj.year - ((today.month, today.day) < (date_obj.month, date_obj.day))
if age < 13:
raise ValueError("User must be at least 13 years old")
if age > 120:
raise ValueError("Invalid birth date")
self._birth_date = date_obj
# ----- COMPUTED PROPERTIES -----
@property
def age(self):
"""Calculate age from birth date"""
today = datetime.now()
return today.year - self._birth_date.year - ((today.month, today.day) < (self._birth_date.month, self._birth_date.day))
@property
def age_group(self):
"""Categorize user by age"""
age = self.age
if age < 18:
return "Minor"
elif age < 25:
return "Young Adult"
elif age < 40:
return "Adult"
elif age < 65:
return "Middle Age"
else:
return "Senior"
@property
def profile_complete(self):
"""Check if profile is complete"""
return all([self._username, self._email, self._birth_date])
@property
def login_count(self):
return self._login_count
@property
def last_login(self):
return self._last_login
@property
def is_active(self):
return self._is_active
# ----- PUBLIC METHODS -----
def login(self):
"""Record a login"""
self._last_login = datetime.now()
self._login_count += 1
if not self._is_active:
self._is_active = True
return f"Welcome back, {self.username}!"
def logout(self):
"""Logout user"""
return f"Goodbye, {self.username}!"
def deactivate(self):
"""Deactivate the account"""
self._is_active = False
return f"Account for {self.username} deactivated"
def get_profile_summary(self):
"""Get a summary of the profile"""
return {
"username": self.username,
"email": self.email,
"age": self.age,
"age_group": self.age_group,
"profile_complete": self.profile_complete,
"login_count": self.login_count,
"is_active": self.is_active,
"last_login": self.last_login.strftime("%Y-%m-%d %H:%M") if self.last_login else "Never"
}
# ============================================================
# DEMONSTRATION
# ============================================================
print("\n1. CREATING A USER PROFILE")
try:
user = UserProfile("alice_123", "alice@example.com", "2000-05-15")
print(f" ā
User created: {user.username}")
print(f" š§ Email: {user.email}")
print(f" š Age: {user.age} ({user.age_group})")
except ValueError as e:
print(f" ā Error: {e}")
print("\n2. VALIDATION TESTS")
try:
user2 = UserProfile("al", "bob@test.com", "1990-01-01")
except ValueError as e:
print(f" ā Username too short: {e}")
try:
user3 = UserProfile("bob_123", "invalid-email", "1990-01-01")
except ValueError as e:
print(f" ā Invalid email: {e}")
try:
user4 = UserProfile("charlie_123", "charlie@test.com", "2015-01-01")
except ValueError as e:
print(f" ā Too young: {e}")
print("\n3. USER ACTIVITY")
print(f" {user.login()}")
print(f" {user.login()}")
print(f" Logins: {user.login_count}")
print("\n4. PROFILE SUMMARY")
summary = user.get_profile_summary()
for key, value in summary.items():
print(f" {key}: {value}")
print("\n5. UPDATING PROFILE")
try:
user.email = "new_email@example.com"
print(f" ā
Email updated: {user.email}")
except ValueError as e:
print(f" ā {e}")
print("\n6. DEACTIVATING ACCOUNT")
print(f" {user.deactivate()}")
print(f" Active: {user.is_active}")
print("\n" + "=" * 60)
print("KEY TAKEAWAYS:")
print("=" * 60)
print("ā
@property provides clean attribute-like access")
print("ā
Validation ensures data quality")
print("ā
Computed properties provide derived values")
print("ā
Getters, setters, and deleters give full control")
print("ā
Encapsulation hides implementation details")
print("ā
The code is clean, readable, and maintainable")
Real-world example key points:
- Validation ā username, email, birth date all validated
- Computed properties ā age, age_group, profile_complete
- Read-only ā login_count, last_login, is_active
- Encapsulation ā internal state is protected
- Clean interface ā users interact with simple attributes
Quick Check: What does the age property do in the UserProfile class? (Answer: It calculates the user's age from their birth date)
Best Practices
Using @property Effectively
# Best Practices for @property
print("=" * 60)
print("BEST PRACTICES FOR @property")
print("=" * 60)
# ============================================================
# 1. KEEP PROPERTIES SIMPLE
# ============================================================
print("\n1. KEEP PROPERTIES SIMPLE")
# ā
GOOD: Simple getters and setters
class Good:
def __init__(self, value):
self._value = value
@property
def value(self):
return self._value
@value.setter
def value(self, new_value):
if new_value < 0:
raise ValueError("Value must be positive")
self._value = new_value
# ā BAD: Complex operations in properties
class Bad:
@property
def value(self):
# Doing heavy computation here is bad
# Properties should be light and fast
return self._heavy_computation()
print(" ā
Properties should be simple and fast")
# ============================================================
# 2. DON'T USE PROPERTIES FOR EXPENSIVE OPERATIONS
# ============================================================
print("\n2. DON'T USE PROPERTIES FOR EXPENSIVE OPERATIONS")
# ā BAD: Expensive calculation in property
class BadReport:
def __init__(self, data):
self._data = data
@property
def processed_data(self):
# This might be slow - better as a method
return self._process_all_data()
# ā
GOOD: Use methods for expensive operations
class GoodReport:
def __init__(self, data):
self._data = data
def process_data(self):
"""This is clearly an operation, not a property"""
return self._process_all_data()
print(" ā
Use methods for expensive operations, not properties")
# ============================================================
# 3. BE CONSISTENT WITH NAMING
# ============================================================
print("\n3. BE CONSISTENT WITH NAMING")
# ā
GOOD: Clear, consistent naming
class Product:
def __init__(self, name, price):
self._name = name
self._price = price
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
@property
def price(self):
return self._price
@price.setter
def price(self, value):
if value < 0:
raise ValueError("Price cannot be negative")
self._price = value
print(" ā
Use the same name for getter and setter")
# ============================================================
# 4. USE READ-ONLY PROPERTIES FOR DERIVED VALUES
# ============================================================
print("\n4. USE READ-ONLY PROPERTIES FOR DERIVED VALUES")
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value <= 0:
raise ValueError("Radius must be positive")
self._radius = value
@property
def area(self):
"""Read-only computed property"""
import math
return math.pi * self._radius ** 2
print(" ā
Use read-only properties for derived values")
# ============================================================
# 5. DON'T OVERUSE @property
# ============================================================
print("\n5. DON'T OVERUSE @property")
# ā BAD: Using @property for everything (overkill)
class Overkill:
def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c
@property
def a(self):
return self._a
@a.setter
def a(self, value):
self._a = value
@property
def b(self):
return self._b
@b.setter
def b(self, value):
self._b = value
@property
def c(self):
return self._c
@c.setter
def c(self, value):
self._c = value
# ā
GOOD: Only use @property when you need control
class Simple:
def __init__(self, a, b, c):
self.a = a # Public attribute - no need for property
self.b = b
self.c = c
print(" ā
Only use @property when you need control")
# ============================================================
# 6. SUMMARY
# ============================================================
print("\n" + "=" * 60)
print("BEST PRACTICES SUMMARY")
print("=" * 60)
print("""
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā PRACTICE ā WHY IT MATTERS ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Keep properties simple ā Should be fast and lightweight ā
ā ā ā
ā Don't use for expensive ā Use methods for heavy operations ā
ā operations ā ā
ā ā ā
ā Be consistent with naming ā Makes code predictable and readable ā
ā ā ā
ā Use read-only for derived ā Clear intent and prevents modification ā
ā values ā ā
ā ā ā
ā Don't overuse @property ā Use when you need control, not by default ā
ā ā ā
ā Document your properties ā Help others understand your code ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
š @property is powerful, but use it when you need control over attribute access.
""")
Best practices summary:
- Keep properties simple ā they should be fast
- Don't use for expensive operations ā use methods instead
- Be consistent with naming ā make it predictable
- Use read-only for derived values ā clear intent
- Don't overuse ā use when you need control
- Document properties ā help others understand
Quick Check: When should you use @property? (Answer: When you need control over attribute access, like validation or computed values)
Try It Yourself
Experiment with the @property decorator in the editor below.
PROPERTY DECORATOR - PRACTICE
==================================================
1. BASIC GETTER AND SETTER
Owner: Alice
Balance: $1000
New balance: $1300
ā Balance cannot be negative
2. COMPUTED PROPERTY
Side: 5
Area: 25
Perimeter: 20
After update - Area: 100
3. DELETER
User: Alice
Closing session for Alice
ā Session is closed
You've Got It!
You now understand the @property decorator in Python. You know how to use getters, setters, and deleters, and how to create computed properties.
Quick Quiz
Test what you've learned:
Frequently Asked Questions
What is the @property decorator in Python?
What's the difference between @property and regular methods?
obj.property) instead of methods (obj.get_property()). This makes the code cleaner and more intuitive. They're great for when you want to add logic without changing the syntax.
Can a property have both a getter and a setter?
@property and the setter uses @name.setter. This gives you full control over reading and writing the property.
What is a computed property?
area property that calculates the area from width and height.
Should I use @property for all attributes?
Can I use @property with dataclasses?
Where to Go From Here
Now that you understand the @property decorator in Python, check out these related topics:
Decorators
Learn about decorators ā the foundation of @property.
Learn More āEncapsulation
Learn how @property helps with encapsulation.
Learn More āDataclasses
Learn how dataclasses work with @property.
Learn More ā