定義:descriptor是一個對象屬性,擁有下述的任何一個綁定方法(__set__, __get__, __delete__)。python
協議:緩存
#descriptor are only invoked for new style objects or classes. class descriptor(object): def __set__(self, obj, value): pass def __get__(self, obj, cls): pass def __delete__(self, obj): pass
類型: data descriptor: 定義了 __set__ , __get__ 方法。函數
non data descriptor: 定義了__get__, 未定義__set__。性能
descriptor 會影響對象屬性的查找順序。總結以下:spa
1. instance 屬性 優先於class 屬性指針
2. 若是在class屬性中發現同名的data descriptor, 那麼該data descriptor會優於instance屬性code
附上一個屬性查找邏輯代碼(get):對象
#search an attribute 'f' of obj, type(obj)=cls if hasattr(cls, 'f'): desc = cls.f type = descriptor.__class__ if hasattr(type, '__get__') and hasattr(type, '__set__') or 'f' not in obj.__dict__: return type.__get__(desc, obj, 'f') #can't found through descriptor if 'f' in obj.__dict__: return obj.__dict__['f'] if hasattr(type, '__get__'): return type.__get__(desc, obj, 'f') #instance's __dict__ can't found if 'f' in obj.__class__.__dict__: return obj.__class__.__dict__['f'] #search in base classes by mro
下面咱們來看一個很是有意思的示例,此例也說明了new style class 方法實現綁定的機制。ip
>>> class A: def fun(self): pass >>> a = A() >>> type(A.fun), type(a.fun) (<class 'function'>, <class 'method'>)
上圖能夠發現A.fun是普通的python函數對象(PyFunction_Type),而A的實例的fun(a.fun)竟然變成了PyMethod_Type。get
爲何會變成這樣,他們之間有什麼關係?下面咱們慢慢道來,查看一下python源碼會發現PyFunction_Type的定義以下:
PyTypeObject PyFunction_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "function", sizeof(PyFunctionObject), 0, (destructor)func_dealloc, /* tp_dealloc */ ... func_descr_get, /* tp_descr_get */ 0, /* tp_descr_set */ offsetof(PyFunctionObject, func_dict), /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ func_new, /* tp_new */ };
能夠看到類型定義裏有2個特殊標記的函數指針,實際上他們分別對應着__get__,__set__的實現。至此咱們明白了原來這個
PyFunction_Type的實例其實就是一個non data descriptor,對於a.fun,因爲a的dict中並無fun的屬性,因此到A的dict中查找,
因爲fun是一個non data descriptor屬性,因此A.fun至關於 A.fun.__get__(a, A)。
下面咱們看一下 func_descr_get 的實現:
/* Bind a function to an object */
static PyObject *
func_descr_get(PyObject *func, PyObject *obj, PyObject *type)
{
if (obj == Py_None || obj == NULL) {
Py_INCREF(func);
return func;
}
return PyMethod_New(func, obj);}
PyObject * PyMethod_New(PyObject *func, PyObject *self) { register PyMethodObject *im; if (self == NULL) { PyErr_BadInternalCall(); return NULL; } im = free_list; if (im != NULL) { free_list = (PyMethodObject *)(im->im_self); PyObject_INIT(im, &PyMethod_Type); numfree--; } else { im = PyObject_GC_New(PyMethodObject, &PyMethod_Type); if (im == NULL) return NULL; } im->im_weakreflist = NULL; Py_INCREF(func); im->im_func = func; Py_XINCREF(self); im->im_self = self; _PyObject_GC_TRACK(im); return (PyObject *)im; }
最終descriptor會返回一個PyMethod_Type的一個instance。實際上這個obj就是fun聲明時的self即(a),這裏你應該也明白
class 方法聲明時的那個self位置參數了吧。上面這個函數變身的過程也就是屬性方法的綁定。每次調用是都會進行綁定,
建立新的PyMethod_Type對象,雖然python是用了對象緩存機制,但仍是不可避免的產生性能損失,對於一個頻繁使用的
方法,建議你們使用unbound method版本,即A.fun(a)。
新手,若有不對還請你們指正。輕拍!