]> granicus.if.org Git - python/commitdiff
Rewrite function attributes to use the generic routines properly.
authorGuido van Rossum <guido@python.org>
Mon, 17 Sep 2001 23:46:56 +0000 (23:46 +0000)
committerGuido van Rossum <guido@python.org>
Mon, 17 Sep 2001 23:46:56 +0000 (23:46 +0000)
This uses the new "restricted" feature of structmember, and getset
descriptors for some of the type checks.

Lib/test/test_funcattrs.py
Objects/funcobject.c

index 746c91ee31497f4bff5bd1aab8c80eb3df64198c..f4ee329e75e1d6fe0c6f4987cb55064fea3b714f 100644 (file)
@@ -1,4 +1,5 @@
-from test_support import verbose, TestFailed
+from test_support import verbose, TestFailed, verify
+import types
 
 class F:
     def a(self):
@@ -210,3 +211,159 @@ d[foo] = 1
 foo.func_code = temp.func_code
 
 d[foo]
+
+# Test all predefined function attributes systematically
+
+def test_func_closure():
+    a = 12
+    def f(): print a
+    c = f.func_closure
+    verify(isinstance(c, tuple))
+    verify(len(c) == 1)
+    verify(c[0].__class__.__name__ == "cell") # don't have a type object handy
+    try:
+        f.func_closure = c
+    except (AttributeError, TypeError):
+        pass
+    else:
+        raise TestFailed, "shouldn't be allowed to set func_closure"
+    try:
+        del a.func_closure
+    except (AttributeError, TypeError):
+        pass
+    else:
+        raise TestFailed, "shouldn't be allowed to del func_closure"
+
+def test_func_doc():
+    def f(): pass
+    verify(f.__doc__ is None)
+    verify(f.func_doc is None)
+    f.__doc__ = "hello"
+    verify(f.__doc__ == "hello")
+    verify(f.func_doc == "hello")
+    del f.__doc__
+    verify(f.__doc__ is None)
+    verify(f.func_doc is None)
+    f.func_doc = "world"
+    verify(f.__doc__ == "world")
+    verify(f.func_doc == "world")
+    del f.func_doc
+    verify(f.func_doc is None)
+    verify(f.__doc__ is None)
+
+def test_func_globals():
+    def f(): pass
+    verify(f.func_globals is globals())
+    try:
+        f.func_globals = globals()
+    except (AttributeError, TypeError):
+        pass
+    else:
+        raise TestFailed, "shouldn't be allowed to set func_globals"
+    try:
+        del f.func_globals
+    except (AttributeError, TypeError):
+        pass
+    else:
+        raise TestFailed, "shouldn't be allowed to del func_globals"
+
+def test_func_name():
+    def f(): pass
+    verify(f.__name__ == "f")
+    verify(f.func_name == "f")
+    try:
+        f.func_name = "f"
+    except (AttributeError, TypeError):
+        pass
+    else:
+        raise TestFailed, "shouldn't be allowed to set func_name"
+    try:
+        f.__name__ = "f"
+    except (AttributeError, TypeError):
+        pass
+    else:
+        raise TestFailed, "shouldn't be allowed to set __name__"
+    try:
+        del f.func_name
+    except (AttributeError, TypeError):
+        pass
+    else:
+        raise TestFailed, "shouldn't be allowed to del func_name"
+    try:
+        del f.__name__
+    except (AttributeError, TypeError):
+        pass
+    else:
+        raise TestFailed, "shouldn't be allowed to del __name__"
+
+def test_func_code():
+    def f(): pass
+    def g(): print 12
+    verify(type(f.func_code) is types.CodeType)
+    f.func_code = g.func_code
+    try:
+        del f.func_code
+    except (AttributeError, TypeError):
+        pass
+    else:
+        raise TestFailed, "shouldn't be allowed to del func_code"
+
+def test_func_defaults():
+    def f(a, b): return (a, b)
+    verify(f.func_defaults is None)
+    f.func_defaults = (1, 2)
+    verify(f.func_defaults == (1, 2))
+    verify(f(10) == (10, 2))
+    def g(a=1, b=2): return (a, b)
+    verify(g.func_defaults == (1, 2))
+    del g.func_defaults
+    verify(g.func_defaults is None)
+    try:
+        g()
+    except TypeError:
+        pass
+    else:
+        raise TestFailed, "shouldn't be allowed to call g() w/o defaults"
+
+def test_func_dict():
+    def f(): pass
+    a = f.__dict__
+    b = f.func_dict
+    verify(a == {})
+    verify(a is b)
+    f.hello = 'world'
+    verify(a == {'hello': 'world'})
+    verify(f.func_dict is a is f.__dict__)
+    f.func_dict = {}
+    try:
+        f.hello
+    except (AttributeError, TypeError):
+        pass
+    else:
+        raise TestFailed, "hello attribute should have disappeared"
+    f.__dict__ = {'world': 'hello'}
+    verify(f.world == "hello")
+    verify(f.__dict__ is f.func_dict == {'world': 'hello'})
+    try:
+        del f.func_dict
+    except (AttributeError, TypeError):
+        pass
+    else:
+        raise TestFailed, "shouldn't be allowed to delete func_dict"
+    try:
+        del f.__dict__
+    except (AttributeError, TypeError):
+        pass
+    else:
+        raise TestFailed, "shouldn't be allowed to delete __dict__"
+
+def testmore():
+    test_func_closure()
+    test_func_doc()
+    test_func_globals()
+    test_func_name()
+    test_func_code()
+    test_func_defaults()
+    test_func_dict()
+
+testmore()
index 57d02fefd91f81e3309968a0f6fc50728d1ab2fa..91a312759fc54b3406fbe25216aed0052ccc335c 100644 (file)
@@ -127,99 +127,145 @@ PyFunction_SetClosure(PyObject *op, PyObject *closure)
 
 #define OFF(x) offsetof(PyFunctionObject, x)
 
