Commits

Jeremy Hylton  committed 856c81f

Backport fix for SF bug 692776.

Add a tp_new slot to function objects that handles the case of a
function requiring a closure. Put the function type in the new
module, rather than having a function new.function(). Add tests.

  • Participants
  • Parent commits 67d560d
  • Branches 2.2

Comments (0)

Files changed (3)

File Lib/test/test_new.py

 verify(g['c'] == 3,
        'Could not create a proper function object')
 
+# test the various extended flavors of function.new
+def f(x):
+    def g(y):
+        return x + y
+    return g
+g = f(4)
+new.function(f.func_code, {}, "blah")
+g2 = new.function(g.func_code, {}, "blah", (2,), g.func_closure)
+verify(g2() == 6)
+g3 = new.function(g.func_code, {}, "blah", None, g.func_closure)
+verify(g3(5) == 9)
+def test_closure(func, closure, exc):
+    try:
+        new.function(func.func_code, {}, "", None, closure)
+    except exc:
+        pass
+    else:
+        print "corrupt closure accepted"
+
+test_closure(g, None, TypeError) # invalid closure
+test_closure(g, (1,), TypeError) # non-cell in closure
+test_closure(g, (1, 1), ValueError) # closure is wrong size
+test_closure(f, g.func_closure, ValueError) # no closure needed
+
 print 'new.code()'
 # bogus test of new.code()
 # Note: Jython will never have new.code()

File Modules/newmodule.c

 	return PyMethod_New(func, self, classObj);
 }
 
-static char new_function_doc[] =
-"Create a function object from (CODE, GLOBALS, [NAME [, ARGDEFS]]).";
-
-static PyObject *
-new_function(PyObject* unused, PyObject* args)
-{
-	PyObject* code;
-	PyObject* globals;
-	PyObject* name = Py_None;
-	PyObject* defaults = Py_None;
-	PyFunctionObject* newfunc;
-
-	if (!PyArg_ParseTuple(args, "O!O!|OO!:function",
-			      &PyCode_Type, &code,
-			      &PyDict_Type, &globals,
-			      &name,
-			      &PyTuple_Type, &defaults))
-		return NULL;
-	if (name != Py_None && !PyString_Check(name)) {
-		PyErr_SetString(PyExc_TypeError,
-				"arg 3 (name) must be None or string");
-		return NULL;
-	}
-
-	newfunc = (PyFunctionObject *)PyFunction_New(code, globals);
-	if (newfunc == NULL)
-		return NULL;
-
-	if (name != Py_None) {
-		Py_XINCREF(name);
-		Py_XDECREF(newfunc->func_name);
-		newfunc->func_name = name;
-	}
-	if (defaults != Py_None) {
-		Py_XINCREF(defaults);
-		Py_XDECREF(newfunc->func_defaults);
-		newfunc->func_defaults  = defaults;
-	}
-
-	return (PyObject *)newfunc;
-}
-
 static char new_code_doc[] =
 "Create a code object from (ARGCOUNT, NLOCALS, STACKSIZE, FLAGS, CODESTRING,\n"
 "CONSTANTS, NAMES, VARNAMES, FILENAME, NAME, FIRSTLINENO, LNOTAB, FREEVARS,\n"
 	 METH_VARARGS, new_instance_doc},
 	{"instancemethod",	new_instancemethod,	
 	 METH_VARARGS, new_im_doc},
-	{"function",		new_function,		
-	 METH_VARARGS, new_function_doc},
 	{"code",		new_code,		
 	 METH_VARARGS, new_code_doc},
 	{"module",		new_module,		
 DL_EXPORT(void)
 initnew(void)
 {
-	Py_InitModule4("new", new_methods, new_doc, (PyObject *)NULL,
-		       PYTHON_API_VERSION);
+	PyObject *m;
+	m = Py_InitModule4("new", new_methods, new_doc, (PyObject *)NULL,
+			   PYTHON_API_VERSION);
+	if (m)
+		PyModule_AddObject(m, "function", &PyFunction_Type);
 }

File Objects/funcobject.c

 	{NULL} /* Sentinel */
 };
 
