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

Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
enable expanding set of marshaling conversions via PyObjectConversions
added sample TupleCodec (only supporting ValueTuple)
  • Loading branch information
lostmsu committed Jan 30, 2020
commit 28143d5f348a0edd891a2d8fa5249f7aab98582f
86 changes: 86 additions & 0 deletions src/embed_tests/Codecs.cs
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);
}
}
}
}
3 changes: 2 additions & 1 deletion src/embed_tests/Python.EmbeddingTest.15.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -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>6</LangVersion>
<LangVersion>7.3</LangVersion>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this intentional? @lostmsu

Copy link
Member Author

@lostmsu lostmsu Dec 22, 2019

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.

Copy link
Contributor

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.

<ErrorReport>prompt</ErrorReport>
<CustomDefineConstants Condition="'$(CustomDefineConstants)' == ''">$(PYTHONNET_DEFINE_CONSTANTS)</CustomDefineConstants>
<BaseDefineConstants>XPLAT</BaseDefineConstants>
Expand Down Expand Up @@ -81,6 +81,7 @@
<PackageReference Include="NUnit.ConsoleRunner" Version="3.7.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
<PackageReference Include="NUnitLite" Version="3.7.2" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.0'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
Expand Down
3 changes: 2 additions & 1 deletion src/embed_tests/Python.EmbeddingTest.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<NoWarn>1591</NoWarn>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
<PythonBuildDir Condition=" '$(PythonBuildDir)' == '' ">$(SolutionDir)\bin\</PythonBuildDir>
<LangVersion>6</LangVersion>
<LangVersion>7.3</LangVersion>
<RestorePackages>true</RestorePackages>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
Expand Down Expand Up @@ -80,6 +80,7 @@
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Compile Include="Codecs.cs" />
<Compile Include="dynamic.cs" />
<Compile Include="pyimport.cs" />
<Compile Include="pyinitialize.cs" />
Expand Down
5 changes: 3 additions & 2 deletions src/embed_tests/packages.config
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>
129 changes: 129 additions & 0 deletions src/runtime/Codecs/TupleCodecs.cs
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);
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, can't you just use type.IsGenericType && type.GetGenericTypeDefinition() == typeof(TTuple) or something like that?


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);
}
}
}
6 changes: 4 additions & 2 deletions src/runtime/Python.Runtime.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
Expand Down Expand Up @@ -76,6 +76,8 @@
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="Codecs\TupleCodecs.cs" />
<Compile Include="converterextensions.cs" />
<Compile Include="finalizer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="..\SharedAssemblyInfo.cs">
Expand Down Expand Up @@ -172,4 +174,4 @@
<Copy SourceFiles="$(TargetAssembly)" DestinationFolder="$(PythonBuildDir)" />
<!--Copy SourceFiles="$(TargetAssemblyPdb)" Condition="Exists('$(TargetAssemblyPdb)')" DestinationFolder="$(PythonBuildDir)" /-->
</Target>
</Project>
</Project>
24 changes: 22 additions & 2 deletions src/runtime/converter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Copy link
Contributor

Choose a reason for hiding this comment

The 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)
Expand Down Expand Up @@ -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>
Expand Down
Loading