+#define RR ()
+
 static struct memberlist func_memberlist[] = {
-        {"func_code",     T_OBJECT,     OFF(func_code)},
-        {"func_globals",  T_OBJECT,     OFF(func_globals),      READONLY},
+        {"func_closure",  T_OBJECT,     OFF(func_closure),
+        RESTRICTED|READONLY},
+        {"func_doc",      T_OBJECT,     OFF(func_doc), WRITE_RESTRICTED},
+        {"__doc__",       T_OBJECT,     OFF(func_doc), WRITE_RESTRICTED},
+        {"func_globals",  T_OBJECT,     OFF(func_globals),
+        RESTRICTED|READONLY},
         {"func_name",     T_OBJECT,     OFF(func_name),         READONLY},
         {"__name__",      T_OBJECT,     OFF(func_name),         READONLY},
-        {"func_closure",  T_OBJECT,     OFF(func_closure),      READONLY},
-        {"func_defaults", T_OBJECT,     OFF(func_defaults)},
-        {"func_doc",      T_OBJECT,     OFF(func_doc)},
-        {"__doc__",       T_OBJECT,     OFF(func_doc)},
-        {"func_dict",     T_OBJECT,     OFF(func_dict)},
-        {"__dict__",      T_OBJECT,     OFF(func_dict)},
         {NULL}  /* Sentinel */
 };
 
+static int
+restricted(void)
+{
+       if (!PyEval_GetRestricted())
+               return 0;
+       PyErr_SetString(PyExc_RuntimeError,
+               "function attributes not accessible in restricted mode");
+       return 1;
+}
+
 static PyObject *
