杰瑞科技汇

python property 用法

为什么需要 property

想象一个场景:你有一个 Student 类,有一个 score 属性,用于存储学生的分数,你希望分数必须在 0 到 100 之间。

python property 用法-图1
(图片来源网络,侵删)

没有 property 的写法(不推荐):

class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score  # 这里可以直接设置任何值,150
# 创建学生实例
s = Student("Alice", 150)
print(s.score)  # 输出: 150,这显然是不合理的

这种写法的问题在于,我们无法在设置 score 时进行任何验证或逻辑处理。property 就是为了解决这个问题而生的。


property 的基本用法

property 的核心思想是:将一个方法伪装成一个属性,当你访问这个“属性”时,实际上是调用一个 getter 方法;当你设置这个“属性”时,实际上是调用一个 setter 方法。

使用装饰器(最常用、最 Pythonic 的方式)

这是最推荐和易读的用法,我们用 @property 装饰器来定义 getter,用 @属性名.setter 装饰器来定义 setter。

python property 用法-图2
(图片来源网络,侵删)
class Student:
    def __init__(self, name, score):
        self.name = name
        # 注意:这里直接调用 self.score = score
        # 这样会触发我们下面定义的 setter 方法,而不是直接设置实例变量
        self.score = score
    @property
    def score(self):
        """这是一个 getter 方法,用于获取分数"""
        print("Getting score...")
        # 实际存储的变量名通常和方法名不同,以避免无限递归
        return self._score
    @score.setter
    def score(self, value):
        """这是一个 setter 方法,用于设置分数"""
        print(f"Setting score to {value}...")
        if not isinstance(value, (int, float)):
            raise TypeError("Score must be a number")
        if value < 0 or value > 100:
            raise ValueError("Score must be between 0 and 100")
        self._score = value
# --- 使用示例 ---
s = Student("Bob", 95)
# 1. 访问 score (触发 getter)
print(f"Bob's score is: {s.score}")
# 输出:
# Getting score...
# Bob's score is: 95
# 2. 设置 score (触发 setter)
s.score = 88
print(f"Bob's new score is: {s.score}")
# 输出:
# Setting score to 88...
# Getting score...
# Bob's new score is: 88
# 3. 尝试设置非法值 (会触发 setter 中的错误检查)
try:
    s.score = 101
except ValueError as e:
    print(f"Error: {e}")
# 输出:
# Setting score to 101...
# Error: Score must be between 0 and 100

代码解析:

  1. @property: 将 score 方法转换成一个属性的 "getter",当你写 s.score 时,Python 会自动调用这个方法。
  2. @score.setter: 将 score 方法转换成一个属性的 "setter",当你写 s.score = ... 时,Python 会自动调用这个方法。
  3. self._score: 我们使用一个下划线开头的变量名(如 _score)作为实际存储数据的“私有”变量,这是一种约定,表示这个变量是内部使用的,不建议外部直接访问,这避免了在 gettersetter 中调用自己而导致的无限递归。
  4. self.score = score__init__: 在 __init__ 方法里,我们同样使用 self.score = score 来赋值,这会触发我们刚刚定义的 setter,确保了即使在创建对象时,分数的合法性也得到了验证。

使用 property() 构造函数(较少用)

你也可以不使用装饰器,而是使用 property() 这个内置函数来创建属性,这种方式更明确,但可读性稍差。

class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score # 同样会触发 setter
    def get_score(self):
        print("Getting score...")
        return self._score
    def set_score(self, value):
        print(f"Setting score to {value}...")
        if not isinstance(value, (int, float)):
            raise TypeError("Score must be a number")
        if value < 0 or value > 100:
            raise ValueError("Score must be between 0 and 100")
        self._score = value
    # 使用 property() 函数创建属性
    score = property(get_score, set_score)
# --- 使用示例 ---
s = Student("Charlie", 72)
print(s.score) # 输出 Getting score... 72
s.score = 80   # 输出 Setting score to 80...

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

  • fget: getter 函数
  • fset: setter 函数
  • fdel: deleter 函数
  • doc: 属性的文档字符串

高级用法:Deleter(删除器)

你还可以定义当删除一个属性时应该执行的操作,这同样通过装饰器 @属性名.deleter 来实现。

python property 用法-图3
(图片来源网络,侵删)
class BankAccount:
    def __init__(self, balance):
        self._balance = balance
    @property
    def balance(self):
        """获取账户余额"""
        print("Getting balance...")
        return self._balance
    @balance.setter
    def balance(self, value):
        """设置账户余额"""
        print(f"Setting balance to {value}...")
        if value < 0:
            raise ValueError("Balance cannot be negative")
        self._balance = value
    @balance.deleter
    def balance(self):
        """删除账户余额"""
        print("Deleting balance...")
        # 通常我们不允许删除余额,这里只是演示
        # del self._balance
        raise AttributeError("Balance cannot be deleted")
# --- 使用示例 ---
acc = BankAccount(1000)
print(acc.balance) # Getting balance... 1000
# del acc.balance
# 输出:
# Deleting balance...
# Traceback (most recent call last):
# ...
# AttributeError: Balance cannot be deleted

只读属性

如果你想让一个属性可以被获取,但不能被修改,只需要定义 getter 而不定义 setter 即可。

class Circle:
    def __init__(self, radius):
        self.radius = radius # 这会触发下面的 setter
    @property
    def diameter(self):
        """这是一个只读属性,计算直径"""
        print("Calculating diameter...")
        return self.radius * 2
    @property
    def radius(self):
        """getter for radius"""
        return self._radius
    @radius.setter
    def radius(self, value):
        """setter for radius"""
        if value < 0:
            raise ValueError("Radius cannot be negative")
        self._radius = value
# --- 使用示例 ---
c = Circle(5)
print(f"Diameter: {c.diameter}")
# 输出:
# Calculating diameter...
# Diameter: 10
# c.diameter = 20  # 尝试设置只读属性
# Traceback (most recent call last):
# ...
# AttributeError: can't set attribute

总结与最佳实践

特性 装饰器语法 property() 函数 描述
Getter @property property(fget) 定义如何获取属性值
Setter @attr.setter property(..., fset) 定义如何设置属性值
Deleter @attr.deleter property(..., fdel) 定义如何删除属性
只读属性 只有 @property 只有 fget 参数 只有 getter,没有 setter

最佳实践:

  1. 优先使用装饰器@property 的语法更清晰、更符合 Python 的习惯。
  2. 内部变量命名:使用下划线前缀(如 _value)来存储实际数据,以避免与 property 方法名冲突和无限递归。
  3. 保持接口一致:当你将一个方法改成 property 后,所有调用该方法的代码都不需要改变,这体现了 Python 的“代码应该对接口编程,而不是对实现编程”的原则。
  4. 封装逻辑:将属性的验证、计算、日志记录等逻辑放在 gettersetter 中,保持代码的整洁和可维护性。

property 是 Python 面向对象编程中实现“封装”这一核心概念的重要工具,它让你的代码既安全又优雅。

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