CarbonPython is an experimental RPython to .NET compiler. Its main focus is to produce DLLs to be used by other .NET programs, not standalone executables; if you want to compile an RPython standalone program, have a look to translate.py.
Compiled RPython programs are much faster (up to 250x) than interpreted IronPython programs, hence it might be a convenient replacement for C# when more speed is needed. RPython programs can be as fast as C# programs.
RPython is a restrict subset of Python, static enough to be analyzed and compiled efficiently to lower level languages. To read more about the RPython limitations read the RPython description.
Disclaimer: RPython is a much less convenient language than Python to program with. If you do not need speed, there is no reason to look at RPython.
Big disclaimer: CarbonPython is still in a pre-alpha stage: it's not meant to be used for production code, and the API might change in the future. Despite this, it might be useful in some situations and you are encouraged to try it by yourself. Suggestions, bug-reports and even better patches are welcome.
Suppose you want to write a little DLL in RPython and call its function from C#.
Here is the file mylibrary.py:
from pypy.translator.cli.carbonpython import export
@export(int, int)
def add(x, y):
return x+y
@export(int, int)
def sub(x, y):
return x-y
And here the C# program main.cs:
using System;
public class CarbonPythonTest
{
public static void Main()
{
Console.WriteLine(mylibrary.add(40, 2));
Console.WriteLine(mylibrary.sub(44, 2));
}
}
Once the files have been created, you can compile mylibrary.py with CarbonPython to get the corresponding DLL:
$ python carbonpython.py mylibrary.py ... lot of stuff
Then, we compile main.cs into an executable, being sure to add a reference to the newly created mylibrary.dll:
# with mono on linux $ gmcs /r:mylibrary.dll main.cs # with Microsoft CLR on windows c:\> csc /r:mylibrary main.cs
Now we can run the executable to see whether the answers are right:
$ mono main.exe 42 42
In RPython, the type of each variable is inferred by the Annotator: the annotator analyzed the whole program top-down starting from an entry-point, i.e. a function whose we specified the types of the parameters.
This approach works for a standalone executables, but not for a library that by definition is composed by more than one entry-point. Thus, you need to explicitly specify which functions you want to include in your DLL, together with the expected input types.
To mark a function as an entry-point, you use the @export decorator, which is defined in pypy.translator.cli.carbonpython, as shown by the previous example. Note that you do not need to specify the return type, because it is automatically inferenced by the annotator.
Since CLS (Common Language Specification) does not support module level static methods, RPython functions marked as entry-points are compiled to static methods of a class, in order to be accessible by every CLS-compliant language such as C# or VB.NET.
The class which each function is placed in depends on its namespace; for example, if the namespace of a function foo is A.B.C, the function will be rendered as a static method of the C class inside the A.B namespace. This allows C# and IronPython code to call the function using the intuitive A.B.C.foo syntax.
By default, the default namespace for exported function is the same as the name of the module. Thus in the previous example the default namespace is mylibrary and the functions are placed inside the corresponding class in the global namespace.
You can change the default namespace by setting the _namespace_ variable in the module you are compiling:
_namespace_ = 'Foo.Bar'
@export(int, int)
def f(x, y):
pass
Finally, you can also set a specific namespace on a per-function basis, using the appropriate keyword argument of the @export decorator:
@export(int, int, namespace='Foo.Bar')
def f(x, y):
pass
RPython libraries can also export classes: to export a class, add the @export decorator to its __init__ method; similarly, you can also export any methods of the class:
class MyClass:
@export(int)
def __init__(self, x):
self.x = x
@export
def getx(self):
return self.x
Note that the type of self must not be speficied: it will automatically assumed to be MyClass.
The __init__ method is not automatically mapped to the .NET constructor; to properly initialize an RPython object from C# or IronPython code you need to explicitly call __init__; for example, in C#:
MyClass obj = new MyClass(); obj.__init__(x);
Note that this is needed only when calling RPython code from outside; the RPython compiler automatically calls __init__ whenever an RPython class is instantiated.
In the future this discrepacy will be fixed and the __init__ method will be automatically mapped to the constructor.
Warning: the API for accessing .NET classes from RPython is highly experimental and will probably change in the future.
In RPython you can access native .NET classes through the CLR object defined in translator.cli.dotnet: from there, you can navigate through namespaces using the usual dot notation; for example, CLR.System.Collections.ArrayList refers to the ArrayList class in the System.Collections namespace.
To instantiate a .NET class, simply call it:
ArrayList = CLR.System.Collections.ArrayList
def foo():
obj = ArrayList()
obj.Add(42)
return obj
At the moment there is no special syntax support for indexers and properties: for example, you can't access ArrayList's elements using the square bracked notation, but you have to call the call the get_Item and set_Item methods; similarly, to access a property XXX you need to call get_XXX and set_XXX:
def foo():
obj = ArrayList()
obj.Add(42)
print obj.get_Item(0)
print obj.get_Count()
Static methods and are also supported, as well as overloadings:
Math = CLR.System.Math
def foo():
print Math.Abs(-42)
print Math.Abs(-42.0)
At the moment, it is not possible to reference assemblies other than mscorlib. This will be fixed soon.