ZetCode

Python __setattr__ 方法

最后修改于 2025 年 4 月 8 日

这份全面的指南探讨了 Python 的 __setattr__ 方法,这个特殊方法控制属性赋值。我们将涵盖基本用法、属性验证、动态属性和实践示例。

基本定义

当尝试对对象进行属性赋值时,会调用 __setattr__ 方法。它拦截所有属性赋值,包括 __init__ 中的赋值。

主要特点:它接收属性名称和值作为参数,必须手动处理属性存储,并且可以阻止或转换属性赋值。 它为所有属性赋值调用。

基本 __setattr__ 实现

这是一个简单的实现,展示了 __setattr__ 如何拦截属性赋值。 它演示了基本结构和要求。

basic_setattr.py
class Person:
    def __setattr__(self, name, value):
        print(f"Setting attribute {name} to {value}")
        super().__setattr__(name, value)

p = Person()
p.name = "Alice"
p.age = 30
print(p.name, p.age)

此示例显示了基本模式:打印消息,然后使用 super() 委托给父类的 __setattr__。 如果没有此委托,属性将不会被存储。

输出显示在存储之前拦截了两个属性赋值。 这是更高级属性控制的基础。

使用 __setattr__ 进行属性验证

__setattr__ 可以在允许设置属性值之前对其进行验证,从而强制执行业务规则或类型约束。

validation.py
class Temperature:
    def __setattr__(self, name, value):
        if name == "celsius":
            if not isinstance(value, (int, float)):
                raise TypeError("Temperature must be numeric")
            if value < -273.15:
                raise ValueError("Temperature below absolute zero")
        super().__setattr__(name, value)

temp = Temperature()
temp.celsius = 25  # Valid
# temp.celsius = -300  # Raises ValueError
# temp.celsius = "hot"  # Raises TypeError

这个温度类验证摄氏度值是否为数字且高于绝对零度。 无效的赋值会在存储发生之前引发异常。

验证发生在调用父类的 __setattr__ 之前,从而防止无效状态。 此模式对于领域模型很有用。

只读属性

__setattr__ 可以通过防止初始赋值后修改某些属性,使其成为只读属性。

readonly.py
class Constants:
    def __init__(self):
        super().__setattr__("_values", {})
        self._values["PI"] = 3.14159
    
    def __setattr__(self, name, value):
        if name in self._values:
            raise AttributeError(f"Cannot modify {name}")
        super().__setattr__(name, value)

const = Constants()
print(const._values["PI"])  # 3.14159
# const.PI = 3.14  # Raises AttributeError
const.E = 2.71828  # Allowed

这个常量类将受保护的值存储在字典中,并防止对其进行修改。 仍然可以正常添加新属性。

技巧是在 __init__ 中使用 super().__setattr__ 来绕过自定义的 __setattr__ 以进行初始设置。

动态属性创建

__setattr__ 可以动态地转换属性值或在赋值发生时创建派生属性。

dynamic.py
class CaseInsensitiveDict:
    def __setattr__(self, name, value):
        lower_name = name.lower()
        if lower_name == "data":
            super().__setattr__("data", {})
        else:
            self.data[lower_name] = value
    
    def __getattr__(self, name):
        return self.data.get(name.lower())

d = CaseInsensitiveDict()
d.Color = "blue"
d.COLOR = "red"
print(d.color)  # "red" (last assignment wins)

这个类似字典的对象以不区分大小写的方式存储属性。 所有赋值都使用小写键进入数据字典。

请注意对“data”属性本身的特殊处理,需要正常存储该属性以避免无限递归。

阻止属性创建

__setattr__ 可以完全阻止新属性的创建,使对象严格遵循预定义的模式。

prevent.py
class StrictPerson:
    __slots__ = ("name", "age")
    
    def __setattr__(self, name, value):
        if name not in self.__slots__:
            raise AttributeError(f"Cannot add attribute {name}")
        super().__setattr__(name, value)

p = StrictPerson()
p.name = "Bob"  # Allowed
p.age = 40      # Allowed
# p.email = "bob@example.com"  # Raises AttributeError

此类将 __slots____setattr__ 结合使用,以创建一个严格控制的对象。 只能设置预定义的属性。

__slots__ 声明提供内存效率,而 __setattr__ 在运行时强制执行模式。

最佳实践

资料来源

作者

我的名字是 Jan Bodnar,我是一位充满热情的程序员,拥有丰富的编程经验。 我从 2007 年开始撰写编程文章。 迄今为止,我已经撰写了超过 1,400 篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 Python 教程