Re: accessing embedded Python in C/C++

Guido.van.Rossum@cwi.nl
Tue, 31 May 1994 22:34:03 +0200

> I have found and read the "Extending and Embedding the Python Interpreter"
> doc but have found it to be skimpy and confusing. Perhaps an example or two
> would help. The demo.c just ran commands but it does not allow C to
> access any results.

Maybe this rather negative opening paragraph (which you repeat now for
the third time) made me ignore your previous requests for help, or
rather give them a low priority. Think about it...

There are many examples of how to access Python data structures in the
Modules subdirectory. Most examples involve accessing arguments
passed to functions/methods written in C; I'll come to other ways of
getting access to Python objects later. One concrete example: assume
you are passed a list of points (represented as tuples of floating
point numbers) and you want to get access to the individual point'
values. Here's an example that just loops over the points and prints
them -- I'm sure you can deduce how to do other things to them. Let's
assume 'args' is the list.

int
printpoints(list)
object *list;
{
int n, i;
double x, y;
item point;

if (list == NULL || !is_listobject(list)) {
err_setstr(TypeError, "list expected");
return -1;
}
n = getlistsize(list);
for (i = 0; i < n; i++) {
object *item;
object *xo, *yo;

item = getlistitem(list, i);
if (!is_tupleobject(item) || gettuplesize(item) != 2) {
err_setstr(TypeError, "pair expected");
return -1;
}
xo = gettupleitem(item, 0);
yo = gettupleitem(item, 1);
if (!is_floatobject(xo) || !is_floatobject(yo)) {
err_setstr(TypeError, "float expected");
return -1;
}
printf("x = %g, y = %g\n",
getfloatvalue(xo), getfloatvalue(yo));
}

return 0; /* Success */
}

The functions and macros used here (like is_listobject(),
getlistsize() and getlistitem()) are not well documented -- this is
planned for a future of the extending/embedding document, but hasn't
been done yet for lack of time. Their interface can easily be glanced
from the corresponding header files (Include/listobject.h in this
case) plus examples or their implementation.

A quicker but less educational way of writing the body of the for loop
would be:

object *item = getlistitem(list, i);
double x, y;
if (!getargs(item, "(dd)", &x, &y))
return -1; /* getargs() sets an appropriate exception */
printf("x = %g, y = %g\n", x, y);

(This has the advantage of automatically converting ints to floats as
well.)

Now what you seem to want is to execute a piece of Python code that
you got as a string from a user, and then access variables set by the
user. If you look in the implementation of run_command(), you see
that there's a lower-level function run_string which takes a strin
(command)g, a grammar start symbol (file_input, defined in
graminit.h), and two dictionaries (d, d). The latter represent the
global and local dictionaries used by the executed code (usually these
are the same). Note that this is the C equivalent of the Python
statement

exec command in globals, locals

If you know in what variables the user's code is supposed to store its
results, say 'answer', you can extract them from the dictionary
containing the local variables (assignments always go into the locals
unless the users uses the 'global' statement). For example

object *answer_in_c;
object *d, *res;
d = newdictobject();
res = run_string(command, file_input, d, d);
if (res == NULL) {
...an exception happened in the user's code...
return ...;
}
answer_in_c = dictlookup(d, "answer");
printpoints(answer);

(Some error checking left out here, e.g. newobject() and printpoints()
could also raise exceptions.)

> Likewise: answer_in_c = (object *)get_python_value("stuff.fcn(xyz)");
> should evaluate the quoted string and return whatever that is.

The trick here is to call run_string() with a different start symbol
(eval_input), it then returns the value of the expression.
> There are other problems with a GUI interface as well:
> 1. The tty loop needs to have output to stdout/stderr killed,
> or at least optionally directed to a log file.

To suppress all writes to sys.stdout, you could assign an instance of
the following class to it:

class devnull:
def write(self, str):
pass

Likewise for sys.stderr. To redirect, assign something like
open('logfile', 'w').

> 2. Blocking for input is totally unacceptable in a GUI, the
> event loop must continue. It would be acceptable to return
> a status indicating more input needed.

Tell the user not to call f.read() or f.readline() where f is
sys.stdin, nor to use the input() and raw_input() functions. You
could "sabotage" sys.stdin and these two functions to enfore this.
Not many other functions block waiting for input (time.sleep() and
select.select() are fairly system-oriented -- users who call them
should know what they are doing).

> 3. stdin/"input" calls must be isolated and possibly
> redirected. While our current idea is to load from a
> previously existing script, furture extensions might
> allow various scenarios.

See above. In practice, input() and raw_input() are rarely used so
it's fine to just disable them (note that if you want you can assign
to __builtin__.input etc.). Unless you have threads, suspending the
interpreter in input() without blocking the rest of the GUI is
difficult (you could try to run the GUI's mainloop recursively from
your version of input() but that seems unattractive).

Hope this gets you on the road again,

--Guido van Rossum, CWI, Amsterdam <Guido.van.Rossum@cwi.nl>
URL: <http://www.cwi.nl/cwi/people/Guido.van.Rossum.html>