Committing PEP 232, function attribute feature, approved by Guido.
Closes SF patch #103123.

funcobject.h:

    PyFunctionObject: add the func_dict slot.

funcobject.c:

    PyFunction_New(): Initialize the func_dict slot to NULL.

    func_getattr(): Rename to func_getattro() and change the
    signature.  It's more efficient to use attro methods and dig the C
    string out than it is to re-convert a C string to a PyString.

    Also, add support for getting the __dict__ (a.k.a. func_dict)
    attribute, and for getting an arbitrary function attribute.

    func_setattr(): Rename to func_setattro() and change the signature
    for the same reason.  Also add support for setting __dict__
    (a.k.a. func_dict) and any arbitrary function attribute.

    func_dealloc(): Be sure to DECREF the func_dict slot.

    func_traverse(): Be sure to traverse func_dict too.

    PyFunction_Type: make the necessary func_?etattro() changes.

classobject.c:

    instancemethod_memberlist: Add __dict__

    instancemethod_setattro(): New method to set arbitrary attributes
    on methods (really the underlying im_func).  Raise TypeError when
    the instance is bound or when you're trying to set one of the
    reserved im_* attributes.

    instancemethod_getattr(): Renamed to instancemethod_getattro()
    since that's what it really is.  Also, added support fo getting
    arbitrary attributes through the im_func.

    PyMethod_Type: Do the ?etattr{,o} dance.
diff --git a/Include/funcobject.h b/Include/funcobject.h
index 6ba1e09..35ccf43 100644
--- a/Include/funcobject.h
+++ b/Include/funcobject.h
@@ -14,6 +14,7 @@
     PyObject *func_defaults;
     PyObject *func_doc;
     PyObject *func_name;
+    PyObject *func_dict;
 } PyFunctionObject;
 
 extern DL_IMPORT(PyTypeObject) PyFunction_Type;
diff --git a/Objects/classobject.c b/Objects/classobject.c
index f7fd30c..4dc72d2 100644
--- a/Objects/classobject.c
+++ b/Objects/classobject.c
@@ -1693,13 +1693,39 @@
 	/* Dummies that are not handled by getattr() except for __members__ */
 	{"__doc__",	T_INT,		0},
 	{"__name__",	T_INT,		0},
+	{"__dict__",    T_OBJECT,       0},
 	{NULL}	/* Sentinel */
 };
 
-static PyObject *
-instancemethod_getattr(register PyMethodObject *im, PyObject *name)
+static int
+instancemethod_setattro(register PyMethodObject *im, PyObject *name,
+			PyObject *v)
 {
 	char *sname = PyString_AsString(name);
+
+	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 attributes through bound methods");
+		return -1;
+	}
+	return PyObject_SetAttr(im->im_func, name, v);
+}
+ 
+
+static PyObject *
+instancemethod_getattro(register PyMethodObject *im, PyObject *name)
+{
+	PyObject *rtn;
+	char *sname = PyString_AsString(name);
 	if (sname[0] == '_') {
 		/* Inherit __name__ and __doc__ from the callable object
 		   implementing the method */
@@ -1712,7 +1738,15 @@
 	    "instance-method attributes not accessible in restricted mode");
 		return NULL;
 	}
-	return PyMember_Get((char *)im, instancemethod_memberlist, sname);
+	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_ExceptionMatches(PyExc_AttributeError)) {
+		PyErr_Clear();
+		rtn = PyObject_GetAttr(im->im_func, name);
+	}
+	return rtn;
 }
 
 static void
@@ -1832,8 +1866,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*/
 	0,			/* tp_as_buffer */
 	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_GC, /*tp_flags*/
 	0,			/* tp_doc */
diff --git a/Objects/funcobject.c b/Objects/funcobject.c
index 8b045f4..027e97f 100644
--- a/Objects/funcobject.c
+++ b/Objects/funcobject.c
@@ -30,7 +30,10 @@
 			doc = Py_None;
 		Py_INCREF(doc);
 		op->func_doc = doc;
+		op->func_dict = NULL;
 	}
+	else
+		return NULL;
 	PyObject_GC_Init(op);
 	return (PyObject *)op;
 }
@@ -102,25 +105,54 @@
 };
 
 static PyObject *
