Python __setattr__() 方法

Python __setattr__() 语法

__getattr__() 是 Python 的一个特殊方法(又称 “魔法方法” 或 “双下划线方法”),它用于定义当尝试设置对象某个属性时,其行为是怎样的。

语法:

object.__setattr__(self, name, value)

说明:

__setattr__() 方法接收以下 3 个参数。

  • self(必选) :是一个约定俗成的名称,表示实例对象。
  • name(必选) :是一个字符串,表示要设置的属性的名称。
  • value(必选) :要赋给属性的值。

当使用 obj.attr = value 语句或 setattr(obj, 'attr', value) 函数设置对象的属性时,Python 会自动调用 obj.__setattr__() 方法。

如果 __setattr__() 方法没有定义或没有正确处理属性设置,Python 会使用默认的行为,即在对象的 __dict__ 中设置属性。

注意:在 __setattr__() 方法内部,为了避免无限递归调用 __setattr__() 自身,我们应该使用 object.__setattr__(self, name, value) 或者直接操作 self.__dict__[name] = value 来设置属性。

Python __setattr__() 摘要

属于 Python 魔法方法
使用频率
官方文档 查看
相关方法 __getattr__()__delattr__()

Python __setattr__() 示例

接下来,我们通过几个简单的例子来讲解一下 Python __setattr__() 方法是如何使用的。

示例 1:__setattr__() 基本用法

class User:
    def __init__(self, name, age):
        # 注意:在 __init__() 中直接赋值也会调用 __setattr__()
        # 为了避免递归,通常在 __init__() 中直接操作 __dict__()
        object.__setattr__(self, 'name', name)
        object.__setattr__(self, 'age', age)

    def __setattr__(self, name, value):
        # 调用基类的 __setattr__ 来实际设置属性,避免递归
        object.__setattr__(self, name, value)

user = User('Jack', 20)
print(user.name, user.age)

# 设置存在的属性
user.name = 'Lucy'
print(user.name)

# 设置新的属性
user.city = 'Guangzhou'
print(user.city)

运行结果如下。

Jack 20
Lucy
Guangzhou

分析:

在这个例子中,无论是初始化时对属性的赋值,还是后续对属性的修改或新增,都会触发 __setattr__() 方法。

在 __setattr__() 内部,我们使用了 object.__setattr__(self, name, value) 来调用基类 object 的 __setattr__ 方法,从而安全地设置属性,避免了无限递归。

示例 2:使用 __setattr__() 进行数据验证

class User:
    def __init__(self, username, email):
        self.username = username
        self.email = email

    def __setattr__(self, name, value):
        if name == 'email':
            if '@' not in value or '.' not in value:
                raise ValueError("无效的邮箱格式!")
        elif name == 'username':
            if not isinstance(value, str) or len(value) < 3:
                raise ValueError("用户名必须是字符串且至少 3 个字符!")
        
        # 实际设置属性
        object.__setattr__(self, name, value)

user = User('Alice', 'alice@example.com')

# 尝试设置有效邮箱
user.email = 'new.alice@domain.com'
print(user.email)

# 尝试设置无效邮箱
try:
    user.email = 'invalid-email'
except ValueError as e:
    print(e)

# 尝试设置有效用户名
user.username = 'Bob'
print(user.username)

# 尝试设置无效用户名(数字)
try:
    user.username = 123
except ValueError as e:
    print(e)

# 尝试设置无效用户名(少于 3 个字符)
try:
    user.username = 'ab'
except ValueError as e:
    print(e)

运行结果如下。

new.alice@domain.com
无效的邮箱格式!
Bob
用户名必须是字符串且至少 3 个字符!
用户名必须是字符串且至少 3 个字符!

分析:

通过 __setattr__() 方法,我们可以对属性赋值进行验证,确保数据的有效性。在这个例子中,__setattr__() 方法对 email 和 username 属性进行了验证。只有当值符合预设的条件时,属性才会被设置。否则,会抛出 ValueError 异常。

示例 3:__setattr__() 实现只读属性

class ImmutablePoint:
    def __init__(self, x, y):
        # 在 __init__() 中设置属性,绕过 __setattr__() 的只读逻辑
        object.__setattr__(self, '_x', x)
        object.__setattr__(self, '_y', y)
    
    @property
    def x(self):
        return self._x

    @property
    def y(self):
        return self._y

    def __setattr__(self, name, value):
        # 如果不是在初始化阶段(即 self._x 已经存在),则禁止修改
        if '_x' in self.__dict__:   # 判断属性是否已存在,间接判断是否在初始化阶段
            raise AttributeError(f"属性 '{name}' 是只读的,不能修改")
        else:
            # 允许在初始化阶段设置属性
            object.__setattr__(self, name, value)

point = ImmutablePoint(10, 20)
print(f"初始点: x={point.x}, y={point.y}")

# 尝试修改只读属性
try:
    point.x = 30
except AttributeError as e:
    print(e)

print(f"修改后的点: x={point.x}, y={point.y}")

# 尝试添加新属性
try:
    point.z = 5
except AttributeError as e:
    print(e)

运行结果如下。

初始点: x=10, y=20
属性 'x' 是只读的,不能修改
修改后的点: x=10, y=20
属性 'z' 是只读的,不能修改

分析:

虽然 Python 没有内置的 “只读属性” 机制,但我们可以利用 __setattr__() 来模拟实现。

在这个例子中,我们创建了一个 ImmutablePoint 类。在 __init__() 方法中,我们直接使用 object.__setattr__() 来设置 _x 和 _y,以避免触发自定义的 __setattr__。

在 __setattr__() 中,我们判断 _x 属性是否已经存在(这表示对象已经初始化)。如果已存在,则任何对属性的设置尝试都会引发 AttributeError 异常,从而实现了只读属性的效果。此外,@property 装饰器主要用于提供对 _x 和 _y 的便捷只读访问。

上一篇: __getattr__()

下一篇: __delattr__()

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号