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

Skip to content

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

Merged
merged 17 commits into from
Feb 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
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
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 @@ -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">
Expand Down
6 changes: 5 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 @@ -73,13 +73,17 @@
<Reference Include="nunit.framework, Version=3.12.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\..\packages\NUnit.3.12.0\lib\net40\nunit.framework.dll</HintPath>
</Reference>
<Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.ValueTuple.4.5.0\lib\portable-net40+sl4+win8+wp8\System.ValueTuple.dll</HintPath>
</Reference>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<None Include="..\pythonnet.snk" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Compile Include="Codecs.cs" />
<Compile Include="dynamic.cs" />
<Compile Include="pyimport.cs" />
<Compile Include="pyinitialize.cs" />
Expand Down
7 changes: 6 additions & 1 deletion src/embed_tests/TestPyScope.cs
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;

Expand Down Expand Up @@ -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" +
" th_cnt += 1\n"
);
}
Expand All @@ -364,8 +368,9 @@ public void TestThread()
{
cnt = ps.Get<int>("th_cnt");
}
System.Threading.Thread.Sleep(10);
Thread.Sleep(10);
}
Thread.MemoryBarrier();
using (Py.GIL())
{
var result = ps.Get<int>("res");
Expand Down
3 changes: 2 additions & 1 deletion 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.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>
133 changes: 133 additions & 0 deletions src/runtime/Codecs/TupleCodecs.cs
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);
}
}
}
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 @@ -175,4 +177,4 @@
<Copy SourceFiles="$(TargetAssembly)" DestinationFolder="$(PythonBuildDir)" />
<!--Copy SourceFiles="$(TargetAssemblyPdb)" Condition="Exists('$(TargetAssemblyPdb)')" DestinationFolder="$(PythonBuildDir)" /-->
</Target>
</Project>
</Project>
3 changes: 3 additions & 0 deletions src/runtime/Util.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ namespace Python.Runtime
{
internal static class Util
{
internal const string UnstableApiMessage =
"This API is unstable, and might be changed or removed in the next minor release";

internal static Int64 ReadCLong(IntPtr tp, int offset)
{
// On Windows, a C long is always 32 bits.
Expand Down
9 changes: 9 additions & 0 deletions src/runtime/assemblymanager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ public static List<string> GetNames(string nsname)
/// looking in the currently loaded assemblies for the named
/// type. Returns null if the named type cannot be found.
/// </summary>
[Obsolete("Use LookupTypes and handle name conflicts")]
public static Type LookupType(string qname)
{
foreach (Assembly assembly in assemblies)
Expand All @@ -465,6 +466,14 @@ public static Type LookupType(string qname)
return null;
}

/// <summary>
/// Returns the <see cref="Type"/> objects for the given qualified name,
/// looking in the currently loaded assemblies for the named
/// type.
/// </summary>
public static IEnumerable<Type> LookupTypes(string qualifiedName)
=> assemblies.Select(assembly => assembly.GetType(qualifiedName)).Where(type => type != null);

internal static Type[] GetTypes(Assembly a)
{
if (a.IsDynamic)
Expand Down
Loading