Of course! In Python, properties are a powerful feature that lets you control how an attribute of a class is accessed, modified, or deleted. They provide a clean way to add "getter," "setter," and "deleter" methods to an attribute, allowing you to enforce validation, perform calculations, or trigger actions whenever the attribute is used.

This is a core concept in Object-Oriented Programming (OOP) and is fundamental to creating robust and maintainable code.
The Problem: Why Do We Need Properties?
Imagine you have a simple Employee class. You want to store the employee's salary. What's wrong with this simple approach?
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
# This works fine...
e = Employee("Alice", 50000)
print(e.salary) # Output: 50000
# ...but what if someone sets an invalid salary?
e.salary = -1000 # This is nonsensical, but Python allows it.
print(e.salary) # Output: -1000
The problem is that the salary attribute is "naked." There's no validation. A developer can set it to any value, including a negative number, which breaks the logic of our program.
The Solution: The @property Decorator
Properties solve this by wrapping an attribute with methods that control its access. We use the @property decorator to create a "getter" method.

Let's refactor the Employee class:
class Employee:
def __init__(self, name, salary):
self.name = name
# Notice we don't do self.salary = salary here
self.salary = salary # Let's use the setter from the start!
@property
def salary(self):
"""This is the GETTER for the salary attribute."""
print("Getting salary...")
return self._salary # Use a "private" name with an underscore
@salary.setter
def salary(self, value):
"""This is the SETTER for the salary attribute."""
print("Setting salary...")
if value < 0:
raise ValueError("Salary cannot be negative!")
self._salary = value
@salary.deleter
def salary(self):
"""This is the DELETER for the salary attribute."""
print("Deleting salary...")
del self._salary
How This Works:
@property: This decorator marks thesalary(self)method as a "getter." When you try to accesse.salary, this method is automatically called.@salary.setter: This decorator marks the method that handles assignment toe.salary = value. It's linked to the getter's name.@salary.deleter: This decorator marks the method that handles thedel e.salarystatement.self._salary: By convention, we use a single underscore_prefix to indicate an attribute is "internal" or "protected." This is the actual place where the data is stored. The public-facing attribute issalary.
Let's Test It:
e = Employee("Bob", 60000)
# --- GETTING the salary ---
print(f"Bob's salary is: {e.salary}")
# Output:
# Getting salary...
# Bob's salary is: 60000
# --- SETTING the salary ---
e.salary = 65000
print(f"Bob's new salary is: {e.salary}")
# Output:
# Setting salary...
# Getting salary...
# Bob's new salary is: 65000
# --- SETTING an invalid salary ---
try:
e.salary = -5000
except ValueError as err:
print(f"Error: {err}")
# Output:
# Setting salary...
# Error: Salary cannot be negative!
# --- DELETING the salary ---
del e.salary
# Output:
# Deleting salary...
# Trying to access it now will fail
try:
print(e.salary)
except AttributeError as err:
print(f"Error: {err}")
# Output:
# Getting salary...
# Error: '_salary'
Key Benefits of Using Properties
-
Encapsulation and Data Validation: You can prevent invalid data from being assigned to your object's attributes, keeping your object's state consistent and valid.
-
Computed Attributes: You can create attributes that are calculated on the fly from other attributes. The user of your class doesn't need to know the calculation is happening.
class Rectangle: def __init__(self, width, height): self.width = width self.height = height @property def area(self): """This attribute is computed, not stored.""" return self.width * self.height @property def perimeter(self): return 2 * (self.width + self.height) r = Rectangle(10, 5) print(f"Area: {r.area}") # Output: Area: 50 print(f"Perimeter: {r.perimeter}") # Output: Perimeter: 30 # You cannot SET r.area, because there's no @area.setter -
Lazy Evaluation: You can defer an expensive calculation until the value is actually requested.
(图片来源网络,侵删)class LargeData: def __init__(self, data_source): self._data = None self.data_source = data_source @property def data(self): if self._data is None: print("Loading data from source... (This is expensive!)") self._data = "A very large amount of data" return self._data ld = LargeData("some_file.txt") print("Data not loaded yet.") print(ld.data) # First access triggers loading print(ld.data) # Second access uses the cached value -
Maintaining a Clean Public API: You can start with a simple attribute and later add validation or logic without changing the public interface of your class. Code that uses
e.salary = ...doesn't need to change whethersalaryis a simple attribute or a complex property with validation.
Properties vs. "Java-style" Getters and Setters
In languages like Java, it's common to write getSalary() and setSalary(value) methods.
// Java-style
class Employee {
private int salary;
public int getSalary() { return salary; }
public void setSalary(int value) { this.salary = value; }
}
In Python, this is considered un-Pythonic (or "un-zen"). The preferred way is to use properties, which allow you to use attribute-style syntax (e.salary) while still having the power of a method call.
# Pythonic way e.salary = 50000 # Looks like simple attribute assignment
This leads to more readable and natural-looking code.
Summary
| Feature | Description | Example |
|---|---|---|
@property |
Creates a "getter" method. Called on attribute access. | @property def salary(self): ... |
@<attr>.setter |
Creates a "setter" method. Called on attribute assignment. | @salary.setter def salary(self, value): ... |
@<attr>.deleter |
Creates a "deleter" method. Called on del statement. |
@salary.deleter def salary(self): ... |
_<attr> |
Convention for the "private" attribute that holds the data. | self._salary = value |
| Benefit | Provides controlled access to attributes for validation, computation, and encapsulation, all while maintaining a clean, Pythonic public API. |