+static char func_doc[] =
+"function(code, globals[, name[, argdefs[, closure]]])\n\
+\n\
+Create a function object from a code object and a dictionary.\n\
+The optional name string overrides the name from the code object.\n\
+The optional argdefs tuple specifies the default argument values.\n\
+The optional closure tuple supplies the bindings for free variables.";
+
+/* func_new() maintains the following invariants for closures.  The
+   closure must correspond to the free variables of the code object.
+   
+   if len(code.co_freevars) == 0: 
+           closure = NULL
+   else:
+           len(closure) == len(code.co_freevars)
+   for every elt in closure, type(elt) == cell
+*/
+
+static PyObject *
+func_new(PyTypeObject* type, PyObject* args, PyObject* kw)
+{
+	PyCodeObject *code;
+	PyObject *globals;
+	PyObject *name = Py_None;
+	PyObject *defaults = Py_None;
+	PyObject *closure = Py_None;
+	PyFunctionObject *newfunc;
+	int nfree, nclosure;
+	static char *kwlist[] = {"code", "globals", "name",
+				 "argdefs", "closure", 0};
+
+	if (!PyArg_ParseTupleAndKeywords(args, kw, "O!O!|OOO:function",
+			      kwlist,
+			      &PyCode_Type, &code,
+			      &PyDict_Type, &globals,
+			      &name, &defaults, &closure))
+		return NULL;
+	if (name != Py_None && !PyString_Check(name)) {
+		PyErr_SetString(PyExc_TypeError,
+				"arg 3 (name) must be None or string");
+		return NULL;
+	}
+	if (defaults != Py_None && !PyTuple_Check(defaults)) {
+		PyErr_SetString(PyExc_TypeError,
+				"arg 4 (defaults) must be None or tuple");
+		return NULL;
+	}
+	nfree = PyTuple_GET_SIZE(code->co_freevars);
+	if (!PyTuple_Check(closure)) {
+		if (nfree && closure == Py_None) {
+			PyErr_SetString(PyExc_TypeError,
+					"arg 5 (closure) must be tuple");
+			return NULL;
+		}
+		else if (closure != Py_None) {
+			PyErr_SetString(PyExc_TypeError,
+				"arg 5 (closure) must be None or tuple");
+			return NULL;
+		}
+	}
+
+	/* check that the closure is well-formed */
+	nclosure = closure == Py_None ? 0 : PyTuple_GET_SIZE(closure);
+	if (nfree != nclosure)
+		return PyErr_Format(PyExc_ValueError,
+				    "%s requires closure of length %d, not %d",
+				    PyString_AS_STRING(code->co_name),
+				    nfree, nclosure);
+	if (nclosure) {
+		int i;
+		for (i = 0; i < nclosure; i++) {
+			PyObject *o = PyTuple_GET_ITEM(closure, i);
+			if (!PyCell_Check(o)) {
+				return PyErr_Format(PyExc_TypeError,
+				    "arg 5 (closure) expected cell, found %s",
+						    o->ob_type->tp_name);
+			}
+		}
+	}
+	
+	newfunc = (PyFunctionObject *)PyFunction_New((PyObject *)code, 
+						     globals);
+	if (newfunc == NULL)
+		return NULL;
+	
+	if (name != Py_None) {
+		Py_INCREF(name);
+		Py_DECREF(newfunc->func_name);
+		newfunc->func_name = name;
+	}
+	if (defaults != Py_None) {
+		Py_INCREF(defaults);
+		newfunc->func_defaults  = defaults;
+	}
+	if (closure != Py_None) {
+		Py_INCREF(closure);
+		newfunc->func_closure = closure;
+	}
+
+	return (PyObject *)newfunc;
+}
+
 static void
 func_dealloc(PyFunctionObject *op)
 {
 	PyObject_GenericSetAttr,		/* tp_setattro */
 	0,					/* tp_as_buffer */
 	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
-	0,					/* tp_doc */
+	func_doc,				/* tp_doc */
 	(traverseproc)func_traverse,		/* tp_traverse */
 	0,					/* tp_clear */
 	0,					/* tp_richcompare */
 	func_descr_get,				/* tp_descr_get */
 	0,					/* tp_descr_set */
 	offsetof(PyFunctionObject, func_dict),	/* tp_dictoffset */
+	0,					/* tp_init */
+	0,					/* tp_alloc */
+	func_new,				/* tp_new */
 };