Python descriptor

定義: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)。

新手,若有不對還請你們指正。輕拍!

相關文章
相關標籤/搜索