Second round: arbitrary function and method attributes

Here's the second go at adding arbitrary attribute support to function and method objects. Note that this time it's illegal (TypeError) to set an attribute on a bound method object; getting an attribute on a bound method object returns the value on the underlying function object. First the diffs, then the test case and test output. Enjoy, -Barry Index: Include/funcobject.h =================================================================== RCS file: /projects/cvsroot/python/dist/src/Include/funcobject.h,v retrieving revision 2.16 diff -u -r2.16 funcobject.h --- funcobject.h 1998/12/04 18:48:02 2.16 +++ funcobject.h 2000/04/07 21:30:40 @@ -44,6 +44,7 @@ PyObject *func_defaults; PyObject *func_doc; PyObject *func_name; + PyObject *func_dict; } PyFunctionObject; extern DL_IMPORT(PyTypeObject) PyFunction_Type; Index: Objects/classobject.c =================================================================== RCS file: /projects/cvsroot/python/dist/src/Objects/classobject.c,v retrieving revision 2.84 diff -u -r2.84 classobject.c --- classobject.c 2000/04/10 13:03:19 2.84 +++ classobject.c 2000/04/11 22:05:08 @@ -1550,28 +1550,75 @@ /* Dummies that are not handled by getattr() except for __members__ */ {"__doc__", T_INT, 0}, {"__name__", T_INT, 0}, + {"__dict__", T_INT, 0}, {NULL} /* Sentinel */ }; +static int +instancemethod_setattro(im, name, v) + register PyMethodObject *im; + PyObject *name; + PyObject *v; +{ + char* sname = PyString_AsString(name); + if (sname == NULL) + return -1; + + if (PyEval_GetRestricted() || + strcmp(sname, "im_func") == 0 || + strcmp(sname, "im_self") == 0 || + strcmp(sname, "im_class") == 0) + { + PyErr_Format(PyExc_TypeError, "read-only attribute: %s", sname); + return -1; + } + if (im->im_self != NULL) { + PyErr_Format(PyExc_TypeError, + "cannot set bound instance-method attribute: %s", + sname); + return -1; + } + return PyObject_SetAttr(im->im_func, name, v); +} + + static PyObject * -instancemethod_getattr(im, name) +instancemethod_getattro(im, name) register PyMethodObject *im; PyObject *name; { - char *sname = PyString_AsString(name); + PyObject *rtn; + char* sname = PyString_AsString(name); + + if (sname == NULL) + return NULL; + if (sname[0] == '_') { /* Inherit __name__ and __doc__ from the callable object - implementing the method */ - if (strcmp(sname, "__name__") == 0 || - strcmp(sname, "__doc__") == 0) + implementing the method. Can't allow access to __dict__ + here because it should not be readable in restricted + execution mode. + */ + 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"); + PyErr_Format(PyExc_RuntimeError, + "instance-method attributes not accessible in restricted mode: %s", + sname); return NULL; + } + if (sname[0] == '_' && strcmp(sname, "__dict__") == 0) + return PyObject_GetAttr(im->im_func, name); + + rtn = PyMember_Get((char *)im, instancemethod_memberlist, sname); + if (rtn == NULL) { + PyErr_Clear(); + rtn = PyObject_GetAttr(im->im_func, name); } - return PyMember_Get((char *)im, instancemethod_memberlist, sname); + return rtn; } static void @@ -1672,8 +1719,8 @@ (hashfunc)instancemethod_hash, /*tp_hash*/ 0, /*tp_call*/ 0, /*tp_str*/ - (getattrofunc)instancemethod_getattr, /*tp_getattro*/ - 0, /*tp_setattro*/ + (getattrofunc)instancemethod_getattro, /*tp_getattro*/ + (setattrofunc)instancemethod_setattro, /*tp_setattro*/ }; /* Clear out the free list */ Index: Objects/funcobject.c =================================================================== RCS file: /projects/cvsroot/python/dist/src/Objects/funcobject.c,v retrieving revision 2.18 diff -u -r2.18 funcobject.c --- funcobject.c 1998/05/22 00:55:34 2.18 +++ funcobject.c 2000/04/11 22:06:12 @@ -62,6 +62,7 @@ doc = Py_None; Py_INCREF(doc); op->func_doc = doc; + op->func_dict = PyDict_New(); } return (PyObject *)op; } @@ -133,6 +134,8 @@ {"__name__", T_OBJECT, OFF(func_name), READONLY}, {"func_defaults",T_OBJECT, OFF(func_defaults)}, {"func_doc", T_OBJECT, OFF(func_doc)}, + {"func_dict", T_OBJECT, OFF(func_dict)}, + {"__dict__", T_OBJECT, OFF(func_dict)}, {"__doc__", T_OBJECT, OFF(func_doc)}, {NULL} /* Sentinel */ }; @@ -142,12 +145,21 @@ PyFunctionObject *op; char *name; { + PyObject* rtn; + if (name[0] != '_' && PyEval_GetRestricted()) { PyErr_SetString(PyExc_RuntimeError, "function attributes not accessible in restricted mode"); return NULL; + } + rtn = PyMember_Get((char *)op, func_memberlist, name); + if (rtn == NULL) { + PyErr_Clear(); + rtn = PyMapping_GetItemString(op->func_dict, name); + if (rtn == NULL) + PyErr_SetString(PyExc_AttributeError, name); } - return PyMember_Get((char *)op, func_memberlist, name); + return rtn; } static int @@ -156,6 +168,8 @@ char *name; PyObject *value; { + int rtn; + if (PyEval_GetRestricted()) { PyErr_SetString(PyExc_RuntimeError, "function attributes not settable in restricted mode"); @@ -178,8 +192,23 @@ } if (value == Py_None) value = NULL; + } + else if (strcmp(name, "func_dict") == 0 || + strcmp(name, "__dict__") == 0) + { + if (value == NULL || !PyMapping_Check(value)) { + PyErr_SetString( + PyExc_TypeError, + "must set func_dict to a mapping object"); + return -1; + } + } + rtn = PyMember_Set((char *)op, func_memberlist, name, value); + if (rtn < 0) { + PyErr_Clear(); + rtn = PyMapping_SetItemString(op->func_dict, name, value); } - return PyMember_Set((char *)op, func_memberlist, name, value); + return rtn; } static void @@ -191,6 +220,7 @@ Py_DECREF(op->func_name); Py_XDECREF(op->func_defaults); Py_XDECREF(op->func_doc); + Py_XDECREF(op->func_dict); PyMem_DEL(op); } from test_support import verbose class F: def a(self): pass def b(): pass # setting attributes on functions try: b.blah except AttributeError: pass else: print 'did not get expected AttributeError' b.blah = 1 print b.blah == 1 print 'blah' in dir(b) # setting attributes on unbound methods try: F.a.blah except AttributeError: pass else: print 'did not get expected AttributeError' F.a.blah = 1 print F.a.blah == 1 print 'blah' in dir(F.a) # setting attributes on bound methods is illegal f1 = F() try: f1.a.snerp = 1 except TypeError: pass else: print 'did not get expected TypeError' # but accessing attributes on bound methods is fine print f1.a.blah print 'blah' in dir(f1.a) f2 = F() print f1.a.blah == f2.a.blah F.a.wazoo = F f1.a.wazoo is f2.a.wazoo # try setting __dict__ illegally try: F.a.__dict__ = (1, 2, 3) except TypeError: pass else: print 'did not get expected TypeError' F.a.__dict__ = {'one': 111, 'two': 222, 'three': 333} print f1.a.two == 222 from UserDict import UserDict d = UserDict({'four': 444, 'five': 555}) F.a.__dict__ = d try: f2.a.two except AttributeError: pass else: print 'did not get expected AttributeError' print f2.a.four is f1.a.four is F.a.four test_funcattrs 1 1 1 1 1 1 1 1 1

On Tue, 11 Apr 2000, Barry A. Warsaw wrote:
Here's the second go at adding arbitrary attribute support to function and method objects. Note that this time it's illegal (TypeError) to set an attribute on a bound method object; getting an attribute on a bound method object returns the value on the underlying function object. First the diffs, then the test case and test output.
In the instancemethod_setattro function, it might be nice to do the speed optimization and test for sname[0] == 'i' before hitting the strcmp() calls. Oh: policy question: I would think that these attributes *should* be available in restricted mode. They aren't "sneaky" like the builtin attributes. Rather than PyMapping_Get/SetItemString()... PyObject_Get/SetItem() should be used. They apply to mappings and will be faster. Note that (internally) the PyMapping_Get/SetItemString use the latter forms (after constructing a string object(!)). ... whoops. I see that the function object doesn't use the ?etattro() variants. hrm. The stuff is looking really good! Cheers, -g -- Greg Stein, http://www.lyra.org/

"GS" == Greg Stein <gstein@lyra.org> writes:
GS> In the instancemethod_setattro function, it might be nice to GS> do the speed optimization and test for sname[0] == 'i' before GS> hitting the strcmp() calls. Yeah, you could do that, but it complicates the code and the win seems negligable. GS> Oh: policy question: I would think that these attributes GS> *should* be available in restricted mode. They aren't "sneaky" GS> like the builtin attributes. Hmm, good point. That does simplify the code too. I wonder if the __dict__ itself should be restricted, but that doesn't seem like it would buy you much. We don't need to restrict them in classobject anyway, because they are already restricted in funcobject (which ends up getting the call anyway). It might be reasonable to relax that for arbitrary func attrs. GS> Rather than GS> PyMapping_Get/SetItemString()... PyObject_Get/SetItem() should GS> be used. They apply to mappings and will be faster. Note that GS> (internally) the PyMapping_Get/SetItemString use the latter GS> forms (after constructing a string object(!)). ... whoops. I GS> see that the function object doesn't use the ?etattro() GS> variants. hrm. Okay cool. Made these changes and `attro'd 'em too. GS> The stuff is looking really good! Thanks! -Barry
participants (3)
-
Barry A. Warsaw
-
bwarsaw@cnri.reston.va.us
-
Greg Stein