2017-05-24 110 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