定义:descriptor是一个对象属性,拥有下述的任何一个绑定方法(__set__, __get__, __delete__)。
协议:
#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 会影响对象属性的查找顺序。总结如下:
1. instance 属性 优先于class 属性
2. 如果在class属性中发现同名的data descriptor, 那么该data descriptor会优于instance属性
附上一个属性查找逻辑代码(get):
#search an attribute 'f' of obj, type(obj)=clsif hasattr(cls, 'f'): desc = cls.ftype = 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 descriptorif 'f' in obj.__dict__: return obj.__dict__['f']if hasattr(type, '__get__'): return type.__get__(desc, obj, 'f')#instance's __dict__ can't foundif 'f' in obj.__class__.__dict__: return obj.__class__.__dict__['f']#search in base classes by mro
下面我们来看一个非常有意思的示例,此例也说明了new style class 方法实现绑定的机制。
>>> class A: def fun(self): pass>>> a = A()>>> type(A.fun), type(a.fun)(, )
上图可以发现A.fun是普通的python函数对象(PyFunction_Type),而A的实例的fun(a.fun)居然变成了PyMethod_Type。
为什么会变成这样,他们之间有什么关系?下面我们慢慢道来,查看一下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)。
新手,如有不对还请大家指正。轻拍!