ZetCode

Python __getattribute__ 方法

最后修改于 2025 年 4 月 8 日

本综合指南探讨了 Python 的 __getattribute__ 方法,这是一种控制属性访问的特殊方法。我们将涵盖基本用法、继承、描述符、属性和实际示例。

基本定义

每次尝试访问对象的属性时,都会无条件调用 __getattribute__ 方法。它拦截所有属性查找,包括方法调用和实例变量访问。

主要特点:它必须接受 self 和属性名称作为参数,返回属性值,并为缺失的属性引发 AttributeError。它的优先级高于 __getattr__

基本的 __getattribute__ 实现

这是一个简单的实现,展示了 __getattribute__ 如何拦截所有属性访问。它演示了基本结构和所需的行为。

basic_getattribute.py
class Logger:
    def __init__(self, name):
        self.name = name
    
    def __getattribute__(self, attr):
        print(f"Accessing attribute '{attr}'")
        return super().__getattribute__(attr)

obj = Logger("test")
print(obj.name)  # Logs access and prints "test"
# obj.missing     # Logs access and raises AttributeError

此示例记录每次属性访问。super().__getattribute__ 调用至关重要 - 它使用父类的方法执行实际的属性查找。

如果不调用父类的实现,您将创建一个无限递归,因为每次属性访问(包括对 super 的访问)都会再次触发 __getattribute__

属性访问控制

__getattribute__ 可以通过在允许访问敏感属性之前验证请求来实现属性访问控制。

access_control.py
class SecureData:
    def __init__(self):
        self.public = "Open data"
        self._secret = "Confidential"
    
    def __getattribute__(self, attr):
        if attr.startswith('_'):
            raise AttributeError(f"Access to '{attr}' is restricted")
        return super().__getattribute__(attr)

data = SecureData()
print(data.public)  # Works
# print(data._secret)  # Raises AttributeError

此类通过引发 AttributeError 来阻止访问任何以下划线开头的属性。此模式对于强制执行对私有属性的访问限制非常有用。

请注意,这并不会使属性真正私有 - 仍然可以通过 object.__getattribute__(instance, '_secret') 或通过实例的 __dict__ 访问它们。

使用 __getattribute__ 的虚拟属性

__getattribute__ 可以创建虚拟属性,这些属性不作为实例变量存在,而是按需计算的。

virtual_attributes.py
class Circle:
    def __init__(self, radius):
        self.radius = radius
    
    def __getattribute__(self, attr):
        if attr == 'area':
            import math
            r = super().__getattribute__('radius')
            return math.pi * r ** 2
        return super().__getattribute__(attr)

c = Circle(5)
print(c.area)  # Computes and returns 78.53981633974483
print(c.radius)  # Returns stored value 5

当访问时,此类计算面积是动态的,而没有将其存储为实例变量。半径仍然正常存储和访问。

对于简单的计算属性,Python 的 @property 装饰器通常更简洁。__getattribute__ 更适合更复杂的情况,或者当您需要动态处理许多虚拟属性时。

调试属性访问

__getattribute__ 可用于调试属性访问模式,帮助识别性能瓶颈或意外的属性使用。

debugging.py
class DebugAttributes:
    def __init__(self):
        self.x = 10
        self.y = 20
    
    def __getattribute__(self, attr):
        import inspect
        caller = inspect.currentframe().f_back
        print(f"Attribute '{attr}' accessed from {caller.f_code.co_filename}:{caller.f_lineno}")
        return super().__getattribute__(attr)

dbg = DebugAttributes()
dbg.x + dbg.y  # Logs both accesses with locations

此类不仅记录访问了哪些属性,还记录了访问的起始代码位置。inspect 模块提供调用方信息。

这项技术对于调试复杂的系统非常有用,在这些系统中,属性访问模式不明确,或者在跟踪由于过多的属性查找而导致的性能问题时。

与 __setattr__ 结合使用

__getattribute__ 通常与 __setattr__ 一起工作,以创建完全受控的属性访问,实现只读属性或验证。

combined.py
class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius
    
    def __getattribute__(self, attr):
        if attr == 'fahrenheit':
            c = super().__getattribute__('celsius')
            return c * 9/5 + 32
        return super().__getattribute__(attr)
    
    def __setattr__(self, attr, value):
        if attr == 'fahrenheit':
            self.celsius = (value - 32) * 5/9
        else:
            super().__setattr__(attr, value)

temp = Temperature(0)
print(temp.fahrenheit)  # 32.0
temp.fahrenheit = 212
print(temp.celsius)     # 100.0

此类以摄氏度维护温度,但提供虚拟的华氏度属性,这些属性在访问时进行转换,并在设置时更新摄氏度值。获取和设置操作都被拦截。

该模式演示了如何在同一基础数据的多个表示形式之间保持数据一致性,同时提供干净的界面。

最佳实践

资料来源

作者

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

列出所有 Python 教程