[Patches] Second round: arbitrary function and method attributes

Barry A. Warsaw bwarsaw@cnri.reston.va.us
Tue, 11 Apr 2000 19:22:14 -0400 (EDT)


--HXjrLbAr5v
Content-Type: text/plain; charset=us-ascii
Content-Description: message body text
Content-Transfer-Encoding: 7bit


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


--HXjrLbAr5v
Content-Type: text/plain
Content-Description: Diff -u to add arbitrary attrs to funcs and meths
Content-Disposition: inline;
	filename="methdiff.txt"
Content-Transfer-Encoding: 7bit

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);
 }
 

--HXjrLbAr5v
Content-Type: text/plain
Content-Description: Test of func/meth attrs
Content-Disposition: inline;
	filename="test_funcattrs.py"
Content-Transfer-Encoding: 7bit

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

--HXjrLbAr5v
Content-Type: text/plain
Content-Description: Output of test of func/meth attrs
Content-Disposition: inline;
	filename="test_funcattrs"
Content-Transfer-Encoding: 7bit

test_funcattrs
1
1
1
1
1
1
1
1
1

--HXjrLbAr5v--