__getattr__和__getattribute__的区别

586 投票
8 回答
225868 浏览
提问于 2025-04-16 01:29

我正在尝试理解什么时候应该定义 __getattr____getattribute__Python 的文档提到 __getattribute__ 适用于新式类。那么什么是新式类呢?

8 个回答

21

这只是一个基于 Ned Batchelder 的解释 的例子。

__getattr__ 的例子:

class Foo(object):
    def __getattr__(self, attr):
        print "looking up", attr
        value = 42
        self.__dict__[attr] = value
        return value

f = Foo()
print f.x 
#output >>> looking up x 42

f.x = 3
print f.x 
#output >>> 3

print ('__getattr__ sets a default value if undefeined OR __getattr__ to define how to handle attributes that are not found')

如果用同样的例子来演示 __getattribute__,你会看到这样的错误信息:>>> RuntimeError: maximum recursion depth exceeded while calling a Python object

244

让我们来看一些简单的例子,了解一下 __getattr____getattribute__ 这两个魔法方法。

__getattr__

当你请求一个还没有定义的属性时,Python 会调用 __getattr__ 方法。在下面的例子中,我的类 Count 没有 __getattr__ 方法。现在在主程序中,当我尝试访问 obj1.myminobj1.mymax 属性时,一切都正常。但当我尝试访问 obj1.mycurrent 属性时,Python 会给我一个错误提示:AttributeError: 'Count' object has no attribute 'mycurrent'

class Count():
    def __init__(self,mymin,mymax):
        self.mymin=mymin
        self.mymax=mymax

obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.mycurrent)  --> AttributeError: 'Count' object has no attribute 'mycurrent'

现在我的类 Count 有了 __getattr__ 方法。当我尝试访问 obj1.mycurrent 属性时,Python 会返回我在 __getattr__ 方法中实现的内容。在我的例子中,每当我尝试调用一个不存在的属性时,Python 会创建这个属性,并将其设置为整数值 0。

class Count:
    def __init__(self,mymin,mymax):
        self.mymin=mymin
        self.mymax=mymax    

    def __getattr__(self, item):
        self.__dict__[item]=0
        return 0

obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.mycurrent1)

__getattribute__

现在我们来看 __getattribute__ 方法。如果你的类中有 __getattribute__ 方法,Python 会对每个属性调用这个方法,无论这个属性是否存在。那么我们为什么需要 __getattribute__ 方法呢?一个很好的理由是,你可以阻止对某些属性的访问,从而提高安全性,下面的例子就说明了这一点。

每当有人尝试访问以 'cur' 开头的属性时,Python 会抛出 AttributeError 异常。否则,它会返回该属性。

class Count:

    def __init__(self,mymin,mymax):
        self.mymin=mymin
        self.mymax=mymax
        self.current=None
   
    def __getattribute__(self, item):
        if item.startswith('cur'):
            raise AttributeError
        return object.__getattribute__(self,item) 
        # or you can use ---return super().__getattribute__(item)

obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.current)

重要提示:为了避免在 __getattribute__ 方法中出现无限递归,它的实现应该始终调用基类中同名的方法来访问所需的属性。例如:object.__getattribute__(self, name)super().__getattribute__(item),而不是 self.__dict__[item]

重要

如果你的类同时包含 getattrgetattribute 这两个魔法方法,那么 __getattribute__ 会先被调用。但如果 __getattribute__ 抛出 AttributeError 异常,那么这个异常会被忽略,接着会调用 __getattr__ 方法。请看下面的例子:

class Count(object):

    def __init__(self,mymin,mymax):
        self.mymin=mymin
        self.mymax=mymax
        self.current=None

    def __getattr__(self, item):
        self.__dict__[item]=0
        return 0

    def __getattribute__(self, item):
        if item.startswith('cur'):
            raise AttributeError
        return object.__getattribute__(self,item)
        # or you can use ---return super().__getattribute__(item)
        # note this class subclass object

obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.current)
680

__getattr____getattribute__ 之间有一个关键的区别:__getattr__ 只有在找不到属性时才会被调用。它非常适合用来处理缺失的属性,可能是你更想用的那个。

__getattribute__ 在查看对象的实际属性之前就会被调用,所以实现起来可能会比较复杂。如果不小心,你可能会陷入无限循环。

新式类是从 object 这个类派生出来的,而旧式类是指在 Python 2.x 中没有明确基类的类。不过,在选择 __getattr____getattribute__ 时,旧式类和新式类的区别并不是最重要的。

你几乎肯定会想用 __getattr__

撰写回答