定义: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)=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 方法实现绑定的机制。
>>> 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)。
新手,如有不对还请大家指正。轻拍!