In our last lesson (https://codeaddict.tistory.com/entry/Python-Intermediate006-Decorators-in-Python), we explored basic and practical decorators like access control. Today, we’ll dive into Python’s @property decorator and its companion @<property_name>.setter, which transform methods into manageable attributes. These tools are key for encapsulation in object-oriented programming. We’ll cover syntax, execution order, and some examples!
What Are @property and Setter Decorators?
- @property: Converts a method into a getter, letting you access it like an attribute (e.g., obj.name instead of obj.name()).
- @<property_name>.setter: Defines a method to set the attribute’s value, often with validation or logic.
These are built-in Python decorators for controlling class attribute access, replacing the need for explicit getter/setter methods.
Syntax and Execution Order
Syntax
class MyClass:
def __init__(self):
self._value = 0 # Private-ish attribute (convention with underscore)
@property
def value(self): # Getter
return self._value
@value.setter
def value(self, new_value): # Setter
self._value = new_value
- Getter: @property decorates a method to act as an attribute.
- Setter: @value.setter (note the dot notation) decorates a method to handle assignment.
Execution Order
Here’s how it works when using these decorators:
- Class Definition: Python registers the @property and @<property_name>.setter methods during class creation.
- Object Creation: The __init__ method initializes the underlying attribute (e.g., _value).
- Getter Call: Accessing obj.value invokes the @property-decorated method.
- Setter Call: Assigning obj.value = x invokes the @value.setter-decorated method.
Example Flow
obj = MyClass()
print(obj.value) # Step 3: Calls the getter
obj.value = 42 # Step 4: Calls the setter
print(obj.value)
Step-by-Step Explanation
- obj = MyClass()
- What Happens: Creates an instance of MyClass. The __init__ method runs, setting self._value = 0 (the initial value).
- Function Run: __init__
- Printed: Nothing is printed yet.
2. print(obj.value) (First print)
- What Happens: Accesses obj.value, which triggers the @property-decorated value method (the getter). This method returns self._value, which is currently 0.
- Function Run: The getter method value(self) under @property.
- Printed: The value returned by the getter, which is 0. So, it prints:
0
3. obj.value = 42
- What Happens: Assigns 42 to obj.value, triggering the @value.setter-decorated value method (the setter). The setter updates self._value to 42.
- Function Run: The setter method value(self, new_value) under @value.setter.
- Printed: Nothing is printed by default (unless the setter has a print statement, which the base example doesn’t). The value of self._value is now 42.
4. print(obj.value) (Second print)
- What Happens: Accesses obj.value again, calling the @property-decorated getter method. It returns self._value, which is now 42.
- Function Run: The getter method value(self) under @property.
- Printed: The updated value, which is 42. So, it prints:
42
Practical Example: Managing Employee Salary
Problem Definition
You’re designing a system to manage employee data. Each employee has a salary, but:
- The salary must not be negative (validation required).
- You want to access and update it like an attribute (e.g., emp.salary instead of emp.get_salary()).
- Updates should log changes for auditing.
How would you implement this? Try solving it before scrolling down!
Solution
class Employee:
def __init__(self, name, salary):
self.name = name
self._salary = salary # Private-ish attribute
@property
def salary(self):
print(f" → Fetching salary for {self.name}")
return self._salary
@salary.setter
def salary(self, new_salary):
if new_salary < 0:
print("Error: Salary cannot be negative!")
return # Ignore invalid updates
print(f"1. Updating salary for {self.name} to {new_salary}")
self._salary = new_salary
# Test it
print("START")
emp = Employee("Alice", 50000)
print(f"Initial salary: {emp.salary}")
emp.salary = 60000 # Update salary
print(f"New salary: {emp.salary}")
emp.salary = -1000 # Try invalid update
print(f"Final salary: {emp.salary}")
print("END")
Output
START
→ Fetching salary for Alice
Initial salary: 50000
1. Updating salary for Alice to 60000
→ Fetching salary for Alice
New salary: 60000
Error: Salary cannot be negative!
→ Fetching salary for Alice
Final salary: 60000
END
Detailed Explanation
- Class Setup: Employee uses _salary as a pseudo-private attribute (underscore convention).
- @property: The salary method becomes a getter. Accessing emp.salary runs this method, logging the fetch and returning _salary.
- @salary.setter: The setter validates new_salary. If negative, it rejects the change; otherwise, it updates _salary and logs it.
- Execution:
- emp.salary (getter) fetches the initial value (50000).
- emp.salary = 60000 (setter) updates it with validation.
- emp.salary = -1000 (setter) fails due to the check, keeping the value at 60000.
This encapsulates _salary, enforces rules, and provides a clean interface.
Summary
- @property: Turns methods into readable attributes.
- @<property_name>.setter: Adds logic to attribute assignment (e.g., validation).
- Practical Use: Ideal for managing data like salaries or settings with control.
'Python Intermediate and Advanced' 카테고리의 다른 글
Python Intermediate_009: Lambda Functions in Python (0) | 2025.04.05 |
---|---|
Python Intermediate_008: Understanding Generators in Python (0) | 2025.03.10 |
Python Intermediate_006: Decorators in Python (0) | 2025.03.04 |
Python Intermediate_005: Type Hinting in Python (1) | 2025.02.08 |
Python Intermediate_004_ Nested Classes (0) | 2025.01.12 |