Introducing Python for .NET - IronPython
Posted by Superadmin on November 20 2019 14:15:04

Introducing Python for .NET - IronPython

 

 

Python is one of the increasingly trendy dynamic languages and it is now available under the .NET umbrella. IronPython is an open source version of the language developed by Guido van Rossum in 1990. Python has a great many users and they are all passionate about the language and mostly about Monty Python as well. Yes, there are lots of Pythonesque (as in the well-known TV series) references in the Python world, but it is a very serious language as well! It currently runs on most platforms, and IronPython extends this range to .NET and Silverlight.

Why bother?

Python may be free and it may be a high quality language but so is C#. Why bother with a strangely named language that you probably haven’t heard very much about? One good reason is that it is an interactive object-oriented language that has managed to take the good ideas from many other languages and meld them together into something workable. If you have looked at early versions of IronPython, now is a good time to take another look because version 2.0 has just been released and this is based on the new Dynamic Language Runtime (DLR) which all of the .NET dynamic languages should be using quite soon. The DLR is an extension to the CLR designed to make it easy to implement dynamic languages. The best argument for Python, however, is to use it – so let’s get started.

Getting IronPython

The main Python website is www.python.org, and if you do become a fan then you will certainly need to visit it for the documentation and the code. There is also a standard Windows, i.e. not .NET, implementation of Python that you can download from the site. If you do want the .NET version your first task is to make sure that the machine on which you plan to install it has the latest .NET Framework already (it has to be at least .NET 2.0 SP1). Next go to the main IronPython site and download and install IronPython. The latest version is 2.0.1 and the standard msi installer is easy to use. At this point you have a working IronPython compiler/interpreter which you can use via the IronPython console. The console is a fun interactive way of using IronPython. You can type in lines of code and have them executed immediately and in many ways this is in the spirit of a dynamic language. This said, many will have grown so accustomed to the use of Visual Studio and similar IDEs that Python without an IDE is unthinkable. Although it might seem that you need to install a full version of Visual Studio to use IronPython with an IDE, there is a free download that you can make use of. First you need to download and install MS VS 2008 Shell Isolated Mode Redistributable. This provides a basic Visual Studio IDE that can be customised to support special projects and languages. Notice that to complete the installation you have to go to the download folder and run the setup “vsshellisolated_enu.exe” to actually install the redistributable runtime – it doesn’t happen as part of the download. Once you have the VS Shell installed you simply download and install IronPython Studio and you have a complete and ready to use Python IDE. All of the above can be found at ironpythonstudio.codeplex.com.

Screenshot
IronPython also works in Silverlight

Getting started

In this case the “hello world” example is trivial. Start up IronPython Studio and start a new console application. This comes complete with the demo code:

hello = "Hello VSX!!!"
print hello

Which is actually slightly more Python than you need to create a first program. Change the code to:

print 'Hello World'
a=raw_input('Press return to continue')

If you now run the program you will see the messages displayed in the console and the program will continue when you press return.

The first thing to notice is that the Python statements don’t have line separators – no semi-colon at the end of each line. This is a reflection of the fact that Python handles white space in a way that might be unfamiliar to C#/C++ programmers – significant whitespace. Yes, that’s correct, the whitespace that you leave in a Python program means something. This goes much further than VB’s use of whitespace and you need to know that while the start of a code block is marked by a colon the end is marked by indentation. For example, Python has a fairly standard for statement:

for i in range(1,11):
    print i
print 'end of loop'

This prints the values 1 to 10 but notice that the block of code that is iterated starts with the colon and includes anything indented below the for. The arguments for and against significant whitespace are many but at least there are no arguments about how Python should be laid out. If you do it your own way then the chances are it won’t work! Python has the usual range of control statements, while, if, etc., and the only real difference is the use of the colon to mark the start of a block and indenting to show where the block ends.

No typing

The key thing about dynamic languages is that they treat variable type in as flexible way as possible. You can generally assign anything to anything and hope it makes sense. Python always attempt to work out what type of data a variable is storing and match the operation to this. This is often called “duck typing” from the old saying “if it looks like a duck, quacks like a duck, etc., then it is a duck”. If you have been schooled in strong typing and brought up on C++ or C# then duck typing will seem like an error waiting to happen and it is true. Strong typing was invented to try and make programming more error free, but it takes time and effort and dynamic or duck typing is quick and makes for efficient code generation.

To define a function you use the def command, complete with parameters if needed, and a return statement to return a value. For example:

def add(x,y):
    return x+y
print(add(1,2))

If you are using IronPython Studio then you have to actually enter the tab to start the body of the function. Notice that parameters are typeless and this is both a convenience and a possible problem. For example, with the above definition of add the following works perfectly:

print(add('lumber','jack'))

…and prints “lumberjack”.

