杰瑞科技汇

property函数如何实现属性封装与控制?

property 是 Python 的一个内置函数,它允许你以一种“面向对象”的方式,为类的属性添加 getter、setter 和 deleter 方法,这使得你可以控制对属性的访问,同时保持代码的简洁性和易用性,让外部调用者感觉就像在直接操作一个普通属性一样。

property函数如何实现属性封装与控制?-图1
(图片来源网络,侵删)

为什么需要 property

想象一个场景:你有一个 Student 类,其中有一个 score 属性,你希望确保分数的值总是在 0 到 100 之间,不能为负数,也不能超过 100。

不使用 property 的方式(不够优雅):

class Student:
    def __init__(self, name, score):
        self.name = name
        self._score = score  # 使用下划线表示“内部使用”的变量
    def set_score(self, value):
        if not 0 <= value <= 100:
            raise ValueError("Score must be between 0 and 100")
        self._score = value
    def get_score(self):
        return self._score
# 使用方式
s = Student("Alice", 95)
# 想要修改分数,必须调用方法,而不是直接赋值
s.set_score(98)
print(s.get_score()) # 输出: 98
# 很容易忘记使用方法,直接赋值就破坏了规则
s._score = 101 # 这样做是允许的,但违背了设计初衷
print(s.get_score()) # 输出: 101,这显然是错误的

这种方式虽然可行,但不够“Pythonic”,使用者需要记住 get_scoreset_score 这两个方法,而不是像使用普通属性一样直观。


使用 property 函数(优雅的方式)

property 函数可以让你将上述的 get_scoreset_score 方法“伪装”成一个属性,这样,外部代码就可以像 s.score = 98 这样直接赋值,Python 会自动调用你定义的 setter 方法。

property函数如何实现属性封装与控制?-图2
(图片来源网络,侵删)

property 函数的基本用法如下: property(fget=None, fset=None, fdel=None, doc=None)

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

示例:用 property 重构 Student

class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score  # 注意:这里会调用 setter 方法!
    # 1. 定义 getter 方法
    def get_score(self):
        print("Getting score...")
        return self._score
    # 2. 定义 setter 方法
    def set_score(self, value):
        print("Setting score...")
        if not isinstance(value, int):
            raise TypeError("Score must be an integer.")
        if not 0 <= value <= 100:
            raise ValueError("Score must be between 0 and 100")
        self._score = value
    # 3. 使用 property 函数将它们绑定到 'score' 属性
    score = property(get_score, set_score)
# --- 使用 ---
s = Student("Bob", 88)
# 获取值 (调用 get_score)
print(s.score) 
# 输出:
# Getting score...
# 88
# 设置值 (调用 set_score)
s.score = 95
# 输出:
# Setting score...
# 尝试设置非法值
try:
    s.score = 101
except ValueError as e:
    print(e)
# 输出:
# Setting score...
# Score must be between 0 and 100

在这个例子中,s.score = 95 看起来是直接赋值,但实际上它调用了 set_score 方法。print(s.score) 看起来是直接访问,但实际上它调用了 get_score 方法,这完美地实现了“控制访问”和“语法简洁”的统一。


更现代的 @property 装饰器语法

虽然 property() 函数很强大,但它的语法(先定义方法,再用 property 绑定)有些冗长,从 Python 2.6 开始,引入了更简洁的 @property 装饰器语法,这是目前最推荐、最常用的方式。

装饰器语法将 gettersetterdeleter 方法更紧密地联系在一起。

property函数如何实现属性封装与控制?-图3
(图片来源网络,侵删)

示例:使用 @property 装饰器

class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score # 同样,这里会调用 setter
    # --- Getter ---
    @property
    def score(self):
        """This is the 'score' property."""
        print("Getting score (using decorator)...")
        return self._score
    # --- Setter ---
    # 注意:装饰器名称必须和 getter 一样!这里是 @score.setter
    @score.setter
    def score(self, value):
        print("Setting score (using decorator)...")
        if not isinstance(value, int):
            raise TypeError("Score must be an integer.")
        if not 0 <= value <= 100:
            raise ValueError("Score must be between 0 and 100")
        self._score = value
    # --- Deleter (可选) ---
    # 当执行 `del s.score` 时会调用此方法
    @score.deleter
    def score(self):
        print("Deleting score...")
        del self._score
        # 或者 self._score = None
# --- 使用 ---
s = Student("Charlie", 76)
# 获取
print(s.score)
# 输出:
# Getting score (using decorator)...
# 76
# 设置
s.score = 82
# 输出:
# Setting score (using decorator)...
# 删除
del s.score
# 输出:
# Deleting score...
# 再次尝试获取会报错,因为 _score 已被删除
try:
    print(s.score)
except AttributeError as e:
    print(e)
# 输出:
# Getting score (using decorator)...
# 'Student' object has no attribute '_score'

@property 语法解析:

  1. @property: 将下面的方法标记为属性的 "getter",这个方法名(这里是 score)本身会成为属性的名称。
  2. @<property_name>.setter: 将下面的方法标记为属性的 "setter"。<property_name> 必须和 getter 的方法名完全一致。
  3. @<property_name>.deleter: 将下面的方法标记为属性的 "deleter"。

这种语法更清晰,将所有与 score 属性相关的方法都放在了一起,可读性更高。


property 的典型应用场景

  1. 数据验证: 如上面的分数例子,确保赋给属性的值是有效的。

  2. 计算属性 (Read-only 属性): 创建一个没有 setter 的属性,它的值是通过其他属性计算得出的,它就像一个只读属性。

    import math
    class Circle:
        def __init__(self, radius):
            self.radius = radius # 这会调用 radius 的 setter
        @property
        def radius(self):
            return self._radius
        @radius.setter
        def radius(self, value):
            if value < 0:
                raise ValueError("Radius cannot be negative")
            self._radius = value
        @property
        def area(self):
            """这是一个只读的计算属性"""
            return math.pi * self._radius ** 2
    c = Circle(5)
    print(c.area) # 输出: 78.53981633974483
    # c.area = 100 # 这会报错,因为没有 @area.setter
  3. 属性修改时的“副作用”: 当一个属性被修改时,可能需要触发一些其他的操作,比如更新数据库、发送通知、重新计算相关数据等。

    class LightBulb:
        def __init__(self):
            self._is_on = False
        @property
        def is_on(self):
            return self._is_on
        @is_on.setter
        def is_on(self, value):
            if self._is_on != value:
                if value:
                    print("Light is ON!")
                else:
                    print("Light is OFF.")
                self._is_on = value
    bulb = LightBulb()
    bulb.is_on = True  # 输出: Light is ON!
    bulb.is_on = False # 输出: Light is OFF.
    bulb.is_on = False # 什么也不输出,因为状态没有改变

特性 property() 函数 @property 装饰器
语法 attr = property(fget, fset, fdel) @property
@attr.setter
@attr.deleter
可读性 一般,getter/setter 方法分散 优秀,相关方法聚合在一起
推荐度 可用,但非首选 强烈推荐,现代 Python 的标准做法
核心思想 将方法转换为属性访问接口,实现对对象内部状态的受控访问,同时保持外部接口的简洁性。

property 的核心思想是 “封装”,它让你可以隐藏对象内部的实现细节,只提供一个干净、可控、易于理解的公共接口,这是面向对象编程中非常重要的一个概念。

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