- Notifications
You must be signed in to change notification settings - Fork750
Codecs: customize set of Py<->C# conversions via PyObjectConversions#1022
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
28143d5
6eca169
97e33c7
449338f
39b2347
daa2901
e8e3b4b
8818610
82f6b99
ec98209
50a3822
2e19f2c
44bfec2
399ae54
e2d3333
5619fb9
41de69d
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
namespace Python.EmbeddingTest { | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Text; | ||
using NUnit.Framework; | ||
using Python.Runtime; | ||
using Python.Runtime.Codecs; | ||
public class Codecs { | ||
[SetUp] | ||
public void SetUp() { | ||
PythonEngine.Initialize(); | ||
} | ||
[TearDown] | ||
public void Dispose() { | ||
PythonEngine.Shutdown(); | ||
} | ||
[Test] | ||
public void ConversionsGeneric() { | ||
ConversionsGeneric<ValueTuple<int, string, object>, ValueTuple>(); | ||
} | ||
static void ConversionsGeneric<T, TTuple>() { | ||
TupleCodec<TTuple>.Register(); | ||
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); | ||
T restored = default; | ||
using (Py.GIL()) | ||
using (var scope = Py.CreateScope()) { | ||
void Accept(T value) => restored = value; | ||
var accept = new Action<T>(Accept).ToPython(); | ||
scope.Set(nameof(tuple), tuple); | ||
scope.Set(nameof(accept), accept); | ||
scope.Exec($"{nameof(accept)}({nameof(tuple)})"); | ||
Assert.AreEqual(expected: tuple, actual: restored); | ||
} | ||
} | ||
[Test] | ||
public void ConversionsObject() { | ||
ConversionsObject<ValueTuple<int, string, object>, ValueTuple>(); | ||
} | ||
static void ConversionsObject<T, TTuple>() { | ||
TupleCodec<TTuple>.Register(); | ||
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); | ||
T restored = default; | ||
using (Py.GIL()) | ||
using (var scope = Py.CreateScope()) { | ||
void Accept(object value) => restored = (T)value; | ||
var accept = new Action<object>(Accept).ToPython(); | ||
scope.Set(nameof(tuple), tuple); | ||
scope.Set(nameof(accept), accept); | ||
scope.Exec($"{nameof(accept)}({nameof(tuple)})"); | ||
Assert.AreEqual(expected: tuple, actual: restored); | ||
} | ||
} | ||
[Test] | ||
public void TupleRoundtripObject() { | ||
TupleRoundtripObject<ValueTuple<int, string, object>, ValueTuple>(); | ||
} | ||
static void TupleRoundtripObject<T, TTuple>() { | ||
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); | ||
using (Py.GIL()) { | ||
var pyTuple = TupleCodec<TTuple>.Instance.TryEncode(tuple); | ||
Assert.IsTrue(TupleCodec<TTuple>.Instance.TryDecode(pyTuple, out object restored)); | ||
Assert.AreEqual(expected: tuple, actual: restored); | ||
} | ||
} | ||
[Test] | ||
public void TupleRoundtripGeneric() { | ||
TupleRoundtripGeneric<ValueTuple<int, string, object>, ValueTuple>(); | ||
} | ||
static void TupleRoundtripGeneric<T, TTuple>() { | ||
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); | ||
using (Py.GIL()) { | ||
var pyTuple = TupleCodec<TTuple>.Instance.TryEncode(tuple); | ||
Assert.IsTrue(TupleCodec<TTuple>.Instance.TryDecode(pyTuple, out T restored)); | ||
Assert.AreEqual(expected: tuple, actual: restored); | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -23,7 +23,7 @@ | ||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir> | ||
<PythonBuildDir Condition="'$(TargetFramework)'=='net40' AND '$(PythonBuildDir)' == ''">$(SolutionDir)\bin\</PythonBuildDir> | ||
<PublishDir Condition="'$(TargetFramework)'!='net40'">$(OutputPath)\$(TargetFramework)_publish</PublishDir> | ||
<LangVersion>7.3</LangVersion> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Is this intentional?@lostmsu MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Yes, I think I am using a few features. Is there a C# compiler, that does not support 7.3 yet? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. @lostmsu just wanted to make sure it wasn't added by mistake. | ||
<ErrorReport>prompt</ErrorReport> | ||
<CustomDefineConstants Condition="'$(CustomDefineConstants)' == ''">$(PYTHONNET_DEFINE_CONSTANTS)</CustomDefineConstants> | ||
<BaseDefineConstants>XPLAT</BaseDefineConstants> | ||
@@ -77,6 +77,7 @@ | ||
<ItemGroup> | ||
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> | ||
<PackageReference Include="NUnit" Version="3.12.0" /> | ||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.11.1" /> | ||
<PackageReference Include="NUnit3TestAdapter" Version="3.16.1"> | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
using System; | ||
using System.Threading; | ||
using NUnit.Framework; | ||
using Python.Runtime; | ||
@@ -337,9 +338,12 @@ public void TestThread() | ||
//add function to the scope | ||
//can be call many times, more efficient than ast | ||
ps.Exec( | ||
"import clr\n" + | ||
"from System.Threading import Thread\n" + | ||
"def update():\n" + | ||
" global res, th_cnt\n" + | ||
" res += bb + 1\n" + | ||
" Thread.MemoryBarrier()\n" + | ||
lostmsu marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
" th_cnt += 1\n" | ||
); | ||
} | ||
@@ -364,8 +368,9 @@ public void TestThread() | ||
{ | ||
cnt = ps.Get<int>("th_cnt"); | ||
} | ||
Thread.Sleep(10); | ||
} | ||
Thread.MemoryBarrier(); | ||
using (Py.GIL()) | ||
{ | ||
var result = ps.Get<int>("res"); | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<packages> | ||
<package id="NUnit" version="3.12.0" targetFramework="net40" /> | ||
<package id="NUnit.ConsoleRunner" version="3.11.1" targetFramework="net40" /> | ||
<package id="System.ValueTuple" version="4.5.0" targetFramework="net40" /> | ||
</packages> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
namespace Python.Runtime.Codecs | ||
{ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Reflection; | ||
[Obsolete(Util.UnstableApiMessage)] | ||
public sealed class TupleCodec<TTuple> : IPyObjectEncoder, IPyObjectDecoder | ||
{ | ||
TupleCodec() { } | ||
public static TupleCodec<TTuple> Instance { get; } = new TupleCodec<TTuple>(); | ||
public bool CanEncode(Type type) | ||
{ | ||
if (type == typeof(object) || type == typeof(TTuple)) return true; | ||
return type.Namespace == typeof(TTuple).Namespace | ||
// generic versions of tuples are named Tuple`TYPE_ARG_COUNT | ||
&& type.Name.StartsWith(typeof(TTuple).Name + '`'); | ||
} | ||
public PyObject TryEncode(object value) | ||
{ | ||
if (value == null) return null; | ||
var tupleType = value.GetType(); | ||
if (tupleType == typeof(object)) return null; | ||
if (!this.CanEncode(tupleType)) return null; | ||
if (tupleType == typeof(TTuple)) return new PyTuple(); | ||
long fieldCount = tupleType.GetGenericArguments().Length; | ||
var tuple = Runtime.PyTuple_New(fieldCount); | ||
Exceptions.ErrorCheck(tuple); | ||
int fieldIndex = 0; | ||
foreach (FieldInfo field in tupleType.GetFields()) | ||
{ | ||
var item = field.GetValue(value); | ||
IntPtr pyItem = Converter.ToPython(item); | ||
Runtime.PyTuple_SetItem(tuple, fieldIndex, pyItem); | ||
fieldIndex++; | ||
} | ||
return new PyTuple(tuple); | ||
} | ||
public bool CanDecode(PyObject objectType, Type targetType) | ||
=> objectType.Handle == Runtime.PyTupleType && this.CanEncode(targetType); | ||
public bool TryDecode<T>(PyObject pyObj, out T value) | ||
{ | ||
if (pyObj == null) throw new ArgumentNullException(nameof(pyObj)); | ||
value = default; | ||
if (!Runtime.PyTuple_Check(pyObj.Handle)) return false; | ||
if (typeof(T) == typeof(object)) | ||
{ | ||
bool converted = Decode(pyObj, out object result); | ||
if (converted) | ||
{ | ||
value = (T)result; | ||
return true; | ||
} | ||
return false; | ||
} | ||
var itemTypes = typeof(T).GetGenericArguments(); | ||
long itemCount = Runtime.PyTuple_Size(pyObj.Handle); | ||
if (itemTypes.Length != itemCount) return false; | ||
if (itemCount == 0) | ||
{ | ||
value = (T)EmptyTuple; | ||
return true; | ||
} | ||
var elements = new object[itemCount]; | ||
for (int itemIndex = 0; itemIndex < itemTypes.Length; itemIndex++) | ||
{ | ||
IntPtr pyItem = Runtime.PyTuple_GetItem(pyObj.Handle, itemIndex); | ||
if (!Converter.ToManaged(pyItem, itemTypes[itemIndex], out elements[itemIndex], setError: false)) | ||
{ | ||
return false; | ||
} | ||
} | ||
var factory = tupleCreate[itemCount].MakeGenericMethod(itemTypes); | ||
value = (T)factory.Invoke(null, elements); | ||
return true; | ||
} | ||
static bool Decode(PyObject tuple, out object value) | ||
{ | ||
long itemCount = Runtime.PyTuple_Size(tuple.Handle); | ||
if (itemCount == 0) | ||
{ | ||
value = EmptyTuple; | ||
return true; | ||
} | ||
var elements = new object[itemCount]; | ||
var itemTypes = new Type[itemCount]; | ||
value = null; | ||
for (int itemIndex = 0; itemIndex < elements.Length; itemIndex++) | ||
{ | ||
var pyItem = Runtime.PyTuple_GetItem(tuple.Handle, itemIndex); | ||
if (!Converter.ToManaged(pyItem, typeof(object), out elements[itemIndex], setError: false)) | ||
{ | ||
return false; | ||
} | ||
itemTypes[itemIndex] = elements[itemIndex]?.GetType() ?? typeof(object); | ||
} | ||
var factory = tupleCreate[itemCount].MakeGenericMethod(itemTypes); | ||
value = factory.Invoke(null, elements); | ||
return true; | ||
} | ||
static readonly MethodInfo[] tupleCreate = | ||
typeof(TTuple).GetMethods(BindingFlags.Public | BindingFlags.Static) | ||
.Where(m => m.Name == nameof(Tuple.Create)) | ||
.OrderBy(m => m.GetParameters().Length) | ||
.ToArray(); | ||
static readonly object EmptyTuple = tupleCreate[0].Invoke(null, parameters: new object[0]); | ||
public static void Register() | ||
{ | ||
PyObjectConversions.RegisterEncoder(Instance); | ||
PyObjectConversions.RegisterDecoder(Instance); | ||
} | ||
} | ||
} |
Uh oh!
There was an error while loading.Please reload this page.