Python dunder 方法包​​装为属性

Zha*_*iel 6 python python-datamodel python-descriptors

我偶然发现了这段代码,我觉得很奇怪,因为它似乎违反了 python 内置函数直接从对象的类调用 dunder 方法的事实。举__call__个例子,如果我们定义类A如下:

\n
class A:\n    @property\n    def __call__(self):\n        def inner():\n            return 'Called.'\n        return inner\n\na = A()\na() # return 'Called.'\n\ntype(a).__call__(a) # return 'property' object is not callable. \n
Run Code Online (Sandbox Code Playgroud)\n

然而,这种行为似乎与Python官方文档中所说的相矛盾:

\n
\n

object.__call__(self[, args...])当实例\xe2\x80\x9c称为\xe2\x80\x9d\nas函数时调用;如果定义了此方法,则x(arg1, arg2, ...)大致\n转换为type(x).__call__(x, arg1, ...).

\n
\n

谁能解释一下这是怎么回事?

\n

jsb*_*eno 2

是的,但它尊重从给定函数检索方法的方式 - 我们可以看到该__get__ 方法被调用:

在下面的代码中,我只是property用一个更简单的描述符替换,该描述符将检索其“func” - 并将其用作方法__call__


In [34]: class X:
    ...:     def __init__(self, func):
    ...:         self.func = func
    ...:     def __get__(self, instance, owner):
    ...:         print("descriptor getter called")
    ...:         return self.func
    ...: 

In [35]: class Y:
    ...:     __call__ = X(lambda: "Z")
    ...: 

In [36]: y = Y()

In [37]: y()
descriptor getter called
Out[37]: 'Z'
Run Code Online (Sandbox Code Playgroud)

因此,“dunder”功能只是像往常一样通过所有方法检索方法__get__。被跳过的是所经历的步骤__getattribute__- Python 将直接进入类__call__中的槽A,而不是通过调用__getattribute__从类开始(对于描述符)的正常查找序列,然后查看实例,而不是回到类(对于常规属性):它假设如果实例的类 dunder 插槽中有某些东西,那么它是一个方法,并__get__相应地使用它的方法。

__get__从实例检索函数时使用函数- 因为这是注入实例作为self调用参数的机制。

而正是替换来发挥其“魔力”的__get__东西。property

为了证明它__getatribute__没有被调用,在 iPython 中,我必须将“y”封装在一个虚拟函数中,否则 iPython__getattribute__在尝试自动完成内容时会触发:


In [42]: class Y:
    ...:     __call__ = X(lambda: "Z")
    ...:     def __getattribute__(self, name):
    ...:         print("getattribute called")
    ...:         return super().__getattribute__(name)
    ...: 

In [43]: def w():
    ...:     y = Y()
    ...:     return y()
    ...: 

In [44]: w()
descriptor getter called
Out[44]: 'Z'
# in contrast with:

In [46]: Y().__call__()
getattribute called
descriptor getter called
Out[46]: 'Z'


Run Code Online (Sandbox Code Playgroud)