ZetCode

Python __set__ 方法

最后修改于 2025 年 4 月 8 日

本综合指南探讨了 Python 的 __set__ 方法,这是描述符中用于自定义属性赋值的特殊方法。我们将涵盖基本用法、类似属性的描述符、验证和实际示例。

基本定义

__set__ 方法是 Python 描述符协议的一部分。当属性被赋予实例上的值时,它会被调用。描述符允许自定义属性访问。

主要特点:它接受三个参数 (self, instance, value),不返回任何内容,并在赋值期间调用。它与 __get__ 结合使用,并且可以选择性地与 __delete__ 结合使用以实现完整的描述符实现。

带有 __set__ 的基本描述符

这是一个简单的描述符实现,展示了 __set__ 如何拦截属性赋值。 这演示了基本的描述符模式。

basic_set.py
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__ 非常适合实现经过验证的属性,这些属性对分配的值强制执行约束。 这是一个年龄验证器示例。

validation.py
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 行为的类似属性的属性。 这是一个温度转换器示例。

property_like.py
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__ 来阻止修改,从而使属性成为只读属性。 此示例显示了一个类似常量的属性。

readonly.py
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__ 的延迟初始化

描述符可以实现延迟初始化,将计算推迟到第一次访问。 这是一个延迟加载的属性示例。

lazy.py
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__ 允许显式设置以绕过延迟计算。

仅在首次访问属性时才调用工厂函数。 这对于可能并不总是需要的昂贵计算很有用。

最佳实践

资料来源

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。 自 2007 年以来,我一直在撰写编程文章。 迄今为止,我撰写了 1,400 多篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 Python 教程