在Python中getattribute和setattribute让我感到困惑。

14
我想知道如果我有这样的类:
class TestClass(object):
    def __init__(self):
        self.a = 20
        self.b = 30
    
obj = TestClass()

当我写obj.a时,以下哪个会首先被调用?

  • TestClass.__getattribute__("a")
  • TestClass.__getattr__("a")
  • getattr(obj, "a")
  • obj.__dict__['a']

我对setattr也有类似的问题。

根据Python 2.7 docs

object._getattr_(self, name)

当属性查找在通常的位置(即它不是实例属性,也没有在self的类树中找到)中未找到属性时调用。name是属性名称。此方法应返回(计算的)属性值或引发AttributeError异常。

它说“在通常的位置中未找到[...]”。什么是“通常的位置”?我想知道何时调用__getattr__

另外,__getattr____getattribute__之间有什么区别?

有人能给我一个使用所有这些方法的例子吗?


我无法理解这个问题。 - LtWorf
你提供的三个选项都不存在。你是不是想说 __getattribute__('a')__getattr__('a')__dict__['a'] - Eric
如果你想知道这三个中哪一个首先被调用?它们不会在同一次调用中同时被称为IIRC,而是在不同的情况下被调用... 请参见http://docs.python.org/2/reference/datamodel.html。 - Torxed
4个回答

47

这有点复杂。如果您请求对象的属性,Python将按以下顺序进行检查。

首先,Python将检查对象的类是否具有__getattribute__方法。如果没有定义一个,它将继承object.__getattribute__,该方法实现了其他查找属性值的方式。

下一个检查是在对象的类的__dict__中进行的。但是,即使在此处找到了值,它也可能不是属性查找的结果!只有“数据描述符”会优先于此处找到的内容。最常见的数据描述符是property对象,它是一个包装器,包装了每次访问属性时都会调用的函数。您可以使用装饰器创建属性:

class foo(object):
    @property
    def myAttr(self):
        return 2

在该类中,myAttr是一个数据描述符。这意味着它通过具有__get____set__方法来实现描述符协议。一个property就是一个数据描述符。
如果该类的__dict__中没有请求的名称,object.__getattribute__会通过其基类(按照MRO)搜索是否继承了该名称。继承的数据描述符与对象类中的数据描述符同样起作用。
如果找到了数据描述符,则调用其__get__方法,并将返回值成为属性查找的值。如果找到了一个不是数据描述符的对象,则将其暂存,但并不立即返回。
接下来,检查对象自己的__dict__以查找属性。这是大多数普通成员变量所在的位置。
如果对象的__dict__中没有任何内容,但在类(或基类)的较早搜索中找到了一些不是数据描述符的东西,则它们优先级更高。普通的类变量将简单地返回,但"非数据描述符"将获得更多处理。
非数据描述符是一个具有__get__方法但没有__set__方法的对象。最常见的非数据描述符种类是函数,当作为对象的非数据描述符访问时,它们成为绑定方法(这就是Python如何自动将对象作为第一个参数传递的)。将调用描述符的__get__方法,并将返回值作为属性查找的结果。
最后,如果前面的检查都没有成功,则将调用__getattr__(如果存在)。
以下是一些使用优先级逐步增加的属性访问机制来覆盖其父类行为的类:
class O1(object):
    def __getattr__(self, name):
        return "__getattr__ has the lowest priority to find {}".format(name)

class O2(O1):
    var = "Class variables and non-data descriptors are low priority"
    def method(self): # functions are non-data descriptors
        return self.var

class O3(O2):
    def __init__(self):
        self.var = "instance variables have medium priority"
        self.method = lambda: self.var # doesn't recieve self as arg

class O4(O3):
    @property # this decorator makes this instancevar into a data descriptor
    def var(self):
        return "Data descriptors (such as properties) are high priority"

    @var.setter # I'll let O3's constructor set a value in __dict__
    def var(self, value):
        self.__dict__["var"]  = value # but I know it will be ignored

class O5(O4):
    def __getattribute__(self, name):
        if name in ("magic", "method", "__dict__"): # for a few names
            return super(O5, self).__getattribute__(name) # use normal access
        
        return "__getattribute__ has the highest priority for {}".format(name)

