【问题标题】:How does __getattribute__ fetch a value?__getattribute__ 如何获取值?
【发布时间】:2024-01-03 16:52:01
【问题描述】:
class test():
  name = 'arthur'

  def __getattribute__(self, value):
    return super().__getattribute__(value)

x = test()
x.name

------------------------------------------
Output: 'arthur'

我试图了解__getattribute__ 的底层机制。我理解(希望)super().__getattribute__(value) 在这种情况下会接触到对象类。但是object 实际上是如何从 test() 类中获取 name 值的呢?如果在避免递归的情况下自己编码在一个类中,如何使用__getattribute__ 获取属性值?

正如我所说,我想了解底层机制,我知道这不是您通常处理事情的方式。

谢谢!

【问题讨论】:

标签: python python-3.x function class built-in


【解决方案1】:

首先,__getattribute__ 是一种神奇的方法(又名dunder 方法/双下划线方法)。这是一个属性/属性访问器方法,即使属性/属性在类本身中可用,它也会拦截每个属性/属性访问。另一方面,__getattr__ 魔术方法仅在您访问的属性在类/实例中不存在时才被调用。总之……

还有一件更重要的事情需要注意,正如您可能已经知道的那样,所有类都隐式或显式地扩展/继承了基础 object 类。因此,您定义的任何类,默认父类都是内置的object 类。你问的一头雾水:

我了解(希望)super().__getattribute__(value) 到达 在这种情况下到 object 类。但实际上objecttest() 类中取出name 值?

让我们先看一些基本的例子:

class Foo:
    def __init__(self, name):
        self.name = name

    def __getattribute__(self, name):
        return super().__getattribute__(name)

等价于

class Foo(object):
    def __init__(self, name):
        self.name = name

    def __getattribute__(self, name):
        return object.__getattribute__(self, name)

所以,当你打电话时

object.__getattribute__(self, name)

您正在将上下文(类的实例)显式传递给父 (object) 类。所以父/对象类知道上下文并从传递的实例中获取属性。另一方面,当您调用时:

super().__getattribute__(name)

Python 为您设置上下文。所以,你可以想象这样的事情:

super(test, self).__getattribute__(name) # it's valid

但在这种情况下,它是隐含的,这就是 super() 调用知道在哪里查找属性的方式。值得一提的是,Python 的super() 内置函数返回一个代理object,一个可以通过委托调用基类方法的替代对象,而且super() 可以带两个参数(正如您在前面的代码中看到的那样) sn-p),第一个是子类类型(在本例中为test),第二个参数是一个对象,该子类的一个实例(test)。

Python 3 中,super(test, self) 调用等效于无参数的super() 调用。希望我已经澄清了你的困惑。你可以阅读更多关于super()的信息。

【讨论】:

    【解决方案2】:

    您无法在 Python 中编写此代码。这是 Python 调用的 C 函数:

    PyObject *
    PyObject_GetAttr(PyObject *v, PyObject *name)
    {
        PyTypeObject *tp = Py_TYPE(v);
    
        if (!PyUnicode_Check(name)) {
            PyErr_Format(PyExc_TypeError,
                         "attribute name must be string, not '%.200s'",
                         name->ob_type->tp_name);
            return NULL;
        }
        if (tp->tp_getattro != NULL)
            return (*tp->tp_getattro)(v, name);
        if (tp->tp_getattr != NULL) {
            const char *name_str = PyUnicode_AsUTF8(name);
            if (name_str == NULL)
                return NULL;
            return (*tp->tp_getattr)(v, (char *)name_str);
        }
        PyErr_Format(PyExc_AttributeError,
                     "'%.50s' object has no attribute '%U'",
                     tp->tp_name, name);
        return NULL;
    }
    

    https://github.com/python/cpython/blob/8289e27393395ee903bd096d42e07c112d7f15c6/Objects/object.c#L1001-L1024

    这是object.__getattribute__。在您的示例中调用它是因为所有类都继承自 object

    【讨论】:

    • 所以为了确保我理解正确,当super()__getattribute__(value) 到达对象时,对象调用一个名为builtin_getatrr 的内部C 函数,它通过继承搜索搜索所需的值,同时以某种方式避免再次触发__getattribute__? (我不懂 C 代码,这就是我问的原因)
    • @Arthred 已更新。最后一个代码是getattr,这可能是它令人困惑的原因。
    • 非常感谢!我是否需要了解object.__getattribute__ 如何避免陷入递归循环并实际设法获取所需的值?
    • 因为到目前为止我读过的所有地方我们都需要调用object.__getattribute,否则我们最终会陷入循环,我很想知道对象实际上是如何避免这种情况的
    • @Arthred 不客气。您使用的是super(),它是object,而不是test。没有递归。