From 514040c03ce86d3e3a3fc884df5e0d3bc607b608 Mon Sep 17 00:00:00 2001 From: Mohamed Koubaa Date: Sun, 29 Mar 2020 13:19:23 -0500 Subject: [PATCH 1/3] add function codec --- src/FunctionCodec.cs | 185 ++++++++++++++++++++++++++++++++++++ tests/FunctionCodecTests.cs | 110 +++++++++++++++++++++ tests/TestCallbacks.cs | 97 +++++++++++++++++++ 3 files changed, 392 insertions(+) create mode 100644 src/FunctionCodec.cs create mode 100644 tests/FunctionCodecTests.cs create mode 100644 tests/TestCallbacks.cs diff --git a/src/FunctionCodec.cs b/src/FunctionCodec.cs new file mode 100644 index 0000000..e7ce8cf --- /dev/null +++ b/src/FunctionCodec.cs @@ -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); + } + + private static bool IsUnaryFunc(Type targetType) + { + return targetType == typeof(Func); + } + + private static bool IsVariadicObjectFunc(Type targetType) + { + return targetType == typeof(Func); + } + + 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 func = (Func)ConvertUnaryFunc(pyObj); + Action action = () => { func(); }; + return (object)action; + } + + private static object ConvertVariadicObjectAction(PyObject pyObj, int numArgs) + { + Func func = (Func)ConvertVariadicObjectFunc(pyObj, numArgs); + Action 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 func = () => + { + var pyArgs = new PyObject[0]; + using (Py.GIL()) + { + var pyResult = pyAction.Invoke(pyArgs); + return pyResult.As(); + } + }; + return (object)func; + } + + private static object ConvertVariadicObjectFunc(PyObject pyObj, int numArgs) + { + var pyAction = new PyObject(pyObj.Handle); + Func 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.As(); + } + }; + return (object)func; + } + + public bool TryDecode(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); + } + } +} diff --git a/tests/FunctionCodecTests.cs b/tests/FunctionCodecTests.cs new file mode 100644 index 0000000..855ce78 --- /dev/null +++ b/tests/FunctionCodecTests.cs @@ -0,0 +1,110 @@ +using System; +using NUnit.Framework; + +namespace Python.Runtime.Codecs +{ + class FunctionCodecTests + { + [SetUp] + public void SetUp() + { + 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))); + 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))); + + Action 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))); + Assert.IsFalse(codec.CanDecode(y, typeof(Func))); + + 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))); + Assert.IsTrue(codec.CanDecode(fooFunc, typeof(Func))); + + Func 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))); + Assert.IsTrue(codec.CanDecode(barFunc, typeof(Func))); + + Func bar; + Assert.IsTrue(codec.TryDecode(barFunc, out bar)); + object res2 = null; + Assert.DoesNotThrow(() => res2 = bar(new[] { (object)true })); + Assert.AreEqual(res2, 2); + } + + } +} diff --git a/tests/TestCallbacks.cs b/tests/TestCallbacks.cs new file mode 100644 index 0000000..c33707d --- /dev/null +++ b/tests/TestCallbacks.cs @@ -0,0 +1,97 @@ +using System; +using NUnit.Framework; + +namespace Python.Runtime.Codecs +{ + public class TestCallbacks + { + [OneTimeSetUp] + public void SetUp() + { + PythonEngine.Initialize(); + } + + [OneTimeTearDown] + public void Dispose() + { + PythonEngine.Shutdown(); + } + + private class Callables + { + internal object CallFunction0(Func func) + { + return func(); + } + + internal object CallFunction1(Func func, object arg) + { + return func(new[] { arg }); + } + + internal void CallAction0(Action func) + { + func(); + } + + internal void CallAction1(Action 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 and not to Func + { + Action result1 = null; + Func result2 = null; + Assert.DoesNotThrow(() => { result1 = ret1.As(); }); + Assert.DoesNotThrow(() => { result2 = ret1.As>(); }); + + Assert.DoesNotThrow(() => { callables.CallAction0((Action)result1); }); + object ret2 = null; + Assert.DoesNotThrow(() => { ret2 = callables.CallFunction0((Func)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 and Func) and not to an Action or Func + { + Action result3 = null; + Func result4 = null; + Assert.DoesNotThrow(() => { result3 = strLen.As>(); }); + Assert.DoesNotThrow(() => { result4 = strLen.As>(); }); + + //try using both func and action to show you can get __len__ of a string but not an integer + Assert.Throws(() => { callables.CallAction1((Action)result3, 2); }); + Assert.DoesNotThrow(() => { callables.CallAction1((Action)result3, "hello"); }); + Assert.Throws(() => { callables.CallFunction1((Func)result4, 2); }); + + object ret2 = null; + Assert.DoesNotThrow(() => { ret2 = callables.CallFunction1((Func)result4, "hello"); }); + Assert.AreEqual(ret2, 5); + } + + //TODO - this function is internal inside of PythonNet. It probably should be public. + //PyObjectConversions.Reset(); + } + } +} From ea84d288c3d1bd4207125a96dfaa8e2ba9ecdd2a Mon Sep 17 00:00:00 2001 From: Mohamed Koubaa Date: Mon, 13 Apr 2020 20:16:36 -0500 Subject: [PATCH 2/3] add ensure --- tests/FunctionCodecTests.cs | 3 ++- tests/TestCallbacks.cs | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/FunctionCodecTests.cs b/tests/FunctionCodecTests.cs index 855ce78..a60eddf 100644 --- a/tests/FunctionCodecTests.cs +++ b/tests/FunctionCodecTests.cs @@ -7,7 +7,8 @@ class FunctionCodecTests { [SetUp] public void SetUp() - { + { + TestsRuntimeConfig.Ensure(); PythonEngine.Initialize(); } diff --git a/tests/TestCallbacks.cs b/tests/TestCallbacks.cs index c33707d..dec68ac 100644 --- a/tests/TestCallbacks.cs +++ b/tests/TestCallbacks.cs @@ -8,6 +8,7 @@ public class TestCallbacks [OneTimeSetUp] public void SetUp() { + TestsRuntimeConfig.Ensure(); PythonEngine.Initialize(); } From 143faca02473b7cef6ddb42d860f792fe989a7de Mon Sep 17 00:00:00 2001 From: Mohamed Koubaa Date: Thu, 23 Apr 2020 10:56:45 -0500 Subject: [PATCH 3/3] use AsManagedObject --- src/FunctionCodec.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FunctionCodec.cs b/src/FunctionCodec.cs index e7ce8cf..e69347b 100644 --- a/src/FunctionCodec.cs +++ b/src/FunctionCodec.cs @@ -102,7 +102,7 @@ private static object ConvertUnaryFunc(PyObject pyObj) using (Py.GIL()) { var pyResult = pyAction.Invoke(pyArgs); - return pyResult.As(); + return pyResult.AsManagedObject(typeof(object)); } }; return (object)func; @@ -123,7 +123,7 @@ private static object ConvertVariadicObjectFunc(PyObject pyObj, int numArgs) using (Py.GIL()) { var pyResult = pyAction.Invoke(pyArgs); - return pyResult.As(); + return pyResult.AsManagedObject(typeof(object)); } }; return (object)func; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy