[Python-Dev] getting at the current frame

Barry A. Warsaw barry@wooz.org
Tue, 24 Oct 2000 16:11:54 -0400 (EDT)


I've been playing around with ideas for internationalizing Mailman,
which naturally <wink> leads to string interpolation.  To see why,
think about making the following code translatable:

    def trade(yours, mine):
	print 'if you give me %s, i will give you %s' % (yours, mine)

Because the order of the interpolated values may change in the
translated string, you really have to do something like:

    def trade(yours, mine):
	print 'if you give me %(yours)s, i will give you %(mine)s' % {
	    'yours': yours,
	    'mine' : mine,
	    }

which actually will look something like this in real code:

    def trade(yours, mine):
	print _('if you give me %(yours)s, i will give you %(mine)s') % {
	    'yours': yours,
	    'mine' : mine,
	    }

The string wrapped in _() is what gets translated here.

Okay, we all know that's a pain, right?  Lots of people have proposed
solutions.  I've looked briefly at !?ng's Itpl.py, but I think it
probably does too much by adding evaluation.

I can define _() to make the problem somewhat more convenient:

    def _(s):
	try:
	    raise Exception
	except Exception:
	    frame = sys.exc_info()[2].tb_frame.f_back
	d = frame.f_globals.copy()
	d.update(frame.f_locals())
	return the_translation_of(s) % d

Now I can write the code like this:

    def trade(yours, mine):
	print _('if you give me %(yours)s, i will give you %(mine)s')

All well and good and doable in Python today, except getting the
current frame with the exception raising trick is slooow.  A simple
proposed addition to the sys module can improve the performance by
about 8x:

    def _(s):
        frame = sys.getcaller(1)
	d = frame.f_globals.copy()
	d.update(frame.f_locals())
	return the_translation_of(s) % d

The implementation of sys.getcaller() is given in the below patch.
Comments?  I think this particular addition is too small for a PEP,
although ?!ng still owns PEP 215 (which needs filling in).

-Barry

-------------------- snip snip --------------------
Index: sysmodule.c
===================================================================
RCS file: /cvsroot/python/python/dist/src/Python/sysmodule.c,v
retrieving revision 2.78
diff -u -r2.78 sysmodule.c
--- sysmodule.c	2000/09/01 23:29:28	2.78
+++ sysmodule.c	2000/10/24 17:50:30
@@ -15,6 +15,8 @@
 */
 
 #include "Python.h"
+#include "compile.h"
+#include "frameobject.h"
 
 #include "osdefs.h"
 
@@ -284,6 +286,38 @@
 }
 #endif
 
+static char getcaller_doc[] =
+"getcaller([depth]) -> frameobject\n\
+\n\
+By default, return the frame object at the top of the call stack.  If an\n\
+integer depth is given, return the frame object that many calls below the\n\
+top of the stack.  If that is deeper than the call stack, a ValueError is\n\
+raised.";
+
+
+static PyObject *
+sys_getcaller(PyObject *self, PyObject *args)
+{
+	PyFrameObject *f = PyThreadState_Get()->frame;
+	int depth = -1;
+
+	if (!PyArg_ParseTuple(args, "|i:getcaller", &depth))
+		return NULL;
+
+	while (depth > 0 && f != NULL) {
+		f = f->f_back;
+		--depth;
+	}
+	if (f == NULL) {
+		PyErr_SetString(PyExc_ValueError,
+				"call stack is not deep enough");
+		return NULL;
+	}
+	Py_INCREF(f);
+	return (PyObject*)f;
+}
+
+
 #ifdef Py_TRACE_REFS
 /* Defined in objects.c because it uses static globals if that file */
 extern PyObject *_Py_GetObjects(PyObject *, PyObject *);
@@ -313,6 +347,7 @@
 	{"getrefcount",	sys_getrefcount, 1, getrefcount_doc},
 	{"getrecursionlimit", sys_getrecursionlimit, 1,
 	 getrecursionlimit_doc},
+	{"getcaller", sys_getcaller, 1, getcaller_doc},
 #ifdef USE_MALLOPT
 	{"mdebug",	sys_mdebug, 1},
 #endif