-
Notifications
You must be signed in to change notification settings - Fork 762
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
28143d5
6eca169
97e33c7
449338f
39b2347
daa2901
e8e3b4b
8818610
82f6b99
ec98209
50a3822
2e19f2c
44bfec2
399ae54
e2d3333
5619fb9
41de69d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
added sample TupleCodec (only supporting ValueTuple)
- Loading branch information
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 |
---|---|---|
@@ -1,5 +1,6 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<packages> | ||
<package id="NUnit" version="3.7.1" targetFramework="net40" /> | ||
<package id="NUnit.ConsoleRunner" version="3.7.0" targetFramework="net40" /> | ||
</packages> | ||
<package id="System.ValueTuple" version="4.5.0" targetFramework="net40" /> | ||
</packages> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
namespace Python.Runtime.Codecs | ||
{ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Reflection; | ||
|
||
public sealed class TupleCodec<TTuple> : IPyObjectEncoder, IPyObjectDecoder | ||
{ | ||
TupleCodec() { } | ||
public static TupleCodec<TTuple> Instance { get; } = new TupleCodec<TTuple>(); | ||
|
||
public bool CanEncode(Type type) | ||
=> type.Namespace == typeof(TTuple).Namespace && type.Name.StartsWith(typeof(TTuple).Name + '`') | ||
|| type == typeof(object) || type == typeof(TTuple); | ||
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. Maybe write this one out as a normal function block, it's a bit tedious to read. 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. Also, can't you just use |
||
|
||
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.XIncref(pyItem); | ||
Runtime.PyTuple_SetItem(tuple, fieldIndex, pyItem); | ||
fieldIndex++; | ||
} | ||
return new PyTuple(Runtime.SelfIncRef(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); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -135,8 +135,16 @@ internal static IntPtr ToPython(object value, Type type) | |
return result; | ||
} | ||
|
||
if (value is IList && !(value is INotifyPropertyChanged) && value.GetType().IsGenericType) | ||
{ | ||
if (Type.GetTypeCode(type) == TypeCode.Object && value.GetType() != typeof(object)) { | ||
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. glad this comes before other builtin conversions but ultimately the built-in conversions should probably use this codec system internally. |
||
var encoded = PyObjectConversions.TryEncode(value, type); | ||
if (encoded != null) { | ||
Runtime.XIncref(encoded.Handle); | ||
return encoded.Handle; | ||
} | ||
} | ||
|
||
if (value is IList && !(value is INotifyPropertyChanged) && value.GetType().IsGenericType) | ||
{ | ||
using (var resultlist = new PyList()) | ||
{ | ||
foreach (object o in (IEnumerable)value) | ||
|
@@ -437,9 +445,21 @@ internal static bool ToManagedValue(IntPtr value, Type obType, | |
return false; | ||
} | ||
|
||
TypeCode typeCode = Type.GetTypeCode(obType); | ||
if (typeCode == TypeCode.Object) | ||
{ | ||
IntPtr pyType = Runtime.PyObject_TYPE(value); | ||
if (PyObjectConversions.TryDecode(value, pyType, obType, out result)) | ||
{ | ||
return true; | ||
} | ||
} | ||
|
||
return ToPrimitive(value, obType, out result, setError); | ||
} | ||
|
||
internal delegate bool TryConvertFromPythonDelegate(IntPtr pyObj, out object result); | ||
|
||
/// <summary> | ||
/// Convert a Python value to an instance of a primitive managed type. | ||
/// </summary> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this intentional? @lostmsu
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The 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?
It is 7.3 in Runtime for a few months now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lostmsu just wanted to make sure it wasn't added by mistake.