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 教程。