-func_getattr(PyFunctionObject *op, char *name)
+func_getattro(PyFunctionObject *op, PyObject *name)
 {
-	if (name[0] != '_' && PyEval_GetRestricted()) {
+	PyObject *rtn;
+	char *sname = PyString_AsString(name);
+	
+	if (sname[0] != '_' && PyEval_GetRestricted()) {
 		PyErr_SetString(PyExc_RuntimeError,
 		  "function attributes not accessible in restricted mode");
 		return NULL;
 	}
-	return PyMember_Get((char *)op, func_memberlist, name);
+
+	if (!strcmp(sname, "__dict__") || !strcmp(sname, "func_dict")) {
+		if (op->func_dict == NULL)
+			rtn = Py_None;
+		else
+			rtn = op->func_dict;
+
+		Py_INCREF(rtn);
+		return rtn;
+	}
+
+	/* no API for PyMember_HasAttr() */
+	rtn = PyMember_Get((char *)op, func_memberlist, sname);
+
+	if (rtn == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) {
+		PyErr_Clear();
+		if (op->func_dict != NULL) {
+			rtn = PyDict_GetItem(op->func_dict, name);
+			Py_XINCREF(rtn);
+		}
+		if (rtn == NULL)
+			PyErr_SetObject(PyExc_AttributeError, name);
+	}
+	return rtn;
 }
 
 static int
-func_setattr(PyFunctionObject *op, char *name, PyObject *value)
+func_setattro(PyFunctionObject *op, PyObject *name, PyObject *value)
 {
+	int rtn;
+	char *sname = PyString_AsString(name);
+
 	if (PyEval_GetRestricted()) {
 		PyErr_SetString(PyExc_RuntimeError,
 		  "function attributes not settable in restricted mode");
 		return -1;
 	}
-	if (strcmp(name, "func_code") == 0) {
+	if (strcmp(sname, "func_code") == 0) {
 		if (value == NULL || !PyCode_Check(value)) {
 			PyErr_SetString(
 				PyExc_TypeError,
@@ -128,7 +160,7 @@
 			return -1;
 		}
 	}
-	else if (strcmp(name, "func_defaults") == 0) {
+	else if (strcmp(sname, "func_defaults") == 0) {
 		if (value != Py_None && !PyTuple_Check(value)) {
 			PyErr_SetString(
 				PyExc_TypeError,
@@ -138,7 +170,33 @@
 		if (value == Py_None)
 			value = NULL;
 	}
-	return PyMember_Set((char *)op, func_memberlist, name, value);
+	else if (!strcmp(sname, "func_dict") || !strcmp(sname, "__dict__")) {
+		if (value != Py_None && !PyDict_Check(value)) {
+			PyErr_SetString(
+				PyExc_TypeError,
+				"func_dict must be set to a dict object");
+			return -1;
+		}
+		if (value == Py_None)
+			value = NULL;
+
+		Py_XDECREF(op->func_dict);
+		Py_XINCREF(value);
+		op->func_dict = value;
+		return 0;
+	}
+
+	rtn = PyMember_Set((char *)op, func_memberlist, sname, value);
+	if (rtn < 0 && PyErr_ExceptionMatches(PyExc_AttributeError)) {
+		PyErr_Clear();
+		if (op->func_dict == NULL) {
+			op->func_dict = PyDict_New();
+			if (op->func_dict == NULL)
+				return -1;
+		}
+		rtn = PyDict_SetItem(op->func_dict, name, value);
+	}
+	return rtn;
 }
 
 static void
@@ -150,6 +208,7 @@
 	Py_DECREF(op->func_name);
 	Py_XDECREF(op->func_defaults);
 	Py_XDECREF(op->func_doc);
+	Py_XDECREF(op->func_dict);
 	op = (PyFunctionObject *) PyObject_AS_GC(op);
 	PyObject_DEL(op);
 }
@@ -227,6 +286,11 @@
 		if (err)
 			return err;
 	}
+	if (f->func_dict) {
+		err = visit(f->func_dict, arg);
+		if (err)
+			return err;
+	}
 	return 0;
 }
 
@@ -238,8 +302,8 @@
 	0,
 	(destructor)func_dealloc, /*tp_dealloc*/
 	0,		/*tp_print*/
-	(getattrfunc)func_getattr, /*tp_getattr*/
-	(setattrfunc)func_setattr, /*tp_setattr*/
+	0, /*tp_getattr*/
+	0, /*tp_setattr*/
 	(cmpfunc)func_compare, /*tp_compare*/
 	(reprfunc)func_repr, /*tp_repr*/
 	0,		/*tp_as_number*/
@@ -248,8 +312,8 @@
 	(hashfunc)func_hash, /*tp_hash*/
 	0,		/*tp_call*/
 	0,		/*tp_str*/
-	0,		/*tp_getattro*/
-	0,		/*tp_setattro*/
+	(getattrofunc)func_getattro,	     /*tp_getattro*/
+	(setattrofunc)func_setattro,	     /*tp_setattro*/
 	0,		/* tp_as_buffer */
 	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_GC, /*tp_flags*/
 	0,		/* tp_doc */