3

I am trying to embed an interactive Python shell in a C# Windows Forms application, using Python.NET.

I was able to embed the interpreter in my C# application and access Python modules from .NET. I am now trying to redirect the output and errors from the Python interpreter to a .NET text box. While, I am able to redirect the standard output to a file, I am having trouble with routing the output to a text box.

This is what I have tried so far: The idea was to assign Python's sys.stdout to a .NET object that implements the same interface as a python stream (write(), writelines()...):

.NET class to mimic Python stream:

public class TextBoxStream : PyObject // To assign to sys.stdout. Is this correct?
{
    private TextBox _output = null;            

    public TextBoxStream() {}

    public TextBoxStream(TextBox output)
    {
        _output = output;
    }

    void write(object value)
    {
        _output.AppendText(value.ToString());
    }

    void writelines(object value)
    {
    }
}

In Form1.cs:

private void button1_Click(object sender, EventArgs e)
{
    using (Py.GIL())
    {
        // Redirect stdout to text box
        dynamic sys = PythonEngine.ImportModule("sys");
        TextBoxStream textBoxStream = new TextBoxStream(textBox1);
        sys.stdout = textBoxStream;
        //sys.SetAttr("stdout", textBoxStream); // This did not work either
        string code =
            "import sys\n" +
            "print 'Message 1'\n" +
            "sys.stdout.write('Message 2')\n" +
            "sys.stdout.flush()";

        PyObject redirectPyObj = PythonEngine.RunString(code); // NULL
        sys.stdout.write("Message 3"); 
        // Exception thrown: 'Python.Runtime.PyObject' does not contain a definition for 'stdout'
    }
}

This does not work either: redirectPyObj is NULL. I tried using the old as well as the new Python.NET API (with dynamic). Neither the sys.stdout.write nor the print statements write to the text box.

Any ideas on how to approach this would be very helpful.

denfromufa
  • 5,610
  • 13
  • 81
  • 138
Spry
  • 688
  • 8
  • 16
  • 1
    have you tried this approach? http://stackoverflow.com/a/4307737/2230844 Essentially you redirect python stdout to python variable and then query this variable from .NET. – denfromufa May 20 '16 at 21:27
  • While I was able to get stdout redirection using a similar approach, I was having trouble with stderr redirection. Your linked answer helped me realize that I first needed to make the PythonEngine write out the errors using PyErr_Print(). Thanks! – Spry May 27 '16 at 18:30
  • 2
    @denfromufa, I hope you don't mind that I posted an answer to close out the question. It might help someone else looking for a C# solution. – Spry May 27 '16 at 21:30
  • absolutely fine! thanks for posting working example! – denfromufa May 27 '16 at 21:32

2 Answers2

2

A solution that worked for me was redirecting the Python stdout/stderr to a stream in Python. I was then able to route this stream into the .NET text box.

private void button1_Click(object sender, EventArgs e)
{
    using (Py.GIL())
    {
        // Redirect stdout to text box
        dynamic sys = PythonEngine.ImportModule("sys");

        string codeToRedirectOutput =
            "import sys\n" +
            "from io import StringIO\n" +
            "sys.stdout = mystdout = StringIO()\n" +
            "sys.stdout.flush()\n" +
            "sys.stderr = mystderr = StringIO()\n" +
            "sys.stderr.flush()\n";
        PythonEngine.RunString(codeToRedirectOutput);            

        // Run Python code
        string pyCode = "print(1 + 2)";
        PyObject result = PythonEngine.RunString(pyCode); // null in case of error
        if (result != null)
        {
            string pyStdout = sys.stdout.getvalue(); // Get stdout
            pyStdout = pyStdout.Replace("\n", "\r\n"); // To support newline for textbox
            textBox1.Text = pyStdout;             
        }
        else
        {
             PythonEngine.PrintError(); // Make Python engine print errors
             string pyStderr = sys.stderr.getvalue(); // Get stderr
             pyStderr = pyStderr.Replace("\n", "\r\n"); // To support newline for textbox
             textBox1.Text = pyStderr;
        }     
    }
}

With this code, I was able to redirect the stdout (and stderr in the case of an error) from the Python engine to a .NET text box.

Spry
  • 688
  • 8
  • 16
1

Python 3.4 introduced contextlib.redirect_stdout() in the standard library. contextlib.redirect_stderr() was added in Python 3.5. https://docs.python.org/3/library/contextlib.html#contextlib.redirect_stdout

These context managers allow for neat redirection of stderr and stdout in Python code like this:

import contextlib
import sys
import io
...
s = None
# Redirect stderr to stdout
with contextlib.redirect_stderr(sys.stdout):
    # Redirect stdout to a buffer
    with contextlib.redirect_stdout(io.StringIO()) as f:
        ...
        <your code>
        ...
        # Fetch any redirected stdout/stderr messages
        s = f.getvalue()
# Print them or whatever you like
print(s)

The same context managers are available in Python.Net The equivalent C# code is:

dynamic contextLib = Py.Import("contextlib");
dynamic ioLib = Py.Import("io");
dynamic sysLib = Py.Import("sys");
string pyStdOut = null;

Py.With((PyObject) contextLib.redirect_stderr(sysLib.stdout), _ =>
{
    Py.With((PyObject) contextLib.redirect_stdout(ioLib.StringIO()), f =>
    {
        ...
        <your code>
        ...
        pyStdOut = f.getvalue();
    });
});

pyStdOut = pyStdOut.Replace("\n", "\r\n");
Russel
  • 41
  • 3