杰瑞科技汇

Python中properties是什么?如何使用?

property 是 Python 的一个内置函数,它允许你将类的方法“伪装”成类的属性,这意味着你可以像访问普通属性一样访问一个方法,但在后台,实际上是调用了这个方法,这让你能够在不改变外部调用方式的情况下,为属性的获取、设置和删除添加额外的逻辑,例如数据验证、计算或触发事件。

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

为什么需要 property

想象一个简单的场景:你有一个 Student 类,每个学生都有一个年龄,你希望确保年龄不能是负数。

没有 property 的实现方式:

class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age
# 创建一个学生
s1 = Student("Alice", 20)
print(s1.age) # 输出: 20
# 问题:可以直接修改 age 为负数,这不符合业务逻辑
s1.age = -5
print(s1.age) # 输出: -5,这显然是错误的!

为了解决这个问题,你可能会想:写一个 set_age 方法。

class Student:
    def __init__(self, name, age):
        self.name = name
        self._age = age  # 使用下划线表示这是一个“受保护的”内部变量
    def set_age(self, new_age):
        if new_age < 0:
            print("年龄不能为负数!")
            return
        self._age = new_age
    def get_age(self):
        return self._age
s1 = Student("Alice", 20)
s1.set_age(25)
print(s1.get_age()) # 输出: 25
s1.set_age(-5) # 输出: 年龄不能为负数!
print(s1.get_age()) # 输出: 25

这种方式虽然能解决问题,但调用方式变得不直观:s1.set_age(25) 而不是 s1.age = 25,如果代码量很大,到处都是 getset 方法,会显得很冗余。

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

这时,property 就派上用场了。


property 的基本用法

property 通常通过装饰器(decorator)语法来实现,这样更简洁、更 Pythonic。

使用装饰器语法(推荐)

这是最常见和最清晰的方式。

class Student:
    def __init__(self, name, age):
        self.name = name
        # 注意:这里我们直接调用 setter 方法来设置初始值
        self.age = age
    @property
    def age(self):
        """这是一个 getter 方法,通过 @property 装饰器变成 'age' 属性"""
        print("正在获取年龄...")
        return self._age  # 返回内部存储的值
    @age.setter
    def age(self, value):
        """这是一个 setter 方法,通过 @age.setter 装饰器与 'age' 属性关联"""
        print(f"正在设置年龄为 {value}...")
        if not isinstance(value, int):
            raise TypeError("年龄必须是整数")
        if value < 0:
            raise ValueError("年龄不能为负数")
        self._age = value
    @age.deleter
    def age(self):
        """这是一个 deleter 方法,通过 @age.deleter 装饰器与 'age' 属性关联"""
        print("正在删除年龄...")
        del self._age
# --- 使用示例 ---
s1 = Student("Alice", 20)
# 1. 获取值 (调用 getter)
print(s1.age) 
# 输出:
# 正在获取年龄...
# 20
# 2. 设置值 (调用 setter)
s1.age = 25
# 输出:
# 正在设置年龄为 25...
print(s1.age)
# 输出:
# 正在获取年龄...
# 25
# 3. 验证逻辑
try:
    s1.age = -5
except ValueError as e:
    print(e)
# 输出:
# 正在设置年龄为 -5...
# 年龄不能为负数
# 4. 删除属性 (调用 deleter)
del s1.age
# 输出:
# 正在删除年龄...
# 再次访问会报错,因为 _age 已经被删除
try:
    print(s1.age)
except AttributeError as e:
    print(e)
# 输出:
# 正在获取年龄...
# 'Student' object has no attribute '_age'

代码解析:

Python中properties是什么?如何使用?-图3
(图片来源网络,侵删)
  • @property: 将 age 方法转换成一个名为 age 的只读属性,当你访问 s1.age 时,这个方法会被调用。
  • @age.setter: 将 age 方法的 setter 版本与 age 属性关联,当你给 s1.age 赋值时(如 s1.age = 25),这个方法会被调用。
  • @age.deleter: 将 age 方法的 deleter 版本与 age 属性关联,当你使用 del s1.age 时,这个方法会被调用。
  • _age: 这是一个命名约定,表示这是一个“内部”或“受保护”的变量,用于实际存储数据,外部代码应该通过 property 来访问它,而不是直接访问 _age

