XD blog

blog page

csharp


2014-09-20 Magic command %%CS for IPython Notebook

The notebooks IPython offer a couple of magic command to run others language such %%R, %%octave or %%julia. I found one option for F# but nothing on something like %%CS. However, magic commands are quite easy to handle. It is not that difficult to add one which allows me to do that:

%%CS mypower System.dll
public static double mypower(double x, double y)
{
    if (y == 0) return 1.0 ;
    return System.Math.Pow(x,y) ;
}

To be able to call it that way:

mypower(3.0,3.0)

pythonnet offers a simple way to call C# from a Python program. Based on that, creating a magic command requires three parts. The first one is a C# DLL which dynamically compiles a code.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using System.Reflection;

namespace MagicIPython
{
    public static class MagicCS
    {
        private const string embedCode = @"
                    namespace MagicCSIPython
                    {0}                
                        public static class MagicCSFunctions_{2}
                        {0}  
                            {3}
                        {1}
                    {1}
                    ";

        public static MethodInfo CreateFunction(string functionName, string code, string[] dependencies)
        {
            CSharpCodeProvider provider = new CSharpCodeProvider();
            CompilerParameters parameters = new CompilerParameters();

            if (dependencies != null)
            {
                foreach (var d in dependencies)
                    parameters.ReferencedAssemblies.Add(d);
            }

            parameters.GenerateInMemory = true;
            parameters.GenerateExecutable = false;

            code = string.Format(embedCode, "{", "}", functionName, code);

            CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
            if (results.Errors.HasErrors)
            {
                StringBuilder sb = new StringBuilder();
                foreach (CompilerError error in results.Errors)
                {
                    sb.AppendLine(String.Format("Error ({0}): {1}", error.ErrorNumber, error.ErrorText));
                }
                throw new InvalidOperationException(sb.ToString());
            }
            Type binaryFunction = results.CompiledAssembly.GetType(string.Format("MagicCSIPython.MagicCSFunctions_{0}", functionName));
            return binaryFunction.GetMethod(functionName);
        }

        public static object RunFunction(MethodInfo function, object[] parameters)
        {
            return function.Invoke(null, parameters);
        }
    }
}

The second part consists in a wrapper around this DLL

def create_cs_function(name, code, dependencies = None):
    AddReference("MagicIPython.dll")
    from MagicIPython import MagicCS
    from System import String
    from System.Collections.Generic import List

    if dependencies is not None and len(dependencies) > 0 :
        myarray = List[String]()
        for i,d in enumerate(dependencies):
            myarray.Add( d )
        myarray = myarray.ToArray()
    else:
        myarray = List[String]().ToArray()
    
    obj = MagicCS.CreateFunction(name, code, myarray)
    return lambda *params: run_cs_function(obj, params)

def run_cs_function(func, params):
    AddReference("MagicIPython.dll")
    from MagicIPython import MagicCS
    from System.Collections.Generic import List
    from System import Object

    par = List[Object]()
    for p in params :
        par.Add ( p )
    return MagicCS.RunFunction(func, par.ToArray())

And the last one is the magic command itself:

import sys
from IPython.core.magic import Magics, magics_class, line_magic, cell_magic
from IPython.core.magic import line_cell_magic
from IPython.core.display import HTML                                

@magics_class
class CustomMagics(Magics):

    @cell_magic
    def CS(self, line, cell):
        """
        Defines command ``%%CS``.
        """
        if not sys.platform.startswith("win"):
            raise Exception("Works only on Windows.")
        
        from ..tips_tricks.pythoncs import create_cs_function
        if line is not None:
            spl = line.strip().split(" ")
            name = spl[0]
            deps = " ".join(spl[1:]) if len(spl) > 1 else ""
            deps = deps.split(";")
            
        if name == "-h": 
            print(  "Usage: "
                    "   %%CS function_name dependency1;dependency2"
                    "   function code")
        else :
            try:
                f = create_cs_function(name, cell, deps)
            except Exception as e :
                print(e)
                return 
            if self.shell is not None:
                self.shell.user_ns[name] = f
            return f

def register_magics():
    """
    register magics function, can be called from a notebook
    """
    ip = get_ipython()
    ip.register_magics(CustomMagics)

After that, what's left is to use it in a notebook: Python et C Sharp


Xavier Dupré