【问题标题】:Difference between __getattr__ vs __getattribute____getattr__ 与 __getattribute__ 之间的区别
【发布时间】:2010-07-19 02:11:19
【问题描述】:

我试图了解何时使用__getattr____getattribute__documentation 提到 __getattribute__ 适用于新式类。什么是新式班?

【问题讨论】:

标签: python getattr getattribute


【解决方案1】:

__getattr____getattribute__ 之间的主要区别在于,__getattr__ 仅在没有以通常方式找到属性时才被调用。这对于实现缺失属性的回退很有用,并且可能是您想要的两个之一。

__getattribute__ 在查看对象的实际属性之前被调用,因此正确实现可能很棘手。您很容易陷入无限递归。

新式类派生自object,旧式类是 Python 2.x 中没有显式基类的类。但是在__getattr____getattribute__ 之间进行选择时,旧式和新式类之间的区别并不重要。

你几乎肯定想要__getattr__

【讨论】:

  • 我可以在同一个类中实现它们吗?如果可以,实现两者的目的是什么?
  • @Alcott:你可以同时实现它们,但我不确定你为什么会这样做。每次访问都会调用__getattribute____getattr__ 将在__getattribute__ 引发AttributeError 的时候调用。为什么不将所有内容合二为一?
  • @NedBatchelder,如果你想(有条件地)覆盖对现有方法的调用,你会想使用__getattribute__
  • "为了避免这个方法无限递归,它的实现应该总是调用同名的基类方法来访问它需要的任何属性,例如object.__getattribute__(self, name) 。”
  • 请注意:要访问__getattribute__ 中的变量而不调用无限递归,必须调用super().__getattribute__(item)
【解决方案2】:

让我们看看__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)

【讨论】:

  • 我不确定覆盖__getattribute__ 的具体用例是什么,但肯定不是。因为根据您的示例,如果对象的__dict__ 中不存在该属性,那么您在__getattribute__ 中所做的一切就是引发AttributeError 异常;但您并不真正需要它,因为这是 __getattribute__ 的默认实现,而事实上 __getattr__ 正是您作为后备机制所需要的。
  • @Rohit current 是在 Count 的实例上定义的(请参阅 __init__),因此如果该属性不存在,只需提高 AttributeError 就不会发生什么 - 它推迟了到__getattr__,用于所有以“cur”开头的名称,包括current,还有curiouscurly ...
【解决方案3】:

这只是基于Ned Batchelder's explanation 的示例。

__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

【讨论】:

  • 实际上,这很糟糕。 现实世界中的__getattr__() 实现只接受有限的一组有效属性名,方法是针对无效属性名引发AttributeError,从而避免@ 987654322@。此示例无条件地接受 all 属性名称为有效 - __getattr__() 的奇怪(并且坦率地说容易出错)滥用。如果您想像本例中那样“完全控制”属性创建,则需要__getattribute__()
  • @CecilCurry:您链接到的所有问题都涉及隐式返回 None 而不是一个值,这个答案没有这样做。接受所有属性名称有什么问题?与defaultdict 相同。
  • 问题是__getattr__会被调用之前超类查找。这对于object 的直接子类来说是可以的,因为您真正关心的唯一方法是忽略实例的魔术方法,但是对于任何更复杂的继承结构,您完全删除了从父类继承任何东西的能力。
  • @Simon K Bhatta4ya,您最后的打印声明是评论。正确的?这是一条很长的线并且阅读起来很乏味(一个人必须在右侧滚动很多)。在代码部分之后放置这一行怎么样?或者如果你想把它放在代码部分,我认为它最好分成两行。
【解决方案4】:
  • getattribute:用于从实例中检索属性。它通过使用点表示法或 getattr() 内置函数来捕获访问实例属性的每一次尝试。
  • getattr:在对象中找不到属性时作为最后一个资源执行。您可以选择返回默认值或引发 AttributeError。

回到 __getattribute__ 函数;如果默认实现没有被覆盖;执行该方法时会进行以下检查:

  • 检查MRO链(方法对象解析)的任意类中是否定义了同名的描述符(属性名)
  • 然后查看实例的命名空间
  • 然后查看类命名空间
  • 然后进入每个基地的命名空间等等。
  • 最后,如果没有找到,默认实现调用实例的回退 getattr() 方法,并引发 AttributeError 异常作为默认实现。

这是 object.__getattribute__ 方法的实际实现

.. c:function:: PyObject* PyObject_GenericGetAttr(PyObject *o, PyObject *name) 通用属性 getter 函数,用于 放入类型对象的 tp_getattro 槽中。它寻找一个 对象的 MRO 中的类字典中的描述符 作为对象的 :attr:~object.dict 中的属性(如果 当下)。如 :ref:descriptors 中所述,数据描述符采用 优先于实例属性,而非数据描述符 不。否则,会引发 :exc:AttributeError 。

【讨论】:

    【解决方案5】:

    我发现没有人提到这个区别:

    __getattribute__ 有一个默认实现,但__getattr__ 没有。

    class A:
        pass
    a = A()
    a.__getattr__ # error
    a.__getattribute__ # return a method-wrapper
    

    这个意思很明确:因为__getattribute__有默认实现,而__getattr__没有,显然python鼓励用户实现__getattr__

    【讨论】:

    • python2没有默认的getattribute吗?
    • @Simplecode 我说的是python 3。我不知道代码在python 2中的行为。
    【解决方案6】:

    在阅读 Beazley & Jones PCB 时,我偶然发现了一个明确而实用的 __getattr__ 用例,它有助于回答 OP 问题的“何时”部分。从书中:

    __getattr__() 方法有点像属性查找的包罗万象。如果代码尝试访问不存在的属性,就会调用该方法。”我们从上述答案中知道这一点,但在 PCB 配方 8.15 中,此功能用于实现委托设计模式。如果对象 A 有一个属性对象 B 实现了对象 A 想要委托给的许多方法,而不是重新定义对象 A 中的所有对象 B 的方法只是为了调用对象 B 的方法,定义一个 __getattr__() 方法如下:

    def __getattr__(self, name):
        return getattr(self._b, name)
    

    其中 _b 是对象 A 的属性名称,即对象 B。当在对象 A 上调用对象 B 上定义的方法时,将在查找链的末尾调用 __getattr__ 方法。这也会使代码更简洁,因为您没有为委托给另一个对象而定义的方法列表。

    【讨论】:

      【解决方案7】:

      新样式类继承自object,或从另一个新样式类:

      class SomeObject(object):
          pass
      
      class SubObject(SomeObject):
          pass
      

      旧式类不会:

      class SomeObject:
          pass
      

      这仅适用于 Python 2 - 在 Python 3 中,以上所有内容都将创建新样式的类。

      有关详细信息,请参阅 9. Classes(Python 教程)、NewClassVsClassicClassWhat is the difference between old style and new style classes in Python?

      【讨论】:

        【解决方案8】:

        新式类是“对象”的子类(直接或间接)。除了__init__ 之外,它们还有一个__new__ 类方法,并且具有更合理的低级行为。

        通常,您需要覆盖 __getattr__(如果您要覆盖其中任何一个),否则您将很难在方法中支持“self.foo”语法。

        额外信息:http://www.devx.com/opensource/Article/31482/0/page/4

        【讨论】:

          猜你喜欢
          • 2011-05-16
          • 1970-01-01
          • 1970-01-01
          • 2017-12-12
          • 2020-01-04
          • 1970-01-01
          • 1970-01-01
          • 2014-11-06
          • 1970-01-01
          相关资源
          最近更新 更多