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 的便捷只读访问。
