Strange handle behavior

When an attribute __get__, __set__or __delete__descriptor, is not a method and instead is a universal callable, the first argument of that callable is incompatible:


class Callable(object):

    def __call__(self, first, *args, **kwargs):
        print(first)


class Descriptor(object):

    __set__ = Callable()
    __delete__ = Callable()
    __get__ = Callable()


class MyClass(object):

    d = Descriptor()


mc = MyClass()
mc.d = 1
del mc.d
mc.d

<__main__.MyClass object at 0x10854cda0>
<__main__.MyClass object at 0x10854cda0>
<__main__.Descriptor object at 0x10855f240>

Why is the owner descriptor passed to the first argument of the __get__called if that attribute is not technically a “method”? And, perhaps more importantly, why is this behavior consistent across all descriptor attributes?

What's going on here?

+4
source share
1 answer

CPython . , , promises Python .

, , , .


a __set__ __delete__, Python, CPython slot_tp_descr_set, C, (, C .)

static int
slot_tp_descr_set(PyObject *self, PyObject *target, PyObject *value)
{
    PyObject *res;
    _Py_IDENTIFIER(__delete__);
    _Py_IDENTIFIER(__set__);

    if (value == NULL)
        res = call_method(self, &PyId___delete__, "(O)", target);
    else
        res = call_method(self, &PyId___set__, "(OO)", target, value);
    if (res == NULL)
        return -1;
    Py_DECREF(res);
    return 0;
}

call_method, __getattribute__, __getattr__ dict, , .

, - MyClass.d, , __set__ __delete__ MyClass.d . , Python, , Python Descriptor __set__ __delete__.


a __get__, Python, CPython slot_tp_descr_get, -.

static PyObject *
slot_tp_descr_get(PyObject *self, PyObject *obj, PyObject *type)
{
    PyTypeObject *tp = Py_TYPE(self);
    PyObject *get;
    _Py_IDENTIFIER(__get__);

    get = _PyType_LookupId(tp, &PyId___get__);
    if (get == NULL) {
        /* Avoid further slowdowns */
        if (tp->tp_descr_get == slot_tp_descr_get)
            tp->tp_descr_get = NULL;
        Py_INCREF(self);
        return self;
    }
    if (obj == NULL)
        obj = Py_None;
    if (type == NULL)
        type = Py_None;
    return PyObject_CallFunctionObjArgs(get, self, obj, type, NULL);
}

CPython _PyType_LookupId __get__ type(mc) call_method mc.

call_method, _PyType_LookupId . Python , , , , self . self ( Descriptor) __get__ PyObject_CallFunctionObjArgs(get, self, obj, type, NULL).


__get__ Descriptor first, Python __get__, __set__ __delete__.

+4

Source: https://habr.com/ru/post/1677879/


All Articles