Skip to content

Commit d6a9e84

Browse files
committed
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.
1 parent 4a420a0 commit d6a9e84

File tree

3 files changed

+114
-15
lines changed

3 files changed

+114
-15
lines changed

Include/funcobject.h

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ typedef struct {
1414
PyObject *func_defaults;
1515
PyObject *func_doc;
1616
PyObject *func_name;
17+
PyObject *func_dict;
1718
} PyFunctionObject;
1819

1920
extern DL_IMPORT(PyTypeObject) PyFunction_Type;

Objects/classobject.c

+38-4
Original file line numberDiff line numberDiff line change
@@ -1693,12 +1693,38 @@ static struct memberlist instancemethod_memberlist[] = {
16931693
/* Dummies that are not handled by getattr() except for __members__ */
16941694
{"__doc__", T_INT, 0},
16951695
{"__name__", T_INT, 0},
1696+
{"__dict__", T_OBJECT, 0},
16961697
{NULL} /* Sentinel */
16971698
};
16981699

1700+
static int
1701+
instancemethod_setattro(register PyMethodObject *im, PyObject *name,
1702+
PyObject *v)
1703+
{
1704+
char *sname = PyString_AsString(name);
1705+
1706+
if (PyEval_GetRestricted() ||
1707+
strcmp(sname, "im_func") == 0 ||
1708+
strcmp(sname, "im_self") == 0 ||
1709+
strcmp(sname, "im_class") == 0)
1710+
{
1711+
PyErr_Format(PyExc_TypeError, "read-only attribute: %s",
1712+
sname);
1713+
return -1;
1714+
}
1715+
if (im->im_self != NULL) {
1716+
PyErr_Format(PyExc_TypeError,
1717+
"cannot set attributes through bound methods");
1718+
return -1;
1719+
}
1720+
return PyObject_SetAttr(im->im_func, name, v);
1721+
}
1722+
1723+
16991724
static PyObject *
1700-
instancemethod_getattr(register PyMethodObject *im, PyObject *name)
1725+
instancemethod_getattro(register PyMethodObject *im, PyObject *name)
17011726
{
1727+
PyObject *rtn;
17021728
char *sname = PyString_AsString(name);
17031729
if (sname[0] == '_') {
17041730
/* Inherit __name__ and __doc__ from the callable object
@@ -1712,7 +1738,15 @@ instancemethod_getattr(register PyMethodObject *im, PyObject *name)
17121738
"instance-method attributes not accessible in restricted mode");
17131739
return NULL;
17141740
}
1715-
return PyMember_Get((char *)im, instancemethod_memberlist, sname);
1741+
if (sname[0] == '_' && strcmp(sname, "__dict__") == 0)
1742+
return PyObject_GetAttr(im->im_func, name);
1743+
1744+
rtn = PyMember_Get((char *)im, instancemethod_memberlist, sname);
1745+
if (rtn == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) {
1746+
PyErr_Clear();
1747+
rtn = PyObject_GetAttr(im->im_func, name);
1748+
}
1749+
return rtn;
17161750
}
17171751

17181752
static void
@@ -1832,8 +1866,8 @@ PyTypeObject PyMethod_Type = {
18321866
(hashfunc)instancemethod_hash, /*tp_hash*/
18331867
0, /*tp_call*/
18341868
0, /*tp_str*/
1835-
(getattrofunc)instancemethod_getattr, /*tp_getattro*/
1836-
0, /*tp_setattro*/
1869+
(getattrofunc)instancemethod_getattro, /*tp_getattro*/
1870+
(setattrofunc)instancemethod_setattro, /*tp_setattro*/
18371871
0, /* tp_as_buffer */
18381872
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_GC, /*tp_flags*/
18391873
0, /* tp_doc */

Objects/funcobject.c

+75-11
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ PyFunction_New(PyObject *code, PyObject *globals)
3030
doc = Py_None;
3131
Py_INCREF(doc);
3232
op->func_doc = doc;
33+
op->func_dict = NULL;
3334
}
35+
else
36+
return NULL;
3437
PyObject_GC_Init(op);
3538
return (PyObject *)op;
3639
}
@@ -102,33 +105,62 @@ static struct memberlist func_memberlist[] = {
102105
};
103106

