Python __set__ 方法
最后修改于 2025 年 4 月 8 日
本综合指南探讨了 Python 的 __set__
方法,这是描述符中用于自定义属性赋值的特殊方法。我们将涵盖基本用法、类似属性的描述符、验证和实际示例。
基本定义
__set__
方法是 Python 描述符协议的一部分。当属性被赋予实例上的值时,它会被调用。描述符允许自定义属性访问。
主要特点:它接受三个参数 (self, instance, value),不返回任何内容,并在赋值期间调用。它与 __get__
结合使用,并且可以选择性地与 __delete__
结合使用以实现完整的描述符实现。
带有 __set__ 的基本描述符
这是一个简单的描述符实现,展示了 __set__
如何拦截属性赋值。 这演示了基本的描述符模式。
class LoggedAttribute: def __set__(self, instance, value): print(f"Setting value {value} on {instance}") instance.__dict__[self.name] = value def __set_name__(self, owner, name): self.name = name class Person: name = LoggedAttribute() age = LoggedAttribute() p = Person() p.name = "Alice" # Prints "Setting value Alice on <__main__.Person object...>" p.age = 30 # Prints "Setting value 30 on <__main__.Person object...>"
这个 LoggedAttribute
描述符记录了所有对其使用的属性的赋值。 __set_name__
方法捕获属性名称以存储在实例的 __dict__
中。
__set__
方法接收描述符实例、所有者实例和正在分配的值。 它将值存储在实例的命名空间中。
带有 __set__ 的验证属性
__set__
非常适合实现经过验证的属性,这些属性对分配的值强制执行约束。 这是一个年龄验证器示例。
class ValidatedAge: def __set__(self, instance, value): if not isinstance(value, int): raise TypeError("Age must be an integer") if not 0 <= value <= 120: raise ValueError("Age must be between 0 and 120") instance.__dict__['age'] = value class Person: age = ValidatedAge() p = Person() p.age = 25 # Works # p.age = 150 # Raises ValueError # p.age = "25" # Raises TypeError
此描述符验证年龄赋值是否为合理范围内的整数。 无效的赋值会引发带有有用消息的异常。
验证在属性赋值期间透明地发生。 描述符模式使验证逻辑与类分离,同时保持干净的属性访问语法。
类似属性的描述符
带有 __set__
的描述符可以创建具有自定义 get/set 行为的类似属性的属性。 这是一个温度转换器示例。
class Celsius: def __get__(self, instance, owner): return instance._celsius def __set__(self, instance, value): instance._celsius = value instance._fahrenheit = value * 9/5 + 32 class Temperature: celsius = Celsius() def __init__(self, celsius=0): self.celsius = celsius @property def fahrenheit(self): return self._fahrenheit temp = Temperature(100) print(temp.fahrenheit) # 212.0 temp.celsius = 0 print(temp.fahrenheit) # 32.0
每当设置摄氏温度时,此描述符会自动更新华氏温度的等效值。 __set__
方法处理转换和存储。
Temperature
类公开了两种温度刻度,同时仅在内部存储摄氏温度。 描述符保持两种表示形式之间的一致性。
只读描述符
描述符可以通过实现 __set__
来阻止修改,从而使属性成为只读属性。 此示例显示了一个类似常量的属性。
class ReadOnly: def __init__(self, value): self.value = value def __get__(self, instance, owner): return self.value def __set__(self, instance, value): raise AttributeError("Cannot modify read-only attribute") class Configuration: VERSION = ReadOnly("1.0.0") config = Configuration() print(config.VERSION) # "1.0.0" # config.VERSION = "2.0.0" # Raises AttributeError
此描述符在初始化期间存储值,并允许读取但不允许写入。 尝试修改属性会引发 AttributeError
。
__set__
方法完全阻止赋值尝试。 此模式对于初始化后不应更改的常量或配置值很有用。
带有 __set__ 的延迟初始化
描述符可以实现延迟初始化,将计算推迟到第一次访问。 这是一个延迟加载的属性示例。
class LazyProperty: def __init__(self, factory): self.factory = factory self.name = None def __set_name__(self, owner, name): self.name = name def __get__(self, instance, owner): if instance is None: return self value = self.factory(instance) instance.__dict__[self.name] = value return value def __set__(self, instance, value): instance.__dict__[self.name] = value class DataProcessor: def __init__(self, data): self.data = data @LazyProperty def processed_data(self): print("Processing data...") return [x * 2 for x in self.data] processor = DataProcessor([1, 2, 3]) print(processor.processed_data) # Processes and prints [2, 4, 6] print(processor.processed_data) # Uses cached value
此描述符仅在第一次访问时计算值,然后将其缓存。 __set__
允许显式设置以绕过延迟计算。
仅在首次访问属性时才调用工厂函数。 这对于可能并不总是需要的昂贵计算很有用。
最佳实践
- 存储在实例 __dict__ 中: 避免使用直接属性访问导致的无限递归
- 实现 __set_name__: 对于 Python 3.6+,自动获取属性名称
- 考虑线程安全: 如果描述符在多线程代码中使用,则添加锁
- 记录行为: 清楚地记录任何特殊的赋值逻辑
- 对于简单情况,使用 properties: 对于单类使用,首选 @property
资料来源
作者
列出所有 Python 教程。