本文介绍了为什么在类上设置描述符会覆盖描述符?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

简单的复制:

class VocalDescriptor(object):
    def __get__(self, obj, objtype):
        print('__get__, obj={}, objtype={}'.format(obj, objtype))
    def __set__(self, obj, val):
        print('__set__')

class B(object):
    v = VocalDescriptor()

B.v # prints "__get__, obj=None, objtype=<class '__main__.B'>"
B.v = 3 # does not print "__set__", evidently does not trigger descriptor
B.v # does not print anything, we overwrote the descriptor

此问题具有有效重复,但没有回答重复项,我作为学习练习对CPython源代码进行了更多研究.警告:我进入了杂草.我真的很希望能从知道那些水域的船长那里得到帮助.为了我自己的未来利益和未来读者的利益,我试图尽可能地明确地跟踪正在寻找的电话.

This question has an effective duplicate, but the duplicate was not answered, and I dug a bit more into the CPython source as a learning exercise. Warning: i went into the weeds. I'm really hoping I can get help from a captain who knows those waters. I tried to be as explicit as possible in tracing the calls I was looking at, for my own future benefit and the benefit of future readers.

我已经看到很多墨水洒在__getattribute__应用于描述符的行为上,例如查找优先级. 调用描述符" 中位于我认为与相应的" tp_slots"然后在其中填充了tp_getattro .而B.v最初会打印__get__, obj=None, objtype=<class '__main__.B'>的事实对我来说很有意义.

I've seen a lot of ink spilled over the behavior of __getattribute__ applied to descriptors, e.g. lookup precedence. The Python snippet in "Invoking Descriptors" just below For classes, the machinery is in type.__getattribute__()... roughly agrees in my mind with what I believe is the corresponding CPython source in type_getattro, which I tracked down by looking at "tp_slots" then where tp_getattro is populated. And the fact that B.v initially prints __get__, obj=None, objtype=<class '__main__.B'> makes sense to me.

我不明白的是,为什么分配B.v = 3盲目地覆盖描述符,而不是触发v.__set__?我试图跟踪CPython调用,从"tp_slots" ,然后查看其中填充了tp_setattro ,然后查看 type_setattro . type_setattro 似乎是薄薄的包装纸 _PyObject_GenericSetAttrWithDict .我困惑的症结在于:_PyObject_GenericSetAttrWithDict似乎具有优先使用描述符的__set__方法的逻辑!考虑到这一点,我想不出为什么B.v = 3盲目地覆盖v而不是触发v.__set__.

What I don't understand is, why does the assignment B.v = 3 blindly overwrite the descriptor, rather than triggering v.__set__? I tried to trace the CPython call, starting once more from "tp_slots", then looking at where tp_setattro is populated, then looking at type_setattro. type_setattro appears to be a thin wrapper around _PyObject_GenericSetAttrWithDict. And there's the crux of my confusion: _PyObject_GenericSetAttrWithDict appears to have logic that gives precedence to a descriptor's __set__ method!! With this in mind, I can't figure out why B.v = 3 blindly overwrites v rather than triggering v.__set__.

免责声明1:我没有使用printfs从源代码重建Python,所以我没有完全确定type_setattro是在B.v = 3期间被调用的内容.

Disclaimer 1: I did not rebuild Python from source with printfs, so I'm notcompletely sure type_setattro is what's being called during B.v = 3.

免责声明2:VocalDescriptor并非示例典型"或推荐"描述符定义.告诉我何时调用方法是一个冗长的操作.

Disclaimer 2: VocalDescriptor is not intended to exemplify "typical" or "recommended" descriptor definition. It's a verbose no-op to tell me when the methods are being called.

推荐答案

您正确的认为B.v = 3只是用整数覆盖描述符(应该如此).

You are correct that B.v = 3 simply overwrites the descriptor with an integer (as it should).

要使B.v = 3调用描述符,应该在元类上(即type(B)上)定义描述符.

For B.v = 3 to invoke a descriptor, the descriptor should have been defined on the metaclass, i.e. on type(B).

>>> class BMeta(type):
...     v = VocalDescriptor()
...
>>> class B(metaclass=BMeta):
...     pass
...
>>> B.v = 3
__set__

要在B上调用描述符,您将使用一个实例:B().v = 3将执行此操作.

To invoke the descriptor on B, you would use an instance: B().v = 3 will do it.

B.v调用getter的原因是允许返回描述符实例本身.通常,您会这样做,以允许通过类对象访问描述符:

The reason for B.v invoking the getter is to allow returning the descriptor instance itself. Usually you would do that, to allow access on the descriptor via the class object:

class VocalDescriptor(object):
    def __get__(self, obj, objtype):
        if obj is None:
            return self
        print('__get__, obj={}, objtype={}'.format(obj, objtype))
    def __set__(self, obj, val):
        print('__set__')

现在,B.v将返回与<mymodule.VocalDescriptor object at 0xdeadbeef>类似的实例,您可以与之交互.它实际上是描述符对象,定义为类属性,并且其状态B.v.__dict__B的所有实例之间共享.

Now B.v would return some instance like <mymodule.VocalDescriptor object at 0xdeadbeef> which you can interact with. It is literally the descriptor object, defined as a class attribute, and its state B.v.__dict__ is shared between all instances of B.

当然,完全由用户代码来定义他们想要B.v要做的事情,返回self只是常见的模式.

Of course it is up to user's code to define exactly what they want B.v to do, returning self is just the common pattern.

这篇关于为什么在类上设置描述符会覆盖描述符?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-07 05:38