飞翔飞翔
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
  • 学习路径
  • 第1章 Python简介

    • Python是什么
    • 安装与运行
    • 交互式解释器
    • 注释与编码规范
  • 第2章 变量与数据类型

    • 变量与对象
    • 整数 int
    • 浮点数 float
    • 复数 complex
    • 布尔值 bool
    • 字符串 str
    • 空值 None
    • 类型转换
  • 第3章 运算符与表达式

    • 算术运算符
    • 比较运算符
    • 赋值运算符
    • 逻辑运算符
    • 位运算符
    • 身份与成员运算符
    • 海象运算符
    • 运算符优先级
  • 第4章 流程控制

    • if 语句
    • if-else 语句
    • if-elif-else 语句
    • match-case 语句
    • 条件表达式(三元运算符)
    • while 循环
    • for 循环
    • range 函数
    • break 与 continue
    • 循环的 else 子句
    • pass 语句
  • 第5章 数据结构

    • 列表创建与索引
    • 列表方法
    • 列表推导式
    • 元组
    • 序列解包
    • 集合
    • 字典创建与访问
    • 字典方法
    • 字典推导式
    • range 对象
  • 第6章 函数

    • 定义函数
    • 位置参数与关键字参数
    • 默认参数
    • 可变参数
    • 解包实参
    • 函数返回值
    • lambda 表达式
    • 文档字符串与注解
    • 作用域与命名空间
    • global 与 nonlocal
  • 第7章 模块与包

    • 模块导入
    • 模块搜索路径
    • 包与相对导入
    • 标准库概览
  • 第8章 文件与输入输出

    • 文件读写
    • 上下文管理器
    • 字符串格式化
    • JSON 与 CSV
  • 第9章 面向对象

    • 类与对象
    • 方法
    • 实例变量与类变量
    • 私有变量
    • 继承
    • 多重继承
    • 魔术方法
    • 属性装饰器
    • 数据类 dataclass
  • 第10章 异常处理

    • 语法错误与异常
    • try-except
    • 异常链与 raise
    • 清理操作
    • 自定义异常
  • 第11章 迭代器与生成器

    • 迭代器协议
    • 生成器
    • 生成器表达式
    • 迭代工具
  • 第12章 高级特性

    • 装饰器
    • 函数式编程
  • 第13章 工程实践

    • 测试与调试
    • 代码质量
    • 虚拟环境

属性装饰器

属性装饰器(@property)将方法调用伪装成属性访问,使得在不影响外部接口的前提下,为数据读写添加计算逻辑、校验规则或访问控制。它是 Python 实现封装的核心工具之一。

@property 基础

在方法前加上 @property 装饰器,即可像访问属性一样读取方法的返回值,无需括号调用。

class Circle:
    def __init__(self, radius):
        self._radius = radius
    
    @property
    def radius(self):
        """半径,只读属性"""
        return self._radius
    
    @property
    def area(self):
        """面积,计算属性"""
        import math
        return math.pi * self._radius ** 2

c = Circle(5)
print(c.radius)    # 5   —— 像属性一样访问
print(c.area)      # 78.54... —— 每次访问都重新计算
# c.radius = 10    # AttributeError: can't set attribute

@property 的本质是将方法转换为描述符(descriptor)。当通过实例访问该名称时,解释器自动调用被装饰的方法并返回结果。由于外部无法直接赋值,这实现了只读属性。

@setter 与 @deleter

为 property 添加写控制,需要定义同名方法并使用 @name.setter 装饰器。同理,@name.deleter 控制删除行为。

class Temperature:
    def __init__(self, celsius=0):
        self._celsius = celsius
    
    @property
    def celsius(self):
        return self._celsius
    
    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError("Temperature below absolute zero is not possible")
        self._celsius = value
    
    @property
    def fahrenheit(self):
        return self._celsius * 9 / 5 + 32
    
    @fahrenheit.setter
    def fahrenheit(self, value):
        self.celsius = (value - 32) * 5 / 9

t = Temperature(25)
print(t.celsius)      # 25
t.celsius = 30        # 通过 setter 赋值
print(t.fahrenheit)   # 86.0

t.fahrenheit = 98.6   # 反向通过华氏度设置
print(t.celsius)      # 37.0

# t.celsius = -300      # ValueError

注意 fahrenheit 的 setter 内部调用了 self.celsius = ...,这会触发 celsius 的 setter,从而复用绝对零度的校验逻辑。这种链式设计避免了重复代码。

如果定义了 setter 但未定义 deleter,尝试 del obj.attr 会抛出 AttributeError。若需要支持删除,应提供 deleter:

