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 */