If you find this strange and worrying then it is perhaps because you have been in the grip of strongly typed languages for too long! When you write the add function you are thinking about “adding” together two objects and it is up to the system to work out what you mean by “+” for any two objects you care to pass. Notice that strongly typed languages such as C# have had to invent the idea of “generics” to allow the general concept of adding two things together to be used in methods irrespective of the exact type of the object passed to the method. If you aren’t being too fussy about typing then generics come for free and quite naturally – the only problem is that you might write a function that does a little more than you ever intended.

It even works for lists – in fact it works for any two objects that makes sense of the + operator:

print(add([1,2],[3,4]))

…which prints [1, 2, 3, 4].

Notice that Python doesn’t have arrays – the list seems to be enough for any purpose – but you can make use of .NET array objects. Exactly how is our next subject.

One more complication is that we need to know the scope of a function or a variable definition. The basic idea is that a name belongs to the namespace that is appropriate to where it is first used – local, global or built in. If you want to make a variable name global from within a function you have to declare it as such using “global”. Arguments are also influenced by the “object” idea. These are passed into functions by assigning them to local names, i.e. the objects that are passed are simply given new references to them within the function. This results in a behaviour which many programmers will regard as strange. For example suppose you write:

def test(a):
    a=1

Then when you use this function with a call like:

x=2
test(x)

…the value of x is unchanged by the assignment within the function. Ah, you might say, this is pass by value, but not so. What is happening is that when the function is called the local variable “a” is set to “point” to the same object that “x” is “pointing” at. When the object 1 is assigned to “a” the variable “x” is still pointing at the object 2. You might think that this is indeed pass by value but it isn’t because if you try:

def test(a):
a[0]= "spam"

…you will discover that the list in the calling program has its first item changed to “spam”. The reason for the difference is that you have changed the object that both variables are pointing at. There are also facilities for passing arguments by name, variable numbers of parameters and parameter defaults. For Lisp enthusiasts Python has a lambda expression facility, which allows any expression to be treated essentially as a data object.

Objects

Moving on to more traditional object-oriented facilities brings us to the class statement. You can define a class complete with variables and functions and create instances of the class by assignment. This sounds all too easy and I have to admit that it really is easy. The only minor complication is that you have to use the keyword “self” as a reference to the particular instance of the class. Consider, for example:

class point:
    def setpos(self,x,y):
    	self.position=(x,y)
    def display(self):
    	print self.position

This defines a class called point which has two methods – setpos and display. Each of these has the default parameter “self” included. The methods use “self” to qualify any variables that are specific to the instance of the class. If you now create an instance of the class using:

a=point()

…you can refer to methods using the usual a.method() notation. For example,

a.setpos(1,2)

…and:

a.display()

Notice that the brackets are all-important – without them you create variables pointing to the class, not the instance. This is a big difference between Python and other object-oriented languages – the class is actually an object that is created on an almost equal footing with its instances. You can create class data, for example, by using variables outside of methods.

If you use a class definition of the form:

class name(superclass):

…then the new class inherits all of the methods defined in superclass. For example:

class complexpoint(point)

…defines a new class called complexpoint and inherits all of the methods defined in point. You can, of course, define new methods and override existing methods. If a method cannot be found in the class’s definition the superclass definition is searched. If the method cannot be found in the superclass then any super-superclass that might exist is searched, and so on until a definition is found or an error is generated. You can also arrange to call the inherited method directly using the class name as the first parameter of the call. In fact all method calls are translated from:

instance.method(parameters)

…to:

class.method(instance,parameters)

…and the first parameter is “self”. You can call instance methods directly using this form. For example:

point.setpos(x,1,2)

…is entirely equivalent to:

x.setpos(1,2)

Finally it is worth knowing that operator overloading is supported using a range of standard method names. For example, if you want to define an addition for points then you need the following method:

def __add__(self,other):
    self.ans=(self.pos[0]+other.pos[0],
    	self.pos[1]+other.pos[1])
    return self.ans

Now you can write:

z=x+y

…where x and y are point objects. Notice that z isn’t a point object but a tuple. If you want to return a point object you have to use:

ans=point()
ans.setpos(self.pos[0]+other.pos[0],
    self.pos[1]+other.pos[1])
return ans

Overriding operators might not seem like something you want to do but after using Python for a while I can assure you that you will – if only to write constructors. The init operator is the objects constructor and if you overwrite it you can define a non-default constructor. For example, if you include:

def __init(self,x=0,y=0):
    self.pos=x
    self.pos=y

…in the point definition you can now initialise a point using:

x=point(1,2)

…and if you don’t specify a value then a (0,0) point is constructed by default. There is also a destructor operator, indexing operator, string slicing operator and so on, all of which can be overloaded.

Screenshot
IronPython in action with WPF

.NET interop

The whole point of using IronPython as opposed to just Python is that it promises access to .NET and what this really means is access to the .NET Framework. To see this aspect of IronPython in action the best thing to do is to start a new Windows Forms project. As long as you are using IronPython Studio you will see the familiar drag-and-drop designer. Place a textbox and a button on the form and double click on it. You will be automatically transferred to the code editor ready to fill out the click event handler stub. If you use code something like:

@accepts(Self(), System.Object,
    System.EventArgs)
@returns(None)
def _button1_Click(self, sender, e):
    self._textBox1.Text='Hello World'

…then the program will display the message in the textbox when you click on the button. Notice that you can use the .NET objects just as if they were Python objects and the only real difference is that you have to remember to make use of the self keyword to make sure that the event handler uses the instance that the event was triggered by.

Of course quite a bit of the code that was needed to make everything work was generated by the template used to create the project. To make use of any .NET classes you have to first import them using the import statement. Specifically you have to import the special System module that connects IronPython to the .NET classes. If you simply use import System you have to use fully qualified class names but you can also import namespaces so that you can use short names. For example the standard Windows Forms template uses:

import System
from System.Windows.Forms import *
from System.ComponentModel import *
from System.Drawing import *
from clr import *

Once you have imported a .NET class you can use it in Python style, i.e. you don’t need a “new” to create an instance. For example:

a=System.String("Hello World")
self._textBox1.Text=a

…creates a .NET String which can be assigned to any .NET property that accepts a String. If you don’t want to use a fully qualified name then you can use:

from System import String
a=String("Hello World")

What is perhaps less obvious is that once you import a .NET class it can mix with the pure Python classes. For example, if you use a Python string and don’t import System then the string doesn’t support the usual .NET string methods such as ToUpper. That is:

c="Hello World"
self._textBox1.Text=c.ToUpper()

…will fail as a Python string object doesn’t have a ToUpper Method. However, if you try the same thing after importing System you will discover that it does work. In other words, importing a .NET class can change the behaviour of your program. This is very odd and something you need to be aware of.

WPF

The fact that the forms designer works for a Windows form application might seem surprising and very welcome but what is perhaps more surprising is that the WPF form designer works in a WPF application. What is more, you can use XAML to create interfaces. If you think about it for a few minutes this seems less of a surprise when you remember that XAML is simply a language for specifying how objects should be instantiated at run time. If it works for one language it should work for any language and indeed it does. However this is where the good news ends as things in WPF world seem to be difficult at the moment. There is no way of making use of the XAML namespace within an IronPython program. What this means is that it is difficult to simply refer to a control that you have placed on the form by using its name. Currently there are a number of solutions but the simplest is to use the standard .NET methods for finding a control at run time.

If you would like to try a WPF project it is important that you first modify the Machine.config file to set the codedom handler. Without this change no WPF project is likely to compile and work. The instructions on how to do this are included in the ReadMe that accompanies the IronPython Studio installation. Add the additional lines to the end of the configuration file before the tag. Try a default WPF project for correct compilation before moving on to something more complicated.

To get started with WPF start a new WPF project and use the designer to add a button and a textbox. Double click on the button and you will be transferred to an event handler. At this point you might be tempted to try to work with names such as Button1 and TextBox1 but if you breakpoint the application you will discover that these names are not properties of the form. The controls are dynamically created at runtime and to find a reference to any given control the simplest solution is to use the LogicalTreeHelper object and its FindLogicalNode method. This will return a reference to the control with a specific name that is a child of the initial control. So for example:

LogicalTreeHelper.FindLogicalNode(
    self.Root, 'textBox1')

…returns a reference to a child control of the form, i.e. the control reference stored in self.Root with the unique name ‘textBox1’. Once you have the reference to the control you can work with it as normal. For example:

@accepts(Self(), System.Object, System.Windows.RoutedEventArgs)
@returns(None)
def _button1_Click(self, sender, e):
    textBox1=LogicalTreeHelper.FindLogicalNode(
    	self.Root, 'textBox1')
    textBox1.Text="Hello World"

The only problem with this approach is that you don’t get any help from Intellisense prompting for properties of the WPF controls. It would be worth writing a method that initialised properties for all of the controls defined by the XAML code, but this is more difficult than you might think. Notice that the variable used in the event handler is actually local to the event handler. If you try to save the control lookup for later use, say, by using self.textBox1 you will discover that you can’t dynamically add a new property to the Window class. The reason is that the WPF Window class is a .NET class, not a Python class, and hence not dynamic. There are ways around this problem but nothing simple and neat.

Where next?

Python is an interesting language that has many powerful features that we haven’t had space to touch on. In particular you can use COM interop from IronPython and you can use Python libraries, two features which extend the language’s usefulness. IronPython is surprisingly good for a language that has been grafted onto .NET.

It still has some potentially serious drawbacks – you can’t use attributes, it doesn’t run on the compact framework and its integration with XAML isn’t perfect. It also suffers from the usual problem with most open source projects, the documentation is terrible. But even after all of these difficulties are taken into account it’s an interesting example of a dynamic language running on the .NET platform – which after all was not designed for dynamic languages.