Introduction
Suppose I have this C code:
#include <stdio.h>
// Of course, these functions are simplified for the purposes of this question.
// The actual functions are more complex and may receive additional arguments.
void printout() {
puts("Hello");
}
void printhere(FILE* f) {
fputs("Hello\n", f);
}
That I'm compiling as a shared object (DLL): gcc -Wall -std=c99 -fPIC -shared example.c -o example.so
And then I'm importing it into Python 3.x running inside Jupyter or IPython notebook:
import ctypes
example = ctypes.cdll.LoadLibrary('./example.so')
printout = example.printout
printout.argtypes = ()
printout.restype = None
printhere = example.printhere
printhere.argtypes = (ctypes.c_void_p) # Should have been FILE* instead
printhere.restype = None
Question
How can I execute both printout()
and printhere()
C functions (through ctypes
) and get the output printed inside the Jupyter/IPython notebook?
If possible, I want to avoid writing more C code. I would prefer a pure-Python solution.
I also would prefer to avoid writing to a temporary file. Writing to a pipe/socket might be reasonable, though.
The the expected state, the current state
If I type the following code in one Notebook cell:
print("Hi") # Python-style print
printout() # C-style print
printhere(something) # C-style print
print("Bye") # Python-style print
I want to get this output:
Hi
Hello
Hello
Bye
But, instead, I only get the Python-style output results inside the notebook. The C-style output gets printed to the terminal that started the notebook process.
Research
As far as I know, inside Jupyter/IPython notebook, the sys.stdout
is not a wrapper to any file:
import sys
sys.stdout
# Output in command-line Python/IPython shell:
<_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>
# Output in IPython Notebook:
<IPython.kernel.zmq.iostream.OutStream at 0x7f39c6930438>
# Output in Jupyter:
<ipykernel.iostream.OutStream at 0x7f6dc8f2de80>
sys.stdout.fileno()
# Output in command-line Python/IPython shell:
1
# Output in command-line Jupyter and IPython notebook:
UnsupportedOperation: IOStream has no fileno.
Related questions and links:
- Python ctypes: Python file object <-> C FILE *
- Python 3 replacement for PyFile_AsFile
- Using fopen, fwrite and fclose through ctypes
- Python ctypes DLL stdout
- Python: StringIO for Popen - Workaround for the lack of
fileno()
inStringIO
, but only applies tosubprocess.Popen
.
The following two links use similar solutions that involve creating a temporary file. However, care must be taken when implementing such solution to make sure both Python-style output and C-style output gets printed in the correct order.
- How do I prevent a C shared library to print on stdout in python?
- Redirecting all kinds of stdout in Python
Is it possible to avoid a temporary file?
I tried finding a solution using C open_memstream()
and assigning the returned FILE*
to stdout
, but it did not work because stdout
cannot be assigned.
Then I tried getting the fileno()
of the stream returned by open_memstream()
, but I can't because it has no file descriptor.
Then I looked at freopen()
, but its API requires passing a filename.
Then I looked at Python's standard library and found tempfile.SpooledTemporaryFile()
, which is a temporary file-like object in memory. However, it gets written to the disk as soon as fileno()
is called.
So far, I couldn't find any memory-only solution. Most likely, we will need to use a temporary file anyway. (Which is not a big deal, but just some extra overhead and extra cleanup that I'd prefer to avoid.)
It may be possible to use os.pipe()
, but that seems difficult to do without forking.
I've finally developed a solution. It requires wrapping the entire cell inside a context manager (or wrapping only the C code). It also uses a temporary file, since I couldn't find any solution without using one.
The full notebook is available as a GitHub Gist: https://gist.github.com/denilsonsa/9c8f5c44bf2038fd000f
Part 1: Preparing the C library in Python
Part 2: Building our own context manager to capture stdout
Part Fun: Using it!
Output:
Credits and further work
This solution is fruit of reading all the links I linked at the question, plus a lot of trial and error.
This solution only redirects
stdout
, it could be interesting to redirect bothstdout
andstderr
. For now, I'm leaving this as an exercise to the reader. ;)Also, there is no exception handling in this solution (at least not yet).