104107
static PyObject *
105-
func_getattr(PyFunctionObject *op, char *name)
108+
func_getattro(PyFunctionObject *op, PyObject *name)
106109
{
107-
if (name[0] != '_' && PyEval_GetRestricted()) {
110+
PyObject *rtn;
111+
char *sname = PyString_AsString(name);
112+
113+
if (sname[0] != '_' && PyEval_GetRestricted()) {
108114
PyErr_SetString(PyExc_RuntimeError,
109115
"function attributes not accessible in restricted mode");
110116
return NULL;
111117
}
112-
return PyMember_Get((char *)op, func_memberlist, name);
118+
119+
if (!strcmp(sname, "__dict__") || !strcmp(sname, "func_dict")) {
120+
if (op->func_dict == NULL)
121+
rtn = Py_None;
122+
else
123+
rtn = op->func_dict;
124+
125+
Py_INCREF(rtn);
126+
return rtn;
127+
}
128+
129+
/* no API for PyMember_HasAttr() */
130+
rtn = PyMember_Get((char *)op, func_memberlist, sname);
131+
132+
if (rtn == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) {
133+
PyErr_Clear();
134+
if (op->func_dict != NULL) {
135+
rtn = PyDict_GetItem(op->func_dict, name);
136+
Py_XINCREF(rtn);
137+
}
138+
if (rtn == NULL)
139+
PyErr_SetObject(PyExc_AttributeError, name);
140+
}
141+
return rtn;
113142
}
114143

115144
static int
116-
func_setattr(PyFunctionObject *op, char *name, PyObject *value)
145+
func_setattro(PyFunctionObject *op, PyObject *name, PyObject *value)
117146
{
147+
int rtn;
148+
char *sname = PyString_AsString(name);
149+
118150
if (PyEval_GetRestricted()) {
119151
PyErr_SetString(PyExc_RuntimeError,
120152
"function attributes not settable in restricted mode");
121153
return -1;
122154
}
123-
if (strcmp(name, "func_code") == 0) {
155+
if (strcmp(sname, "func_code") == 0) {
124156
if (value == NULL || !PyCode_Check(value)) {
125157
PyErr_SetString(
126158
PyExc_TypeError,
127159
"func_code must be set to a code object");
128160
return -1;
129161
}
130162
}
131-
else if (strcmp(name, "func_defaults") == 0) {
163+
else if (strcmp(sname, "func_defaults") == 0) {
132164
if (value != Py_None && !PyTuple_Check(value)) {
133165
PyErr_SetString(
134166
PyExc_TypeError,
@@ -138,7 +170,33 @@ func_setattr(PyFunctionObject *op, char *name, PyObject *value)
138170
if (value == Py_None)
139171
value = NULL;
140172
}
141-
return PyMember_Set((char *)op, func_memberlist, name, value);
173+
else if (!strcmp(sname, "func_dict") || !strcmp(sname, "__dict__")) {
174+
if (value != Py_None && !PyDict_Check(value)) {
175+
PyErr_SetString(
176+
PyExc_TypeError,
177+
"func_dict must be set to a dict object");
178+
return -1;
179+
}
180+
if (value == Py_None)
181+
value = NULL;
182+
183+
Py_XDECREF(op->func_dict);
184+
Py_XINCREF(value);
185+
op->func_dict = value;
186+
return 0;
187+
}
188+
189+
rtn = PyMember_Set((char *)op, func_memberlist, sname, value);
190+
if (rtn < 0 && PyErr_ExceptionMatches(PyExc_AttributeError)) {
191+
PyErr_Clear();
192+
if (op->func_dict == NULL) {
193+
op->func_dict = PyDict_New();
194+
if (op->func_dict == NULL)
195+
return -1;
196+
}
197+
rtn = PyDict_SetItem(op->func_dict, name, value);
198+
}
199+
return rtn;
142200
}
143201

144202
static void
@@ -150,6 +208,7 @@ func_dealloc(PyFunctionObject *op)
150208
Py_DECREF(op->func_name);
151209
Py_XDECREF(op->func_defaults);
152210
Py_XDECREF(op->func_doc);
211+
Py_XDECREF(op->func_dict);
153212
op = (PyFunctionObject *) PyObject_AS_GC(op);
154213
PyObject_DEL(op);
155214
}
@@ -227,6 +286,11 @@ func_traverse(PyFunctionObject *f, visitproc visit, void *arg)
227286
if (err)
228287
return err;
229288
}
289+
if (f->func_dict) {
290+
err = visit(f->func_dict, arg);
291+
if (err)
292+
return err;
293+
}
230294
return 0;
231295
}
232296

@@ -238,8 +302,8 @@ PyTypeObject PyFunction_Type = {
238302
0,
239303
(destructor)func_dealloc, /*tp_dealloc*/
240304
0, /*tp_print*/
241-
(getattrfunc)func_getattr, /*tp_getattr*/
242-
(setattrfunc)func_setattr, /*tp_setattr*/
305+
0, /*tp_getattr*/
306+
0, /*tp_setattr*/
243307
(cmpfunc)func_compare, /*tp_compare*/
244308
(reprfunc)func_repr, /*tp_repr*/
245309
0, /*tp_as_number*/
@@ -248,8 +312,8 @@ PyTypeObject PyFunction_Type = {
248312
(hashfunc)func_hash, /*tp_hash*/
249313
0, /*tp_call*/
250314
0, /*tp_str*/
251-
0, /*tp_getattro*/
252-
0, /*tp_setattro*/
315+
(getattrofunc)func_getattro, /*tp_getattro*/
316+
(setattrofunc)func_setattro, /*tp_setattro*/
253317
0, /* tp_as_buffer */
254318
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_GC, /*tp_flags*/
255319
0, /* tp_doc */

0 commit comments

Comments
 (0)