-func_getattro(PyObject *op, PyObject *name)
+func_get_dict(PyFunctionObject *op)
 {
-       char *sname = PyString_AsString(name);
-       
-       if (sname[0] != '_' && PyEval_GetRestricted()) {
-               PyErr_SetString(PyExc_RuntimeError,
-                 "function attributes not accessible in restricted mode");
+       if (restricted())
                return NULL;
-       }
-       /* If func_dict is being accessed but no attribute has been set
-        * yet, then initialize it to the empty dictionary.
-        */
-       if ((!strcmp(sname, "func_dict") || !strcmp(sname, "__dict__"))
-            && ((PyFunctionObject*)op)->func_dict == NULL)
-       {
-               PyFunctionObject* funcop = (PyFunctionObject*)op;
-               
-               funcop->func_dict = PyDict_New();
-               if (funcop->func_dict == NULL)
+       if (op->func_dict == NULL) {
+               op->func_dict = PyDict_New();
+               if (op->func_dict == NULL)
                        return NULL;
        }
-       return PyObject_GenericGetAttr(op, name);
+       Py_INCREF(op->func_dict);
+       return op->func_dict;
 }
 
 static int
-func_setattro(PyObject *op, PyObject *name, PyObject *value)
+func_set_dict(PyFunctionObject *op, PyObject *value)
 {
-       char *sname = PyString_AsString(name);
+       PyObject *tmp;
 
-       if (PyEval_GetRestricted()) {
-               PyErr_SetString(PyExc_RuntimeError,
-                 "function attributes not settable in restricted mode");
+       if (restricted())
+               return -1;
+       /* It is illegal to del f.func_dict */
+       if (value == NULL) {
+               PyErr_SetString(PyExc_TypeError,
+                               "function's dictionary may not be deleted");
+               return -1;
+       }
+       /* Can only set func_dict to a dictionary */
+       if (!PyDict_Check(value)) {
+               PyErr_SetString(PyExc_TypeError,
+                               "setting function's dictionary to a non-dict");
                return -1;
        }
-       if (strcmp(sname, "func_code") == 0) {
-               /* not legal to del f.func_code or to set it to anything
-                * other than a code object.
-                */
-               if (value == NULL || !PyCode_Check(value)) {
-                       PyErr_SetString(
-                               PyExc_TypeError,
+       tmp = op->func_dict;
+       Py_INCREF(value);
+       op->func_dict = value;
+       Py_XDECREF(tmp);
+       return 0;
+}
+
+static PyObject *
+func_get_code(PyFunctionObject *op)
+{
+       if (restricted())
+               return NULL;
+       Py_INCREF(op->func_code);
+       return op->func_code;
+}
+
+static int
+func_set_code(PyFunctionObject *op, PyObject *value)
+{
+       PyObject *tmp;
+
+       if (restricted())
+               return -1;
+       /* Not legal to del f.func_code or to set it to anything
+        * other than a code object. */
+       if (value == NULL || !PyCode_Check(value)) {
+               PyErr_SetString(PyExc_TypeError,
                                "func_code must be set to a code object");
-                       return -1;
-               }
+               return -1;
        }
-       else if (strcmp(sname, "func_defaults") == 0) {
-               /* legal to del f.func_defaults.  Can only set
-                * func_defaults to NULL or a tuple.
-                */
-               if (value == Py_None)
-                       value = NULL;
-               if (value != NULL && !PyTuple_Check(value)) {
-                       PyErr_SetString(
-                               PyExc_TypeError,
-                               "func_defaults must be set to a tuple object");
-                       return -1;
-               }
+       tmp = op->func_code;
+       Py_INCREF(value);
+       op->func_code = value;
+       Py_DECREF(tmp);
+       return 0;
+}
+
+static PyObject *
+func_get_defaults(PyFunctionObject *op)
+{
+       if (restricted())
+               return NULL;
+       if (op->func_defaults == NULL) {
+               Py_INCREF(Py_None);
+               return Py_None;
        }
-       else if (!strcmp(sname, "func_dict") || !strcmp(sname, "__dict__")) {
-               /* It is illegal to del f.func_dict.  Can only set
-                * func_dict to a dictionary.
-                */
-               if (value == NULL) {
-                       PyErr_SetString(
-                               PyExc_TypeError,
-                               "function's dictionary may not be deleted");
-                       return -1;
-               }
-               if (!PyDict_Check(value)) {
-                       PyErr_SetString(
-                               PyExc_TypeError,
-                               "setting function's dictionary to a non-dict");
-                       return -1;
-               }
+       Py_INCREF(op->func_defaults);
+       return op->func_defaults;
+}
+
+static int
+func_set_defaults(PyFunctionObject *op, PyObject *value)
+{
+       PyObject *tmp;
+
+       if (restricted())
+               return -1;
+       /* Legal to del f.func_defaults.
+        * Can only set func_defaults to NULL or a tuple. */
+       if (value == Py_None)
+               value = NULL;
+       if (value != NULL && !PyTuple_Check(value)) {
+               PyErr_SetString(PyExc_TypeError,
+                               "func_defaults must be set to a tuple object");
+               return -1;
        }
-       return PyObject_GenericSetAttr(op, name, value);
+       tmp = op->func_defaults;
+       Py_XINCREF(value);
+       op->func_defaults = value;
+       Py_XDECREF(tmp);
+       return 0;
 }
 
+static struct getsetlist func_getsetlist[] = {
+        {"func_code", (getter)func_get_code, (setter)func_set_code},
+        {"func_defaults", (getter)func_get_defaults,
+        (setter)func_set_defaults},
+       {"func_dict", (getter)func_get_dict, (setter)func_set_dict},
+       {"__dict__", (getter)func_get_dict, (setter)func_set_dict},
+       {NULL} /* Sentinel */
+};
+
 static void
 func_dealloc(PyFunctionObject *op)
 {
@@ -365,8 +411,8 @@ PyTypeObject PyFunction_Type = {
        0,                                      /* tp_hash */
        function_call,                          /* tp_call */
        0,                                      /* tp_str */
-       func_getattro,                          /* tp_getattro */
-       func_setattro,                          /* tp_setattro */
+       PyObject_GenericGetAttr,                /* tp_getattro */
+       PyObject_GenericSetAttr,                /* tp_setattro */
        0,                                      /* tp_as_buffer */
        Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
        0,                                      /* tp_doc */
@@ -378,7 +424,7 @@ PyTypeObject PyFunction_Type = {
        0,                                      /* tp_iternext */
        0,                                      /* tp_methods */
        func_memberlist,                        /* tp_members */
-       0,                                      /* tp_getset */
+       func_getsetlist,                        /* tp_getset */
        0,                                      /* tp_base */
        0,                                      /* tp_dict */
        func_descr_get,                         /* tp_descr_get */