人人都会AI编程

属性访问控制:__getattr__、__setattr__、__getattribute__

更新时间:2026-07-12

Python 为我们提供了三个魔法方法,让我们可以在读取设置对象属性时插入自定义逻辑,就像在属性访问的必经之路上布下“钩子”。这三个方法分工不同,理解它们的调用时机和区别,是写出灵活且健壮代码的关键。


getattr —— 查不到时才调用

  • 触发时机:当你访问一个不存在的属性时,Python 才会调用 getattr
  • 方法签名getattr(self, name)name 是属性名字符串。
  • 核心作用:提供“默认值”或“动态属性”的机制,避免 AttributeError

实用示例:创建一个对象,访问未定义的属性时返回 None 而不是报错。

class SafeObject:
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)
    
    def __getattr__(self, name):
        return None

obj = SafeObject(x=1, y=2)
print(obj.x)    # 1
print(obj.z)    # None,不会报错
print(obj.abc)  # None

典型应用场景:配置对象的默认值、代理模式的转发(如将未知属性转发给内部对象)。


getattribute —— 不管存不存在都会被调用

  • 触发时机每次访问属性时都会被调用,无论该属性是否存在。这在属性访问流程中是最顶层的拦截点。
  • 方法签名getattribute(self, name)
  • 风险警告:在 getattribute 内部很容易因为不小心再次触发属性访问而导致无限递归。通常需要借助 object.getattribute(self, name) 来获取真正的属性值。

实用示例:记录所有属性访问日志。

class LoggedAccess:
    def __getattribute__(self, name):
        print(f"正在访问属性: {name}")
        # 必须调用基类的 __getattribute__,避免递归
        return super().__getattribute__(name)

obj = LoggedAccess()
obj.x = 10
print(obj.x)
# 输出:
# 正在访问属性: x
# 10

重要提醒:覆盖 getattribute 是高级技巧,大多数情况应优先考虑 getattr@property。一旦使用,务必记住任何属性访问(包括 self.dict)都会触发它,因此要小心处理。


setattr —— 设置属性时调用

  • 触发时机每次给属性赋值时(如 self.name = value)都会调用,无论属性之前是否存在。
  • 方法签名setattr(self, name, value)
  • 核心作用:拦截属性赋值操作,实现校验、类型检查、不可变性控制等。

实用示例:创建一个只读对象,禁止后续修改属性。

class ReadOnly:
    def __init__(self, **kwargs):
        # 绕过 __setattr__ 直接写入实例字典
        self.__dict__.update(kwargs)
    
    def __setattr__(self, name, value):
        raise AttributeError("该对象是只读的,不能修改属性")

obj = ReadOnly(x=5)
print(obj.x)   # 5
obj.x = 10     # 抛出 AttributeError

另一个常见场景:类型强制转换,比如保证某个属性总是整数:

class StrictType:
    def __setattr__(self, name, value):
        if name == 'age' and not isinstance(value, int):
            raise TypeError("age 必须是整数")
        self.__dict__[name] = value

obj = StrictType()
obj.age = 30      # OK
obj.age = '老'    # 抛出 TypeError

注意:在 setattr 内部,必须使用 self.dict[name] = value 来实际存储值,而不能使用 self.name = value,否则会无限递归调用自身。


调用顺序与关系总结

  • 读取属性时getattribute 总是最先被调用。如果它找不到属性(即需要抛出 AttributeError),然后才会轮到 getattr 作为最后的“兜底”尝试。
  • 设置属性时setattr 总是被调用,不论属性是否已存在。
  • 最佳实践
  • 一般优先使用 @property 装饰器来管理单个属性的访问控制,既清晰又安全。
  • 当需要对多个或全部属性做统一拦截时,再考虑 getattr / setattr
  • getattribute 尽量不要乱动,除非你非常清楚自己在做什么。

掌握这三个方法,可以让你设计出更灵活的对象模型,比如动态代理、惰性加载属性、属性校验容器等,在框架开发和底层库编写中尤其有用。