2017-05-24 109 views
4

__get____set__,或者描述符的__delete__属性不是一个方法,并且是替代的通用调用,该调用的第一个参数是不一致的:奇怪描述符行为


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> 

为什么业主描述符传递到的的第一个参数当这个属性在技术上不是一个“方法”时可以调用?也许更重要的是,为什么这种行为在所有描述符属性中都不一致?

这是怎么回事吗?

回答

4

CPython内部相关部分刚刚没有实现。这可能被认为是一个错误,尽管我不知道Python对这种情况的适当描述符处理做出了什么承诺。

我可以准确解释内部会发生什么,但是由于这里有多层描述符处理,事情会变得混乱。


对于用Python实现一个__set____delete__,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__,和实例字典,而是执行描述符处理像一个正常的属性查找。

注意,有描述的两级处理在这里 - 我们在处理MyClass.d描述符中间,但现在我们需要考虑MyClass.d描述符的__set____delete__方法是否是自己的描述。它们不是,但是如果它们是用常规的Python函数实现的,它们将是描述符,并且Python函数的描述符处理将绑定Descriptor实例作为其__set____delete__方法的第一个参数。


对于用Python实现一个__get__,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实例)传递给方法PyObject_CallFunctionObjArgs(get, self, obj, type, NULL)


__get__看到Descriptor实例作为first因为Python使用了二级描述符坏快捷调用__get__当内部处理,但不是要求__set____delete__时。

+0

伟大的描述。据我所知,这不是一个规范,但我可能是错的。不管查找方法如何,这里的不一致似乎并不合适。 – rmorshea

+0

查看[bpo-30469](http://bugs.python.org/issue30469)。 – eryksun