Introduction
IronPython is an implementation of Python for .NET. It provides full access to .NET libraries through the Python programming language, while also providing access to standard Python libraries. While it can be used to write standalone applications, this article is about using it as a scripting language for your application.
Why a Scripting Language?
- Change functionality at run time
There could be lots of reasons for this - maybe every one of your customers want your application to behave differently and writing and shipping plugins is too much work. Or your customers are developers and they want the ability to change how the app behaves and customize it with immediate feedback, without having to recompile the app or plugins. Or, you are testing some functionality and want the application to behave differently at different times, without breaking into a debugger.
- Provide "extra' functionality
You're running your application and you notice that it's eating up memory. You decide to run it through a memory profiler, but before that, you want to run a full GC so that the profiler only picks up live objects. Wouldn't it be great if there was some way to call GC.Collect
? Or let's say you have a complicated UI that requires a lot of navigation and you want to spare developers the trouble of going through the UI every single time. Wouldn't it be nice to have something that would automatically do whatever is necessary and help developers get to the interesting part quickly?
- Live testing
Unit testing is great for testing out individual components separately, but wouldn't it be nice to have the ability to write tests that could run on a live system? Like say, when the user clicks button X and the software is in state Y, run test Z? Or do UI testing like filling out textboxes and clicking buttons automatically?
- Poking around live systems
When you are stuck without a debugger and want to check the state of the system (like the value of a class member, for example), wouldn't it be great to have the ability to just type that into a command window and see the result right away?
Integrating IronPython
IronPython is incredibly easy to integrate. You'll need to:
- Add a reference to IronPython.dll from your application
- Instantiate a
PythonEngine
object - Add the objects that you want to expose via scripting to
pythonEngine.Globals
- Redirect standard output and error streams so you can show errors and script output
The following snippet of code shows how it has been done in the attached sample application:
public Form1()
{
InitializeComponent();
InitializeIronPython();
}
private void InitializeIronPython()
{
Options.PrivateBinding = true;
pythonEngine = new PythonEngine();
MessageBoxStream s = new MessageBoxStream();
pythonEngine.SetStandardError(s);
pythonEngine.SetStandardOutput(s);
pythonEngine.AddToPath(AppDomain.CurrentDomain.BaseDirectory);
pythonEngine.Globals.Add("form", this);
pythonEngine.Globals.Add("bl", bl);
}
MessageBoxStream
is a class deriving from System.IO.Stream
that shows whatever is written to the stream using MessageBox.Show
. The pythonEngine.Globals.Add
method exposes your objects as global variables to the scripts. The first parameter is a string
that translates to the variable name in the script, and the second parameter is the object corresponding to that variable. The sample application exposes the Form
object and a business logic object to the script.
The sample application also has a "command prompt" at the bottom, which is actually a textbox
, to execute live Python code. This is how that code looks like:
private void commandPrompt_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == '\r')
{
try
{
pythonEngine.ExecuteToConsole(commandPrompt.Text);
commandPrompt.Clear();
}
catch(Exception e)
{
MessageBox.Show(e.ToString());
}
}
}
It simply forwards the text to the ExecuteToConsole
method on the pythonEngine
object.
Using IronPython
The attached project demonstrates how to do things mentioned in the previous section. It's a pretty contrived application - it has two input textbox
es and an output textbox
. There are three buttons, "Add", "Subtract" and "Custom". As you would expect, the Add button adds the numbers in the input textbox
es and writes it to the output, Subtract subtracts them and Custom allows people to write custom actions. Let's go through the points mentioned in the previous section and see how they can be done using IronPython.
Change Functionality at Run Time
The "Custom" button executes a script named custom.py in the application's directory. That script gets the input textbox
es and a reference to the Form
instance, which it can use to do whatever custom action it wants. IronPython allows you to control what objects the script can reference.
private void customButton_Click(object sender, EventArgs e)
{
Dictionary<string,object /> locals = new Dictionary<string,object />()
{
{"input1", input1},
{"input2", input2},
};
pythonEngine.ExecuteFile("custom.py", pythonEngine.DefaultModule, locals);
}
The custom.py script automatically gets the global objects added to the pythonEngine
object. In addition to them, you can pass a dictionary of name/object pairs as local variables for that script. The script file can then access them like:
def append(x, y):
return ''.join([x,y])
form.ShowResult(int(append(input1.Text, input2.Text)))
This script simply concatenates the text in the input textbox
es, converts the concatenated result to a number and calls the Form
object's ShowResult
.
As another example, type the following command:
form.TransformResult = lambda x : '-'.join(str(x))
Then do an Add or Subtract - you'll notice that the result has hyphens between the digits.
If you look at the code for ShowResult
, you'll see that it calls the TransformResult
delegate (if available) before setting the resultTextBox
's Text
property. The above snippet of code creates an anonymous function that converts the passed number to a string
and then inserts a hyphen between each character. That anonymous function is assigned to the TransformResult
delegate of the Form
object, which it then invoked when you clicked the Add Button.
Provide "Extra" Functionality
You can simply type GC.Collect
in the command prompt textbox
and hit Enter and have the CLR do a GC right away.
For a more interesting example, let's say that you as a developer would like to know the results generated in the current session. Assume that this is something that you wouldn't want to write as part of the application. With IronPython, you could do something like:
resultTextBox = form.Controls.Find('result', True)[0]
results = []
def fn(arg1, arg2) : results.append(resultTextBox.Text)
resultTextBox.TextChanged += fn
You could then type...
results
... at any point in the command prompt to get the results calculated so far. The script basically subscribes to the TextChanged
event of resultTextBox
and appends the contents to a list.
Live Testing
Using the Controls.Find
method shown in the previous section, you can get a reference to any UI object and then manipulate it.
input1TextBox = form.Controls.Find('input1', True)[0]
input2TextBox = form.Controls.Find('input2', True)[0]
resultTextBox = form.Controls.Find('result', True)[0]
input1TextBox.Text = '1'
input2TextBox.Text = '2'
addButton = form.Controls.Find('addButton', True)[0]
addButton.PerformClick()
print int(resultTextBox.Text) == 3
print int(resultTextBox.Text) == 4
As you can see, this piece of code gets references to the UI objects, sets values, executes an action and then verifies the result. You could also choose to expose the UI objects by adding them to PythonEngine.Globals
, instead of using Control.Find
to get them.
Poking Around Live Systems
You have already seen that you can access UI objects and manipulate them. You can do...
form.Text = 'IronPython'
... to change the form
's title, for example. You can also access business logic components, if you added them to the PythonEngine. The sample application exposes the BusinessLogic
object it holds with the name bl
, so you can now access its state too. It holds the last operation's type as a member, so you can do...
bl.LastOperation
... to view the last executed operation.
Conclusion
I hope this article got you interested in IronPython and integrating scripting into your application. The IronPython project is hosted at CodePlex, so that's the place to go to download and learn more about it. The command prompt in the sample application is very rudimentary and there are alternatives like IronTextBox that do a much better job. You could also add "meta" commands to your prompt that allow you to execute arbitrary python files. And please keep in mind that the attached application is a sample application, intended to demonstrate IronPython integration, so if the code is missing error checking, you know why.
History
- 3:39 PM 3/16/2008: Initial post