One of the challenges in designing IronPython lies in attempting to satisfy the two separate audiences the language is intended to serve. On the one hand, it needs to work as much like the standard C-based Python implementation as possible. On the other, it has to have high fidelity interop with the .NET Framework. Sometimes, the needs of those two priorities clash. For example, what should be the result of this code?
s = 'hello, world!'
In Python, the string type does not have a ToUpper
method, so this code should throw an exception. However, in .NET, calling the ToUpper
function on this string should return "HELLO, WORLD!" These are obviously contradictory requirements.
IronPython handles this by being a good Python implementation by default, but allowing developer to indicate they want high fidelity .NET interop. In Python, code is organized into modules and namespaces, similar to .NET. IronPython includes a special module called clr
. By importing that module, developers indicate they want to use .NET interop. Here's an interactive IronPython session that demonstrates the use of import clr
>>> s = 'hello, world'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no
>>> import clr
Before the call to import clr
, you can't call the ToUpper
method on a string. String objects in Python don't have a ToUpper
method so IronPython hides that method, among others. However, after the call to import clr
, all of the String object's methods—both Python and native .NET—are available.
You also use the clr
module to load external assemblies. For example, to use .NET XML processing classes such as XmlReader and XmlDocument, you need to add a reference to System.Xml.dll
. In C#, you add that reference declaratively at compile time. But because there is no compile time in IronPython, you need to add the reference imperatively via code. Subsequently, you can import classes into your current scope and use them just like any other Python object.
from System.Xml import XmlDocument
xml = XmlDocument()
It should be pretty obvious what this code does, but there are a few things I want to note. First off, there is no new
statement in Python; you create type instances by calling the type like a function. Second, in C#, importing classes from namespaces (via the using
statement) is optional, designed to save typing. In Python, it's mandatory. In other words, writing the code this way wouldn't work:
# the following line doesn't work
xml = System.Xml.XmlDocument()
form of the statement imports the short name. To use the fully namespace-scoped name, write import System.Xml.XmlDocument
. Import also supports renaming types to avoid collisions using the syntax import <real name> as <new name>
You can make nearly the entire .NET Framework available to IronPython simply by adding references to the relevant assemblies and importing the needed types. Besides being able to create instances of .NET types, you can also consume .NET events, implement .NET interfaces, and inherit from .NET types. For example, here's some code from the IronPython tutorial that uses Windows Forms.
# the winforms module in the tutorial directory
from System.Windows.Forms import *
from System.Drawing import *
f = Form()
f.Text = "My First Interactive Application"
f.Click += click
However, one thing you can't do from IronPython is adorn your code with attributes. Any part of the .NET framework that requires attributes, such as WCF contracts or XML serialization, won't work with IronPython. Those libraries depend on custom attributes that act like custom metadata to extend the existing static type. Python objects don't have a static type, so there's nothing to attach the custom attribute to.
The other thing about .NET interop is that it's essentially one way. It's easy for Python to call into .NET classes written in statically typed languages. However, there's no easy way (yet) for statically typed languages to call into dynamically typed objects. Static languages depend on the compile-time type metadata to dispatch method calls, but that metadata just doesn't exist in dynamically typed languages such as Python. Obviously, given the multi-language nature of the CLR, enabling statically typed languages to call into dynamically typed code is a scenario we would like to enable in the future.
Python has significant traction in the industry as an easily-embeddable language. Likewise, IronPython can be easily embedded inside your .NET applications to provide a scripting or macro development experience.
IronPython 1.x handled embedding entirely thru the PythonEngine type. Here's an example that accesses the PythonEngine from the interactive console.
>>> import clr
>>> from IronPython.Hosting import PythonEngine
>>> pe = PythonEngine()
This example doesn't give the hosted Python environment any hooks into the host, so the code it can execute is fairly limited. However, PythonEngine provides a Globals
collection that you can use to expose your application object model to the Python environment.
In IronPython 2.0, the hosting code gets a bit more complicated:
from IronPython.Hosting import PythonEngine
pe = PythonEngine.CurrentEngine
scope = pe.CreateScope()
source = pe.CreateScriptSourceFromString('2+2')
result = source.Execute(scope)
The Dynamic Language Runtime (DLR) is one reason the IronPython 2.0 code is more complicated. The DLR is an extension to the CLR that provides common capabilities needed for dynamic languages. But it also provides a common hosting API that allows any application hosting
the DLR to support any language that targets
Between significant whitespace and dynamic typing, there's no question Python is a wholly different development experience from C# or VB. I'm a recent convert to Python from C#, so I know exactly how strange it can feel. But once you get past that feeling of unfamiliarity, you'll start to see just how productive Python can be.