使用 property() 函数(非装饰器语法)

你也可以不使用装饰器,而是直接在类中调用 property() 函数,这种方式在旧代码或某些特定场景下可能会见到。

class Celsius:
    def __init__(self, temperature=0):
        self.temperature = temperature
    def get_temperature(self):
        return self._temperature
    def set_temperature(self, value):
        if value < -273:
            raise ValueError("温度不能低于绝对零度")
        self._temperature = value
    # 使用 property() 函数创建属性
    temperature = property(get_temperature, set_temperature)
c = Celsius(10)
print(c.temperature)  # 调用 get_temperature
c.temperature = 37    # 调用 set_temperature
print(c.temperature)
try:
    c.temperature = -300
except ValueError as e:
    print(e)

property(fget, fset, fdel, doc) 的参数分别是:

  • fget: 获取属性的函数 (getter)。
  • fset: 设置属性的函数 (setter)。
  • fdel: 删除属性的函数 (deleter)。
  • doc: 属性的文档字符串。

装饰器语法的优势在于代码组织更清晰,getter, setter, deleter 都围绕同一个属性名,可读性更强。


property 的典型应用场景

  1. 数据验证 这是最常见的用途,确保赋给属性的值是合法的,如上面的年龄例子,或者确保邮箱格式正确、密码长度足够等。

  2. 计算属性(Computed Attributes) 当一个属性的值需要根据其他属性动态计算时,使用 property 可以让访问方式像普通属性一样。

    class Circle:
        def __init__(self, radius):
            self.radius = radius
        @property
        def diameter(self):
            """计算直径,这是一个只读属性"""
            print("计算直径...")
            return self.radius * 2
        @property
        def area(self):
            """计算面积,这也是一个只读属性"""
            print("计算面积...")
            return 3.14159 * self.radius ** 2
    c = Circle(5)
    print(f"半径: {c.radius}")
    print(f"直径: {c.diameter}") # 访问时自动计算
    print(f"面积: {c.area}")   # 访问时自动计算
    # 输出:
    # 半径: 5
    # 计算直径...
    # 直径: 10
    # 计算面积...
    # 面积: 78.53975

    注意:diameterarea 是只读的,因为我们没有为它们定义 setter

  3. 延迟计算 对于计算成本很高的属性,可以在第一次访问时计算并缓存结果,后续访问直接返回缓存值。

  4. 维护类的内部一致性 当一个类的多个属性需要保持同步时,property 非常有用。

    class BankAccount:
        def __init__(self, owner, balance):
            self.owner = owner
            self._balance = balance
            self._transactions = []
        @property
        def balance(self):
            return self._balance
        @balance.setter
        def balance(self, value):
            if value < 0:
                raise ValueError("余额不能为负")
            self._transactions.append(f"余额更新: {self._balance} -> {value}")
            self._balance = value
    account = BankAccount("Alice", 1000)
    account.balance = 1500 # 触发 setter,同时记录交易
    account.balance = 1200
    print(account._transactions)
    # 输出: ['余额更新: 1000 -> 1500', '余额更新: 1500 -> 1200']

特性 描述
核心作用 将方法封装成属性,实现“受控”的访问。
主要优势 保持接口简洁(像访问普通属性一样),同时可以添加复杂的内部逻辑(验证、计算等)。
三种方法 getter (@property), setter (@<attr>.setter), deleter (@<attr>.deleter)。
命名约定 通常使用一个下划线前缀(如 _value)作为内部存储变量,以区分通过 property 公开的属性。
最佳实践 优先使用装饰器语法,因为它更清晰、更符合 Python 的风格。

property 是 Python 面向对象编程中一个非常重要且强大的工具,它完美地体现了 Python "简洁而强大" 的哲学。

分享:
扫描分享到社交APP
上一篇
下一篇