杰瑞科技汇

Python properties如何使用?

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.

Python properties如何使用?-图1
(图片来源网络,侵删)

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.

Python properties如何使用?-图2
(图片来源网络,侵删)

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:

  1. @property: This decorator marks the salary(self) method as a "getter." When you try to access e.salary, this method is automatically called.
  2. @salary.setter: This decorator marks the method that handles assignment to e.salary = value. It's linked to the getter's name.
  3. @salary.deleter: This decorator marks the method that handles the del e.salary statement.
  4. 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 is salary.

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

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

  2. 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
  3. Lazy Evaluation: You can defer an expensive calculation until the value is actually requested.

    Python properties如何使用?-图3
    (图片来源网络,侵删)
    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
  4. 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 whether salary is 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.
分享:
扫描分享到社交APP
上一篇
下一篇