 Saturday, September 30, 2006
Part 2 - More IronPython and delegates…
So we have briefly covererd strongly typed delegates and event
handlers... But they make the assumption that we know what our
arguments are at compile time, what if we dont?
Say for instance you're expression engine has a bag of contextual values being passed around in a dictionary, like this:
Dictionary<string, object> context = new Dictionary<string, object>();
context.Add("user", "alex");
context.Add("age", 26);
What if we want to use python to evaluate expressions against that
context?... Say something like writing out “my name is alex and my age
is 25" - the expression in python is easy enough, we can go:
‘my name is’ + name + ‘ and my age is ‘ + str(age)
But how do we marry all these together... lets start exploring... ever noticed that delegates have a DynamicInvoke method with a signature like this?
object
DynamicInvoke(params object[] parameters)
Perhaps we can try and use this to our advantage... lets see shall we?
First Attempt
[Test]
[ExpectedException(typeof (ArgumentException), "T must be a concrete delegate, not MulticastDelegate or Delegate")]
public void LooseDelegateAndDynamicInvoke()
{
PythonEngine engine = new PythonEngine();
List<string> parameters = new List<string>(new string[] {"name", "age"});
Delegate func =
engine.CreateMethod<Delegate>("return ‘my name is’ + name + ‘ and my age is ‘ + str(age)", parameters);
string result = (string) func.DynamicInvoke("alex", 26);
Assert.AreEqual("my name is alex and my age is 25", result);
}
Sadly this doesn’t work so well, seems the PythonEngine is looking for
a concrete delegate... we could try giving it void delegate,
but it does type checking on the number of parameters and their type,
so we're a little stuck.
At this point I think the best answer is to actually build some code
which generates the apropriate delegate type, based on the supplied
dictionary, and then passing that onto the python engine, but there is
another way… it’s a little less elegant, but it’s at least amusing:
Round 2
First of let’s build a little helper method...
private static string GenerateFunction(string functionName, string[] parameters, string statements)
{
StringBuilder builder = new StringBuilder();
builder.AppendFormat("def {0}(", functionName);
for (int i = 0; i < parameters.Length; i++)
{
if (i > 0) builder.AppendFormat(", ");
builder.AppendFormat(parameters[i]);
}
builder.AppendFormat("):\r\n ");
builder.AppendFormat(statements.Replace("\r\n", "\n").Replace("\r", "\n").Replace("\n", "\r\n "));
return builder.ToString();
}
You can no doubt see where this is going – so given a statement like this:
GenerateFunction("func", new string[] { "user", "age" }, "return user + ' is ' + str(age) + ' years old'")
We’d end up with a string like this:
def func(user, age):
return user + ' is ' + str(age) + ' years old'
And, moving on from there we build a test…
[Test]
public void GeneratingPythonFunction()
{
Dictionary<string, object> context = new Dictionary<string, object>();
context.Add("user", "alex");
context.Add("age", 26);
List<string> parameters = new List<string>(context.Keys);
List<object> values = new List<object>(context.Values);
PythonEngine engine = new PythonEngine();
engine.Execute(
GenerateFunction("func", parameters.ToArray(), "return user + ' is ' + str(age) + ' years old'"));
PythonFunction func1 = engine.EvaluateAs<PythonFunction>("func");
engine.Execute(
GenerateFunction("func", parameters.ToArray(),
"return user + ' is ' + str(age+1) + ' years old next year'"));
PythonFunction func2 = engine.EvaluateAs<PythonFunction>("func");
object result1 = func1.Call(values.ToArray());
Assert.AreEqual("alex is 26 years old", result1);
object result2 = func2.Call(values.ToArray());
Assert.AreEqual("alex is 27 years old next year", result2);
}
Are there problems with this?… well there’s a few, unsurprisingly!
• PythonFunction’s are not delegates.
• String manipulation is a bit clunky
• Because these are named functions, you probably
want to lock against some object when generating the function, to avoid
cross-threading issues…
Back to delegates
Aside from the problems it works alright, but what we really want is a
delegate… those PythonFunctions don’t give you the flexibility to
substitute IronRuby in the future now do they?
So first off, lets
declare a delegate suitable for our purposes.
[ThereBeDragons("Only use as a last resort")]
public delegate object UntypedDelegate(params object[] parameters);
Aint she a beauty ;o) - full credit for the ThereBeDragons attribute goes to Ayende,
though it's probably not really warranted in this situation - now,
let’s rework the last test to use a delegate instead, a simple
anonymous delegate will do the dirty work:
[Test]
public void UntypedDelegateForPythonFunction()
{
Dictionary<string, object> context = new Dictionary<string, object>();
context.Add("user", "alex");
context.Add("age", 26);
List<string> parameters = new List<string>(context.Keys);
List<object> values = new List<object>(context.Values);
PythonEngine engine = new PythonEngine();
engine.Execute(GenerateFunction("func",parameters.ToArray(),"return user + ' is ' + str(age) + ' years old'"));
PythonFunction func1 = engine.EvaluateAs<PythonFunction>("func");
UntypedDelegate func1Delegate = delegate(object[] param)
{
return func1.Call(param);
};
object result1 = func1Delegate(values.ToArray());
Assert.AreEqual("alex is 26 years old", result1);
}
It’s certainly better then passing around PythonFunction instances –
though you need to be a little careful… there’s a little quirk here, if
we were to use:
object result1 = func1Delegate.DynamicInvoke(values.ToArray());
It’s going to fail because the wrong number of arguments were supplied
(it expects only 1, an array), so our delegate doesn’t really behave
like it has multiple parameters.. so to dynamically invoke this
delegate we’d need to take special care, promoting the arguments into a
second array like so:
object result1 = func1Delegate.DynamicInvoke(new object[] { values.ToArray()});
Next Time
When I get bored I'll write a version which doesn't require the clunky generator and post it up...
Next time I'll talk about writing classes which are python-friendly, riveting stuff eh? As you were.
© Copyright 2009 Alex Henderson
Theme design by Bryan Bell
newtelligence dasBlog 1.9.6264.0  | Page rendered at Tuesday, January 06, 2009 8:14:58 AM (New Zealand Daylight Time, UTC+13:00)
|
Search
FeedCount
Tags...
Who am I?

|
Alex Henderson
Auckland, New Zealand
Managing Director at Dev|Defined Limited
"Self Confessed Coding Junky for 15 years"
|
 |
| |
| Mobile: |
+64-21-402-969 |
| Email: |
bittercoder 'at' gmail 'dot' com |
| MSN: |
bittercoder_nz@hotmail |
| Skype: |
alex.devdefined |
Navigation
On this page....
Blogs I read by New Zealanders...
Blogs I read on Castle...
BlogMap
Del.icio.us
Wishlists
|