以下是类的演示:

O1 (__getattr__):

>>> o1 = O1()
>>> o1.var
'__getattr__ has the lowest priority to find var'

O2(类变量和非数据描述符):

>>> o2 = O2()
>>> o2.var
'Class variables and non-data descriptors are low priority'
>>> o2.method
<bound method O2.method of <__main__.O2 object at 0x000000000338CD30>>
>>> o2.method()
'Class variables and non-data descriptors are low priority'

O3(实例变量,包括本地重写的方法):

>>> o3 = O3()
>>> o3.method
<function O3.__init__.<locals>.<lambda> at 0x00000000034AAEA0>
>>> o3.method()
'instance variables have medium priority'
>>> o3.var
'instance variables have medium priority'

O4(数据描述符,使用property装饰器):
>>> o4 = O4()
>>> o4.method()
'Data descriptors (such as properties) are high priority'
>>> o4.var
'Data descriptors (such as properties) are high priority'
>>> o4.__dict__["var"]
'instance variables have medium priority'

O5 (__getattribute__):

>>> o5 = O5()
>>> o5.method
<function O3.__init__.<locals>.<lambda> at 0x0000000003428EA0>
>>> o5.method()
'__getattribute__ has the highest priority for var'
>>> o5.__dict__["var"]
'instance variables have medium priority'
>>> o5.magic
'__getattr__ has the lowest priority to find magic'

你能给我一个像Torx所给出的示例代码,然后对每个函数使用Print“function name”raise AttributeError,以便我可以看到事件链吗? - user196264097
这有点棘手。任何给定属性查找只会调用一个函数。哪个函数取决于我上面详细说明的优先级。我想我可能在你发布请求时正在编辑,所以请再次查看答案,它可能会更清晰。 - Blckknght
我正在尝试修改后的Totx代码,链接在这里http://pastebin.com/rtrpCGQy。我可以首先进入`Getattribute`,然后进入`getattr`,然后就会出现错误。为什么我没有进入`__dict__`呢? - user196264097
@user9:我已经更新了一堆示例代码和不同类型成员访问的演示。每个类都覆盖了前一个类的行为。 - Blckknght
设置属性怎么样?我该如何让Python查找是否有具有该名称的属性,如果没有,则调用__setattr__ - Nearoo
给属性赋值的逻辑比查找要简单一些。有三个步骤。首先检查类中是否存在__setattr__方法。如果存在,则始终调用该方法。如果不存在,则第二步是检查属性是否描述了类中的数据描述符(或继承的基类)。如果找到数据描述符,则调用其__set__方法。非数据描述符和其他类变量将被忽略(它们将被遮蔽)。如果前面两种情况都没有命中,则将值简单地分配到实例的__dict__中。 - Blckknght

1
class test():
    def __init__(self):
        self.a = 1

    def __getattribute__(self, attr):
        print 'Getattribute:',attr

    def __getattr__(self, attr):
        print 'GetAttr:',attr

    def __dict__(self, attr):
        print 'Dict:',attr

    def __call__(self, args=None):
        print 'Called:',args

    def __getitem__(self, attr):
        print 'GetItem:',attr

    def __get__(self, instance, owner):
        print 'Get:',instance,owner

    def __int__(self):
        print 'Int'



x = test()
print x.a

以上内容都不会被调用。

[root@faparch doxid]# python -m trace --trace test_dict.py
 --- modulename: test_dict, funcname: <module>
test_dict.py(1): class test():
 --- modulename: test_dict, funcname: test
test_dict.py(1): class test():
test_dict.py(2):    def __init__(self):
test_dict.py(5):    def __getattribute__(self, attr):
test_dict.py(8):    def __getattr__(self, attr):
test_dict.py(11):   def __dict__(self, attr):
test_dict.py(14):   def __call__(self, args=None):
test_dict.py(17):   def __getitem__(self, attr):
test_dict.py(20):   def __get__(self, instance, owner):
test_dict.py(23):   def __int__(self):
test_dict.py(28): x = test()
 --- modulename: test_dict, funcname: __init__
