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

Skip to content

Commit d7bfa49

Browse files
committed
add function codec
1 parent 6f75c16 commit d7bfa49

File tree

3 files changed

+401
-0
lines changed

3 files changed

+401
-0
lines changed

src/FunctionCodec.cs

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
using System;
2+
using System.Reflection;
3+
4+
namespace Python.Runtime.Codecs
5+
{
6+
//converts python functions to C# actions
7+
public class FunctionCodec : IPyObjectDecoder
8+
{
9+
private static int GetNumArgs(PyObject pyCallable)
10+
{
11+
var locals = new PyDict();
12+
locals.SetItem("f", pyCallable);
13+
using (Py.GIL())
14+
PythonEngine.Exec(@"
15+
from inspect import signature
16+
try:
17+
x = len(signature(f).parameters)
18+
except:
19+
x = 0
20+
", null, locals.Handle);
21+
22+
var x = locals.GetItem("x");
23+
return new PyInt(x).ToInt32();
24+
}
25+
26+
private static int GetNumArgs(Type targetType)
27+
{
28+
MethodInfo invokeMethod = targetType.GetMethod("Invoke");
29+
return invokeMethod.GetParameters().Length;
30+
}
31+
32+
private static bool IsUnaryAction(Type targetType)
33+
{
34+
return targetType == typeof(Action);
35+
}
36+
37+
private static bool IsVariadicObjectAction(Type targetType)
38+
{
39+
return targetType == typeof(Action<object[]>);
40+
}
41+
42+
private static bool IsUnaryFunc(Type targetType)
43+
{
44+
return targetType == typeof(Func<object>);
45+
}
46+
47+
private static bool IsVariadicObjectFunc(Type targetType)
48+
{
49+
return targetType == typeof(Func<object[], object>);
50+
}
51+
52+
private static bool IsAction(Type targetType)
53+
{
54+
return IsUnaryAction(targetType) || IsVariadicObjectAction(targetType);
55+
}
56+
57+
private static bool IsFunc(Type targetType)
58+
{
59+
return IsUnaryFunc(targetType) || IsVariadicObjectFunc(targetType);
60+
}
61+
62+
private static bool IsCallable(Type targetType)
63+
{
64+
//Python.Runtime.ClassManager dtype
65+
return targetType.IsSubclassOf(typeof(MulticastDelegate));
66+
}
67+
68+
public static FunctionCodec Instance { get; } = new FunctionCodec();
69+
public bool CanDecode(PyObject objectType, Type targetType)
70+
{
71+
//python object must be callable
72+
if (!objectType.IsCallable()) return false;
73+
74+
//C# object must be callable
75+
if (!IsCallable(targetType))
76+
return false;
77+
78+
return true;
79+
}
80+
81+
private static object ConvertUnaryAction(PyObject pyObj)
82+
{
83+
Func<object> func = (Func<object>)ConvertUnaryFunc(pyObj);
84+
Action action = () => { func(); };
85+
return (object)action;
86+
}
87+
88+
private static object ConvertVariadicObjectAction(PyObject pyObj, int numArgs)
89+
{
90+
Func<object[], object> func = (Func<object[], object>)ConvertVariadicObjectFunc(pyObj, numArgs);
91+
Action<object[]> action = (object[] args) => { func(args); };
92+
return (object)action;
93+
}
94+
95+
//TODO share code between ConvertUnaryFunc and ConvertVariadicObjectFunc
96+
private static object ConvertUnaryFunc(PyObject pyObj)
97+
{
98+
Func<object> func = () =>
99+
{
100+
//Runtime.XIncref(pyObj.Handle);
101+
PyObject pyAction = new PyObject(pyObj.Handle);
102+
var pyArgs = new PyObject[0];
103+
using (Py.GIL())
104+
{
105+
var pyResult = pyAction.Invoke(pyArgs);
106+
return pyResult.As<object>();
107+
}
108+
};
109+
return (object)func;
110+
}
111+
112+
private static object ConvertVariadicObjectFunc(PyObject pyObj, int numArgs)
113+
{
114+
Func<object[], object> func = (object[] o) =>
115+
{
116+
//Runtime.XIncref(pyObj.Handle);
117+
PyObject pyAction = new PyObject(pyObj.Handle);
118+
var pyArgs = new PyObject[numArgs];
119+
int i = 0;
120+
foreach (object obj in o)
121+
{
122+
pyArgs[i++] = obj.ToPython();
123+
}
124+
125+
using (Py.GIL())
126+
{
127+
var pyResult = pyAction.Invoke(pyArgs);
128+
return pyResult.As<object>();
129+
}
130+
};
131+
return (object)func;
132+
}
133+
134+
public bool TryDecode<T>(PyObject pyObj, out T value)
135+
{
136+
value = default(T);
137+
var tT = typeof(T);
138+
if (!IsCallable(tT))
139+
return false;
140+
141+
var numArgs = GetNumArgs(pyObj);
142+
if (numArgs != GetNumArgs(tT))
143+
return false;
144+
145+
if (IsAction(tT))
146+
{
147+
object actionObj = null;
148+
if (numArgs == 0)
149+
{
150+
actionObj = ConvertUnaryAction(pyObj);
151+
}
152+
else
153+
{
154+
actionObj = ConvertVariadicObjectAction(pyObj, numArgs);
155+
}
156+
157+
value = (T)actionObj;
158+
return true;
159+
}
160+
else if (IsFunc(tT))
161+
{
162+
163+
object funcObj = null;
164+
if (numArgs == 0)
165+
{
166+
funcObj = ConvertUnaryFunc(pyObj);
167+
}
168+
else
169+
{
170+
funcObj = ConvertVariadicObjectFunc(pyObj, numArgs);
171+
}
172+
173+
value = (T)funcObj;
174+
return true;
175+
}
176+
else
177+
{
178+
return false;
179+
}
180+
}
181+
182+
public static void Register()
183+
{
184+
PyObjectConversions.RegisterDecoder(Instance);
185+
}
186+
}
187+
}

tests/FunctionCodecTests.cs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
using NUnit.Framework;
5+
using Python.Runtime.Codecs;
6+
7+
namespace Python.Runtime.Codecs
8+
{
9+
class FunctionCodecTests
10+
{
11+
[SetUp]
12+
public void SetUp()
13+
{
14+
PythonEngine.Initialize();
15+
}
16+
17+
18+
[TearDown]
19+
public void Dispose()
20+
{
21+
PythonEngine.Shutdown();
22+
}
23+
24+
[Test]
25+
public void FunctionAction()
26+
{
27+
var codec = FunctionCodec.Instance;
28+
29+
PyInt x = new PyInt(1);
30+
PyDict y = new PyDict();
31+
//non-callables can't be decoded into Action
32+
Assert.IsFalse(codec.CanDecode(x, typeof(Action)));
33+
Assert.IsFalse(codec.CanDecode(y, typeof(Action)));
34+
35+
var locals = new PyDict();
36+
PythonEngine.Exec(@"
37+
def foo():
38+
return 1
39+
def bar(a):
40+
return 2
41+
", null, locals.Handle);
42+
43+
//foo, the function with no arguments
44+
var fooFunc = locals.GetItem("foo");
45+
Assert.IsFalse(codec.CanDecode(fooFunc, typeof(bool)));
46+
47+
//CanDecode does not work for variadic actions
48+
//Assert.IsFalse(codec.CanDecode(fooFunc, typeof(Action<object[]>)));
49+
Assert.IsTrue(codec.CanDecode(fooFunc, typeof(Action)));
50+
51+
Action fooAction;
52+
Assert.IsTrue(codec.TryDecode(fooFunc, out fooAction));
53+
Assert.DoesNotThrow(() => fooAction());
54+
55+
//bar, the function with an argument
56+
var barFunc = locals.GetItem("bar");
57+
Assert.IsFalse(codec.CanDecode(barFunc, typeof(bool)));
58+
//Assert.IsFalse(codec.CanDecode(barFunc, typeof(Action)));
59+
Assert.IsTrue(codec.CanDecode(barFunc, typeof(Action<object[]>)));
60+
61+
Action<object[]> barAction;
62+
Assert.IsTrue(codec.TryDecode(barFunc, out barAction));
63+
Assert.DoesNotThrow(() => barAction(new[] { (object)true }));
64+
}
65+
66+
[Test]
67+
public void FunctionFunc()
68+
{
69+
var codec = FunctionCodec.Instance;
70+
71+
PyInt x = new PyInt(1);
72+
PyDict y = new PyDict();
73+
//non-callables can't be decoded into Func
74+
Assert.IsFalse(codec.CanDecode(x, typeof(Func<object>)));
75+
Assert.IsFalse(codec.CanDecode(y, typeof(Func<object>)));
76+
77+
var locals = new PyDict();
78+
PythonEngine.Exec(@"
79+
def foo():
80+
return 1
81+
def bar(a):
82+
return 2
83+
", null, locals.Handle);
84+
85+
//foo, the function with no arguments
86+
var fooFunc = locals.GetItem("foo");
87+
Assert.IsFalse(codec.CanDecode(fooFunc, typeof(bool)));
88+
89+
//CanDecode does not work for variadic actions
90+
//Assert.IsFalse(codec.CanDecode(fooFunc, typeof(Func<object[], object>)));
91+
Assert.IsTrue(codec.CanDecode(fooFunc, typeof(Func<object>)));
92+
93+
Func<object> foo;
94+
Assert.IsTrue(codec.TryDecode(fooFunc, out foo));
95+
object res1 = null;
96+
Assert.DoesNotThrow(() => res1 = foo());
97+
Assert.AreEqual(res1, 1);
98+
99+
//bar, the function with an argument
100+
var barFunc = locals.GetItem("bar");
101+
Assert.IsFalse(codec.CanDecode(barFunc, typeof(bool)));
102+
//Assert.IsFalse(codec.CanDecode(barFunc, typeof(Func<object>)));
103+
Assert.IsTrue(codec.CanDecode(barFunc, typeof(Func<object[], object>)));
104+
105+
Func<object[], object> bar;
106+
Assert.IsTrue(codec.TryDecode(barFunc, out bar));
107+
object res2 = null;
108+
Assert.DoesNotThrow(() => res2 = bar(new[] { (object)true }));
109+
Assert.AreEqual(res2, 2);
110+
}
111+
112+
}
113+
}

0 commit comments

Comments
 (0)