[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--