文章目录

Python 元编程:__getattr__ 与 __setattr__ 方法

发布于 2026-04-06 07:12:49 · 浏览 10 次 · 评论 0 条

Python 元编程:getattrsetattr 方法

在 Python 中,有一种编程技巧让你能在运行时动态修改对象的行为,这就是元编程。而 __getattr____setattr__ 是元编程中最常用、最实用的两个魔法方法。掌握它们,你就能写出更灵活、更优雅的代码。


这两个方法到底是干什么的

当你想访问一个对象不存在的属性时,Python 默认会抛出 AttributeError。但如果你定义了 __getattr__ 方法,Python 就会调用它,而不是报错。同样的道理,当你想给一个属性赋值时,__setattr__ 会在赋值之前拦截这次操作。

简单来说:

  • __getattr__ 负责处理「读」的操作
  • __setattr__ 负责处理「写」的操作

这两个方法让你可以在属性访问和赋值时插入自定义逻辑,实现诸如懒加载、属性验证、动态代理等功能。


getattr:优雅地处理缺失属性

基本语法

def __getattr__(self, name):
    # 当访问 self.name 且该属性不存在时调用
    # 必须返回某个值或抛出 AttributeError
    pass

关键点:只有当属性不存在时,这个方法才会被调用。如果你显式定义了一个属性,这个方法不会触发。

实用案例:懒加载数据库连接

class DatabaseManager:
    def __init__(self):
        self._connections = {}

    def __getattr__(self, name):
        if name.startswith('conn_'):
            db_name = name[5:]
            if db_name not in self._connections:
                # 第一次访问时才建立连接
                self._connections[db_name] = f"连接:{db_name}"
            return self._connections[db_name]
        raise AttributeError(f"'{self.__class__.__name__}' 对象没有属性 '{name}'")

# 使用示例
db = DatabaseManager()
print(db.conn_mysql)   # 输出:连接:mysql(自动创建)
print(db.conn_mysql)   # 直接返回已有连接(复用)

在这个例子中,conn_mysql 属性并不存在于对象中,但通过 __getattr__,我们实现了按需创建连接池的效果。

实用案例:动态属性映射

class APIResponse:
    def __init__(self, data):
        self._raw_data = data

    def __getattr__(self, name):
        if name in self._raw_data:
            return self._raw_data[name]
        return None

# 扁平化访问嵌套数据
response = APIResponse({"user": {"name": "张三", "age": 28}})
print(response.user)    # 输出:{'name': '张三', 'age': 28}
print(response.user_name)  # 输出:张三(自动嵌套访问)

setattr:掌控属性赋值全过程

基本语法

def __setattr__(self, name, value):
    # 当给 self.name 赋值时调用
    # 通常需要在最后执行实际的赋值操作
    self.__dict__[name] = value

关键点:在 __setattr__ 中直接写 self.name = value 会导致递归调用。你需要通过 self.__dict__[name] = value 来绕过递归。

实用案例:属性类型检查

class Config:
    def __init__(self):
        # 使用 __dict__ 绕过 __setattr__ 的拦截
        self.__dict__.update({
            '_attrs': {'port': int, 'host': str, 'debug': bool}
        })

    def __setattr__(self, name, value):
        if name in self._attrs:
            expected_type = self._attrs[name]
            if not isinstance(value, expected_type):
                raise TypeError(f"{name} 必须是 {expected_type.__name__} 类型")
        self.__dict__[name] = value

# 使用示例
config = Config()
config.port = 8080       # 正常
config.debug = True      # 正常
config.port = "abc"      # 抛出 TypeError

实用案例:只读属性与不可变对象

class ImmutablePoint:
    def __init__(self, x, y):
        self.__dict__['_values'] = {'x': x, 'y': y}

    def __setattr__(self, name, value):
        if hasattr(self, '_values'):
            if name in self._values:
                raise AttributeError(f"'{self.__class__.__name__}' 的属性是只读的")
        self.__dict__[name] = value

    def __getattr__(self, name):
        if name in self._values:
            return self._values[name]
        raise AttributeError(f"'{self.__class__.__name__}' 对象没有属性 '{name}'")

p = ImmutablePoint(3, 4)
print(p.x, p.y)   # 输出:3 4
p.x = 5           # 抛出 AttributeError

两者配合:实现完整的属性拦截

class ObservedObject:
    def __init__(self):
        self.__dict__['_changes'] = []
        self.__dict__['_data'] = {}

    def __getattr__(self, name):
        if name.startswith('_'):
            raise AttributeError(f"私有属性 '{name}' 不可访问")
        return self._data.get(name, None)

    def __setattr__(self, name, value):
        if name.startswith('_'):
            self.__dict__[name] = value
            return
        old_value = self._data.get(name, '<未定义>')
        self._changes.append(f"'{name}' 从 {old_value} 变为 {value}")
        self._data[name] = value

# 使用示例
obj = ObservedObject()
obj.name = "初始值"
obj.name = "修改后"
print(obj._changes)  # 输出:["'name' 从 <未定义> 变为 初始值", "'name' 从 初始值 变为 修改后"]

这个模式常用于数据绑定、状态追踪、ORM 框架等场景。


重要注意事项

递归陷阱:在 __setattr__ 中,绝对不要使用 self.name = valueself.__dict__['name'] = value 以外的赋值方式,否则会导致无限递归。

性能考量:这两个方法会在每次属性访问时被调用,如果你的类需要频繁访问大量属性,使用这两个方法会带来额外的开销。评估是否真的需要这种灵活性,再决定是否使用。

异常处理__getattr__ 方法中,如果你不希望拦截某个属性,必须抛出 AttributeError。返回 None 或其他值会静默吞掉这个异常,可能导致难以调试的问题。

__getattribute__ 的区别__getattribute__ 会在所有属性访问时调用(包括已存在的属性),而 __getattr__ 只在属性不存在时调用。后者更安全、更可控。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文