class Config:
    def __init__(self):
        self._timeout = 30
    
    @property
    def timeout(self):
        return self._timeout
    
    @timeout.setter
    def timeout(self, value):
        self._timeout = value
    
    @timeout.deleter
    def timeout(self):
        self._timeout = None

c = Config()
del c.timeout
print(c.timeout)   # None

property() 函数形式

@property 装饰器是内置函数 property(fget=None, fset=None, fdel=None, doc=None) 的语法糖。在需要动态创建属性或兼容旧代码时,可以直接使用函数形式。

class Rectangle:
    def __init__(self, width, height):
        self._width = width
        self._height = height
    
    def get_width(self):
        return self._width
    
    def set_width(self, value):
        if value <= 0:
            raise ValueError("width must be positive")
        self._width = value
    
    width = property(get_width, set_width, doc="矩形宽度")

r = Rectangle(4, 5)
print(r.width)     # 4
r.width = 10       # 调用 set_width
print(r.width)     # 10

函数形式在元编程和框架代码中更为常见,因为它允许在运行时组装 getter、setter 和 deleter。

计算属性与缓存

计算属性在每次访问时执行方法体。如果计算成本较高且结果不会频繁变化,可以结合缓存策略优化。

class Polygon:
    def __init__(self, sides):
        self._sides = list(sides)
        self._perimeter = None
    
    @property
    def sides(self):
        return self._sides
    
    @sides.setter
    def sides(self, value):
        self._sides = list(value)
        self._perimeter = None   # 缓存失效
    
    @property
    def perimeter(self):
        if self._perimeter is None:
            self._perimeter = sum(self._sides)
        return self._perimeter

p = Polygon([3, 4, 5])
print(p.perimeter)   # 12 —— 首次计算并缓存
print(p.perimeter)   # 12 —— 直接返回缓存值
p.sides = [5, 5, 5]
print(p.perimeter)   # 15 —— 缓存已失效,重新计算

Python 3.8+ 提供了 functools.cached_property,自动处理缓存和失效,但要求实例是可哈希的或缓存存储在实例上。对于更复杂的场景,手动管理缓存往往更清晰可控。

只读属性的实现模式

实现只读属性有多种方式,应根据具体需求选择:

模式一:纯 property,无 setter

class ImmutablePoint:
    def __init__(self, x, y):
        self._x = x
        self._y = y
    
    @property
    def x(self):
        return self._x
    
    @property
    def y(self):
        return self._y

模式二:setter 中抛出异常

class IDCard:
    def __init__(self, id_number):
        self._id = id_number
    
    @property
    def id(self):
        return self._id
    
    @id.setter
    def id(self, value):
        raise AttributeError("ID is read-only")

模式三:使用 setattr 拦截

class Frozen:
    def __init__(self, value):
        super().__setattr__('_locked', False)
        self.value = value
        super().__setattr__('_locked', True)
    
    def __setattr__(self, name, value):
        if getattr(self, '_locked', False):
            raise AttributeError(f"Cannot modify {name}")
        super().__setattr__(name, value)

模式一最简洁,是首选方案。模式二在需要明确告知调用者"此属性不可写"时使用。模式三用于需要全面冻结实例的场景。

property 与描述符的关系

property 是 Python 描述符协议的高层次封装。描述符是实现了 __get__、__set__ 或 __delete__ 方法的类。property 内部正是通过这三个方法实现属性拦截的。

class Validator:
    """自定义描述符,功能类似 property + setter"""
    
    def __init__(self, min_value, max_value):
        self.min_value = min_value
        self.max_value = max_value
        self.name = None
    
    def __set_name__(self, owner, name):
        self.name = name
        self.storage_name = f'_{name}'
    
    def __get__(self, instance, owner):
        if instance is None:
            return self
        return getattr(instance, self.storage_name, None)
    
    def __set__(self, instance, value):
        if not (self.min_value <= value <= self.max_value):
            raise ValueError(f"{self.name} must be in [{self.min_value}, {self.max_value}]")
        setattr(instance, self.storage_name, value)

class Person:
    age = Validator(0, 150)
    score = Validator(0, 100)
    
    def __init__(self, age, score):
        self.age = age
        self.score = score

p = Person(25, 88)
print(p.age)       # 25
# p.age = 200      # ValueError: age must be in [0, 150]

当 @property 无法满足需求(例如需要在多个属性间复用同一套逻辑)时,自定义描述符是更优雅的解决方案。Python 3.6 引入的 __set_name__ 让描述符能够自动获知被绑定的属性名,进一步简化了代码。

上一页
魔术方法
下一页
数据类 dataclass