From f0b35e1501f279aba1b97df068c6bc7384e2ab1a Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 18 Sep 2001 03:53:24 +0000 Subject: [PATCH] Redo the PyMethod attributes using a dir()-friendly approach, creating descriptors for each attribute. The getattr() implementation is similar to PyObject_GenericGetAttr(), but delegates to im_self instead of looking in __dict__; I couldn't do this as a wrapper around PyObject_GenericGetAttr(). XXX A problem here is that this is a case of *delegation*. dir() doesn't see exactly the same attributes that are actually defined; e.g. if the delegate is a Python function object, it supports attributes like func_code etc., but these are not visible to dir(); on the other hand, dynamic function attributes (stored in the function's __dict__) *are* visible to dir(). Maybe we need a mechanism to tell dir() about the delegation mechanism? I vaguely recall seeing a request in the newsgroup for a more formal definition of attribute delegation too. Sigh, time for a new PEP. --- Objects/classobject.c | 118 +++++++++++++++++++++++++++--------------- 1 file changed, 75 insertions(+), 43 deletions(-) diff --git a/Objects/classobject.c b/Objects/classobject.c index 9d84173b0b3..25a0743a3cf 100644 --- a/Objects/classobject.c +++ b/Objects/classobject.c @@ -2000,58 +2000,90 @@ PyMethod_New(PyObject *func, PyObject *self, PyObject *class) return (PyObject *)im; } -/* Class method methods */ +/* Descriptors for PyMethod attributes */ + +/* im_class, im_func and im_self are stored in the PyMethod object */ #define OFF(x) offsetof(PyMethodObject, x) static struct memberlist instancemethod_memberlist[] = { - {"im_func", T_OBJECT, OFF(im_func)}, - {"im_self", T_OBJECT, OFF(im_self)}, - {"im_class", T_OBJECT, OFF(im_class)}, - /* Dummies that are not handled by getattr() except for __members__ */ - {"__doc__", T_INT, 0}, - {"__name__", T_INT, 0}, - {"__dict__", T_OBJECT, 0}, + {"im_class", T_OBJECT, OFF(im_class), READONLY|RESTRICTED}, + {"im_func", T_OBJECT, OFF(im_func), READONLY|RESTRICTED}, + {"im_self", T_OBJECT, OFF(im_self), READONLY|RESTRICTED}, {NULL} /* Sentinel */ }; -static int -instancemethod_setattro(register PyMethodObject *im, PyObject *name, - PyObject *v) -{ - char *sname = PyString_AsString(name); - - PyErr_Format(PyExc_TypeError, "read-only attribute: %s", sname); - return -1; -} - +/* __dict__, __doc__ and __name__ are retrieved from im_func */ static PyObject * -instancemethod_getattro(register PyMethodObject *im, PyObject *name) +im_get_dict(PyMethodObject *im) { - PyObject *rtn; - char *sname = PyString_AsString(name); - if (sname[0] == '_') { - /* Inherit __name__ and __doc__ from the callable object - implementing the method */ - if (strcmp(sname, "__name__") == 0 || - strcmp(sname, "__doc__") == 0) - return PyObject_GetAttr(im->im_func, name); - } - if (PyEval_GetRestricted()) { - PyErr_SetString(PyExc_RuntimeError, - "instance-method attributes not accessible in restricted mode"); - return NULL; - } - if (sname[0] == '_' && strcmp(sname, "__dict__") == 0) - return PyObject_GetAttr(im->im_func, name); + return PyObject_GetAttrString(im->im_func, "__dict__"); +} - rtn = PyMember_Get((char *)im, instancemethod_memberlist, sname); - if (rtn == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) { - PyErr_Clear(); - rtn = PyObject_GetAttr(im->im_func, name); +static PyObject * +im_get_doc(PyMethodObject *im) +{ + return PyObject_GetAttrString(im->im_func, "__doc__"); +} + +static PyObject * +im_get_name(PyMethodObject *im) +{ + return PyObject_GetAttrString(im->im_func, "__name__"); +} + +static struct getsetlist instancemethod_getsetlist[] = { + {"__dict__", (getter)im_get_dict}, + {"__doc__", (getter)im_get_doc}, + {"__name__", (getter)im_get_name}, + {NULL} /* Sentinel */ +}; + +/* The getattr() implementation for PyMethod objects is similar to + PyObject_GenericGetAttr(), but instead of looking in __dict__ it + asks im_self for the attribute. Then the error handling is a bit + different because we want to preserve the exception raised by the + delegate, unless we have an alternative from our class. */ + +static PyObject * +instancemethod_getattro(PyObject *obj, PyObject *name) +{ + PyMethodObject *im = (PyMethodObject *)obj; + PyTypeObject *tp = obj->ob_type; + PyObject *descr, *res; + descrgetfunc f; + + if (tp->tp_dict == NULL) { + if (PyType_Ready(tp) < 0) + return NULL; } - return rtn; + + descr = _PyType_Lookup(tp, name); + f = NULL; + if (descr != NULL) { + f = descr->ob_type->tp_descr_get; + if (f != NULL && PyDescr_IsData(descr)) + return f(descr, obj, (PyObject *)obj->ob_type); + } + + res = PyObject_GetAttr(im->im_func, name); + if (res != NULL || !PyErr_ExceptionMatches(PyExc_AttributeError)) + return res; + + if (f != NULL) { + PyErr_Clear(); + return f(descr, obj, (PyObject *)obj->ob_type); + } + + if (descr != NULL) { + PyErr_Clear(); + Py_INCREF(descr); + return descr; + } + + assert(PyErr_Occurred()); + return NULL; } static void @@ -2298,7 +2330,7 @@ PyTypeObject PyMethod_Type = { instancemethod_call, /* tp_call */ 0, /* tp_str */ (getattrofunc)instancemethod_getattro, /* tp_getattro */ - (setattrofunc)instancemethod_setattro, /* tp_setattro */ + PyObject_GenericSetAttr, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */ 0, /* tp_doc */ @@ -2309,8 +2341,8 @@ PyTypeObject PyMethod_Type = { 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ - 0, /* tp_members */ - 0, /* tp_getset */ + instancemethod_memberlist, /* tp_members */ + instancemethod_getsetlist, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ instancemethod_descr_get, /* tp_descr_get */