Thanks to visit codestin.com
Credit goes to github.com

Skip to content

add function codec #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 185 additions & 0 deletions src/FunctionCodec.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
using System;
using System.Reflection;

namespace Python.Runtime.Codecs
{
//converts python functions to C# actions
public class FunctionCodec : IPyObjectDecoder
{
private static int GetNumArgs(PyObject pyCallable)
{
var locals = new PyDict();
locals.SetItem("f", pyCallable);
using (Py.GIL())
PythonEngine.Exec(@"
from inspect import signature
try:
x = len(signature(f).parameters)
except:
x = 0
", null, locals.Handle);

var x = locals.GetItem("x");
return new PyInt(x).ToInt32();
}

private static int GetNumArgs(Type targetType)
{
MethodInfo invokeMethod = targetType.GetMethod("Invoke");
return invokeMethod.GetParameters().Length;
}

private static bool IsUnaryAction(Type targetType)
{
return targetType == typeof(Action);
}

private static bool IsVariadicObjectAction(Type targetType)
{
return targetType == typeof(Action<object[]>);
}

private static bool IsUnaryFunc(Type targetType)
{
return targetType == typeof(Func<object>);
}

private static bool IsVariadicObjectFunc(Type targetType)
{
return targetType == typeof(Func<object[], object>);
}

private static bool IsAction(Type targetType)
{
return IsUnaryAction(targetType) || IsVariadicObjectAction(targetType);
}

private static bool IsFunc(Type targetType)
{
return IsUnaryFunc(targetType) || IsVariadicObjectFunc(targetType);
}

private static bool IsCallable(Type targetType)
{
//Python.Runtime.ClassManager dtype
return targetType.IsSubclassOf(typeof(MulticastDelegate));
}

public static FunctionCodec Instance { get; } = new FunctionCodec();
public bool CanDecode(PyObject objectType, Type targetType)
{
//python object must be callable
if (!objectType.IsCallable()) return false;

//C# object must be callable
if (!IsCallable(targetType))
return false;

return true;
}

private static object ConvertUnaryAction(PyObject pyObj)
{
Func<object> func = (Func<object>)ConvertUnaryFunc(pyObj);
Action action = () => { func(); };
return (object)action;
}

private static object ConvertVariadicObjectAction(PyObject pyObj, int numArgs)
{
Func<object[], object> func = (Func<object[], object>)ConvertVariadicObjectFunc(pyObj, numArgs);
Action<object[]> action = (object[] args) => { func(args); };
return (object)action;
}

//TODO share code between ConvertUnaryFunc and ConvertVariadicObjectFunc
private static object ConvertUnaryFunc(PyObject pyObj)
{
var pyAction = new PyObject(pyObj.Handle);
Func<object> func = () =>
{
var pyArgs = new PyObject[0];
using (Py.GIL())
{
var pyResult = pyAction.Invoke(pyArgs);
return pyResult.AsManagedObject(typeof(object));
}
};
return (object)func;
}

private static object ConvertVariadicObjectFunc(PyObject pyObj, int numArgs)
{
var pyAction = new PyObject(pyObj.Handle);
Func<object[], object> func = (object[] o) =>
{
var pyArgs = new PyObject[numArgs];
int i = 0;
foreach (object obj in o)
{
pyArgs[i++] = obj.ToPython();
}

using (Py.GIL())
{
var pyResult = pyAction.Invoke(pyArgs);
return pyResult.AsManagedObject(typeof(object));
}
};
return (object)func;
}

public bool TryDecode<T>(PyObject pyObj, out T value)
{
value = default(T);
var tT = typeof(T);
if (!IsCallable(tT))
return false;

var numArgs = GetNumArgs(pyObj);
if (numArgs != GetNumArgs(tT))
return false;

if (IsAction(tT))
{
object actionObj = null;
if (numArgs == 0)
{
actionObj = ConvertUnaryAction(pyObj);
}
else
{
actionObj = ConvertVariadicObjectAction(pyObj, numArgs);
}

value = (T)actionObj;
return true;
}
else if (IsFunc(tT))
{

object funcObj = null;
if (numArgs == 0)
{
funcObj = ConvertUnaryFunc(pyObj);
}
else
{
funcObj = ConvertVariadicObjectFunc(pyObj, numArgs);
}

value = (T)funcObj;
return true;
}
else
{
return false;
}
}

public static void Register()
{
PyObjectConversions.RegisterDecoder(Instance);
}
}
}
111 changes: 111 additions & 0 deletions tests/FunctionCodecTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using System;
using NUnit.Framework;

namespace Python.Runtime.Codecs
{
class FunctionCodecTests
{
[SetUp]
public void SetUp()
{
TestsRuntimeConfig.Ensure();
PythonEngine.Initialize();
}


[TearDown]
public void Dispose()
{
PythonEngine.Shutdown();
}

[Test]
public void FunctionAction()
{
var codec = FunctionCodec.Instance;

PyInt x = new PyInt(1);
PyDict y = new PyDict();
//non-callables can't be decoded into Action
Assert.IsFalse(codec.CanDecode(x, typeof(Action)));
Assert.IsFalse(codec.CanDecode(y, typeof(Action)));

var locals = new PyDict();
PythonEngine.Exec(@"
def foo():
return 1
def bar(a):
return 2
", null, locals.Handle);

//foo, the function with no arguments
var fooFunc = locals.GetItem("foo");
Assert.IsFalse(codec.CanDecode(fooFunc, typeof(bool)));

//CanDecode does not work for variadic actions
//Assert.IsFalse(codec.CanDecode(fooFunc, typeof(Action<object[]>)));
Assert.IsTrue(codec.CanDecode(fooFunc, typeof(Action)));

Action fooAction;
Assert.IsTrue(codec.TryDecode(fooFunc, out fooAction));
Assert.DoesNotThrow(() => fooAction());

//bar, the function with an argument
var barFunc = locals.GetItem("bar");
Assert.IsFalse(codec.CanDecode(barFunc, typeof(bool)));
//Assert.IsFalse(codec.CanDecode(barFunc, typeof(Action)));
Assert.IsTrue(codec.CanDecode(barFunc, typeof(Action<object[]>)));

Action<object[]> barAction;
Assert.IsTrue(codec.TryDecode(barFunc, out barAction));
Assert.DoesNotThrow(() => barAction(new[] { (object)true }));
}

[Test]
public void FunctionFunc()
{
var codec = FunctionCodec.Instance;

PyInt x = new PyInt(1);
PyDict y = new PyDict();
//non-callables can't be decoded into Func
Assert.IsFalse(codec.CanDecode(x, typeof(Func<object>)));
Assert.IsFalse(codec.CanDecode(y, typeof(Func<object>)));

var locals = new PyDict();
PythonEngine.Exec(@"
def foo():
return 1
def bar(a):
return 2
", null, locals.Handle);

//foo, the function with no arguments
var fooFunc = locals.GetItem("foo");
Assert.IsFalse(codec.CanDecode(fooFunc, typeof(bool)));

//CanDecode does not work for variadic actions
//Assert.IsFalse(codec.CanDecode(fooFunc, typeof(Func<object[], object>)));
Assert.IsTrue(codec.CanDecode(fooFunc, typeof(Func<object>)));

Func<object> foo;
Assert.IsTrue(codec.TryDecode(fooFunc, out foo));
object res1 = null;
Assert.DoesNotThrow(() => res1 = foo());
Assert.AreEqual(res1, 1);

//bar, the function with an argument
var barFunc = locals.GetItem("bar");
Assert.IsFalse(codec.CanDecode(barFunc, typeof(bool)));
//Assert.IsFalse(codec.CanDecode(barFunc, typeof(Func<object>)));
Assert.IsTrue(codec.CanDecode(barFunc, typeof(Func<object[], object>)));

Func<object[], object> bar;
Assert.IsTrue(codec.TryDecode(barFunc, out bar));
object res2 = null;
Assert.DoesNotThrow(() => res2 = bar(new[] { (object)true }));
Assert.AreEqual(res2, 2);
}

}
}
98 changes: 98 additions & 0 deletions tests/TestCallbacks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using System;
using NUnit.Framework;

namespace Python.Runtime.Codecs
{
public class TestCallbacks
{
[OneTimeSetUp]
public void SetUp()
{
TestsRuntimeConfig.Ensure();
PythonEngine.Initialize();
}

[OneTimeTearDown]
public void Dispose()
{
PythonEngine.Shutdown();
}

private class Callables
{
internal object CallFunction0(Func<object> func)
{
return func();
}

internal object CallFunction1(Func<object[], object> func, object arg)
{
return func(new[] { arg });
}

internal void CallAction0(Action func)
{
func();
}

internal void CallAction1(Action<object[]> func, object arg)
{
func(new[] { arg });
}
}

[Test]
public void TestPythonFunctionPassedIntoCLRMethod()
{
var locals = new PyDict();
PythonEngine.Exec(@"
def ret_1():
return 1
def str_len(a):
return len(a)
", null, locals.Handle);

var ret1 = locals.GetItem("ret_1");
var strLen = locals.GetItem("str_len");

var callables = new Callables();

FunctionCodec.Register();

//ret1. A function with no arguments that returns an integer
//it must be convertible to Action or Func<object> and not to Func<object, object>
{
Action result1 = null;
Func<object> result2 = null;
Assert.DoesNotThrow(() => { result1 = ret1.As<Action>(); });
Assert.DoesNotThrow(() => { result2 = ret1.As<Func<object>>(); });

Assert.DoesNotThrow(() => { callables.CallAction0((Action)result1); });
object ret2 = null;
Assert.DoesNotThrow(() => { ret2 = callables.CallFunction0((Func<object>)result2); });
Assert.AreEqual(ret2, 1);
}

//strLen. A function that takes something with a __len__ and returns the result of that function
//It must be convertible to an Action<object[]> and Func<object[], object>) and not to an Action or Func<object>
{
Action<object[]> result3 = null;
Func<object[], object> result4 = null;
Assert.DoesNotThrow(() => { result3 = strLen.As<Action<object[]>>(); });
Assert.DoesNotThrow(() => { result4 = strLen.As<Func<object[], object>>(); });

//try using both func and action to show you can get __len__ of a string but not an integer
Assert.Throws<PythonException>(() => { callables.CallAction1((Action<object[]>)result3, 2); });
Assert.DoesNotThrow(() => { callables.CallAction1((Action<object[]>)result3, "hello"); });
Assert.Throws<PythonException>(() => { callables.CallFunction1((Func<object[], object>)result4, 2); });

object ret2 = null;
Assert.DoesNotThrow(() => { ret2 = callables.CallFunction1((Func<object[], object>)result4, "hello"); });
Assert.AreEqual(ret2, 5);
}

//TODO - this function is internal inside of PythonNet. It probably should be public.
//PyObjectConversions.Reset();
}
}
}