從原始碼看Python的descriptor
descriptor是Python初學者比較不好懂的概念之一,再加上官網文件說的也不是很清楚, 就更容易讓人誤解了。但是原始碼總不會說不清楚,所以我們從原始碼看清楚descriptor到底 在幹啥。
PyObject * _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict) { /* Make sure the logic of _PyObject_GetMethod is in sync with this method. */ PyTypeObject *tp = Py_TYPE(obj); PyObject *descr = NULL; PyObject *res = NULL; descrgetfunc f; Py_ssize_t dictoffset; PyObject **dictptr; if (!PyUnicode_Check(name)){ PyErr_Format(PyExc_TypeError, "attribute name must be string, not '%.200s'", name->ob_type->tp_name); return NULL; } Py_INCREF(name); if (tp->tp_dict == NULL) { if (PyType_Ready(tp) < 0) goto done; } descr = _PyType_Lookup(tp, name);// 從自身包括繼承關係中找descriptor f = NULL; if (descr != NULL) { Py_INCREF(descr); f = descr->ob_type->tp_descr_get; if (f != NULL && PyDescr_IsData(descr)) { // 如果有descriptor並且是data descriptor,那麼就呼叫並返回 res = f(descr, obj, (PyObject *)obj->ob_type); goto done; } } if (dict == NULL) { /* Inline _PyObject_GetDictPtr */ dictoffset = tp->tp_dictoffset; if (dictoffset != 0) { if (dictoffset < 0) { Py_ssize_t tsize; size_t size; tsize = ((PyVarObject *)obj)->ob_size; if (tsize < 0) tsize = -tsize; size = _PyObject_VAR_SIZE(tp, tsize); assert(size <= PY_SSIZE_T_MAX); dictoffset += (Py_ssize_t)size; assert(dictoffset > 0); assert(dictoffset % SIZEOF_VOID_P == 0); } dictptr = (PyObject **) ((char *)obj + dictoffset); dict = *dictptr; } } if (dict != NULL) { Py_INCREF(dict); // 從__dict__裡查詢屬性並返回 res = PyDict_GetItem(dict, name); if (res != NULL) { Py_INCREF(res); Py_DECREF(dict); goto done; } Py_DECREF(dict); } if (f != NULL) { // 非data descriptor,返回 res = f(descr, obj, (PyObject *)Py_TYPE(obj)); goto done; } if (descr != NULL) { res = descr; descr = NULL; goto done; } PyErr_Format(PyExc_AttributeError, "'%.50s' object has no attribute '%U'", tp->tp_name, name); done: Py_XDECREF(descr); Py_DECREF(name); return res; } PyObject * PyObject_GenericGetAttr(PyObject *obj, PyObject *name) { // 呼叫此函式,往上看 return _PyObject_GenericGetAttrWithDict(obj, name, NULL); }
所以可以得知:
- descriptor是作為屬性時才會生效,如下demo中,Foo是作為屬性時,才會生效 - 屬性查詢順序為 data descriptor -> __dict__ -> descriptor
In [1]: class Foo: ...:def __get__(self, obj, type): ...:print("__get__") ...:def __set__(self, obj, val): ...:print("__set__") ...: In [2]: class Test: ...:bar = Foo() ...: In [3]: t = Test() In [4]: t.bar __get__ In [5]: foo = Foo() In [6]: foo.bar --------------------------------------------------------------------------- AttributeErrorTraceback (most recent call last) <ipython-input-6-673afadb7b8e> in <module>() ----> 1 foo.bar AttributeError: 'Foo' object has no attribute 'bar'