test_dict.py(3):        self.a = 1
test_dict.py(29): print x.a
1
 --- modulename: trace, funcname: _unsettrace
trace.py(80):         sys.settrace(None)

你可能需要查看:http://docs.python.org/2/library/numbers.html#numbers.Number 最有可能需要实现一个嵌套类来处理数字类函数,以便捕获示例中的调用。或者,至少这是一种方法..

整数值包含以下函数,您需要拦截

['__abs__', '__add__', '__and__', '__class__', '__cmp__', '__coerce__', '__delattr__', '__div__', '__divmod__', '__doc__', '__float__', '__floordiv__', '__format__', '__getattribute__', '__getnewargs__', '__hash__', '__hex__', '__index__', '__init__', '__int__', '__invert__', '__long__', '__lshift__', '__mod__', '__mul__', '__neg__', '__new__', '__nonzero__', '__oct__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdiv__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'imag', 'numerator', 'real']

1
你的测试是在Python 2中完成的吗?如果是,那么你应该让你的类继承自“object”,否则你将得到一个旧式类,它不使用“__getattribute__”方法(或描述符,或Python数据模型的许多其他复杂部分)。 - Blckknght
我尝试从Object继承,然后raise AttributeError,然后我得到了这个Getattribute: a GetAttr: a None。我该如何通过其他链式方法?我没有越过__getattr__ - user196264097
这是因为在我的示例中,我从未返回任何值,因此输出为 None。您还需要拦截 __setitem__ 等,并以某种方式跟踪设置的值并相应地返回它们 :) - Torxed

0

来自文档

... 例如,obj.d在obj的字典中查找d。如果d定义了方法__get__(),那么根据下面列出的优先级规则调用d.__get__(obj)

... 对于对象,机制在object.__getattribute__()中,它将b.x转换为type(b).__dict__['x'].__get__(b, type(b))。该实现通过一个优先级链工作,其中数据描述符优先于实例变量,实例变量优先于非数据描述符,并将最低优先级分配给提供的__getattr__()

也就是说,obj.a调用默认使用__dict____getattribute__()。如果提供了__getattr__(),则作为最后一道防线进行调用。其余内容描述了描述符(例如property或普通方法)的行为。

如何在 d 中定义方法 __get__()
import random 

class C(object):
    @property
    def d(self):
        return random.random()

c = C()
print(c.d)

如何定义 d__get__() 方法?你能给个例子吗? - user196264097
@user9:我已经添加了一个例子。 - jfs
但是你没有定义__get__方法。同时Torex说什么都没有被调用。他是对的吗?你能给我一个像torx一样的示例,让我可以看到所有的打印语句,以便我可以看到调用方法的链条吗? - user196264097
@user9:Torex的回答使用了旧式类(它不是“object”的子类)。文档显示了纯Python property实现,您可以看到如何定义__get __()。在您的问题中,您不需要了解描述符就可以理解简单的属性访问。 - jfs
我在这里尝试修改过的Torx代码 http://pastebin.com/rtrpCGQy。我可以先进入`Getattribute`,然后进入`getattr`,然后就会出现错误。为什么我没有进入`__dict__`呢? - user196264097
@user9:__dict__是一个字典(它是隐式定义的),而不是一个方法。 - jfs

0

首先调用命名空间字典(__dict__)。

来自docs:

类实例有一个作为字典实现的命名空间,这是属性引用搜索的第一个位置。当在那里找不到属性时,并且该实例的类具有该名称的属性,则继续搜索类属性。如果没有找到类属性,并且对象的类具有__getattr__()方法,则调用该方法以满足查找。

object.__getattribute__的文档:

无条件地调用以实现对类的实例的属性访问。如果类还定义了__getattr__(),则除非__getattribute__()显式调用它或引发AttributeError,否则不会调用后者。此方法应返回(计算出的)属性值或引发AttributeError异常。

object.__getattr__(self, name) 的文档:

如果属性通过常规机制找到,则不会调用 __getattr__()


1
如果通过正常机制找到了属性,那么这个正常机制是什么? - user196264097
在命名空间dict中,__getattribute__是常规机制。请阅读文档。 - Ashwini Chaudhary

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接