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

Skip to content

Commit 4d1442d

Browse files
authored
Merge pull request #1022 from losttech/PR/Codecs
Codecs: customize set of Py<->C# conversions via PyObjectConversions
2 parents 770fc01 + 41de69d commit 4d1442d

14 files changed

+485
-18
lines changed

src/embed_tests/Codecs.cs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
namespace Python.EmbeddingTest {
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Text;
5+
using NUnit.Framework;
6+
using Python.Runtime;
7+
using Python.Runtime.Codecs;
8+
9+
public class Codecs {
10+
[SetUp]
11+
public void SetUp() {
12+
PythonEngine.Initialize();
13+
}
14+
15+
[TearDown]
16+
public void Dispose() {
17+
PythonEngine.Shutdown();
18+
}
19+
20+
[Test]
21+
public void ConversionsGeneric() {
22+
ConversionsGeneric<ValueTuple<int, string, object>, ValueTuple>();
23+
}
24+
25+
static void ConversionsGeneric<T, TTuple>() {
26+
TupleCodec<TTuple>.Register();
27+
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object());
28+
T restored = default;
29+
using (Py.GIL())
30+
using (var scope = Py.CreateScope()) {
31+
void Accept(T value) => restored = value;
32+
var accept = new Action<T>(Accept).ToPython();
33+
scope.Set(nameof(tuple), tuple);
34+
scope.Set(nameof(accept), accept);
35+
scope.Exec($"{nameof(accept)}({nameof(tuple)})");
36+
Assert.AreEqual(expected: tuple, actual: restored);
37+
}
38+
}
39+
40+
[Test]
41+
public void ConversionsObject() {
42+
ConversionsObject<ValueTuple<int, string, object>, ValueTuple>();
43+
}
44+
static void ConversionsObject<T, TTuple>() {
45+
TupleCodec<TTuple>.Register();
46+
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object());
47+
T restored = default;
48+
using (Py.GIL())
49+
using (var scope = Py.CreateScope()) {
50+
void Accept(object value) => restored = (T)value;
51+
var accept = new Action<object>(Accept).ToPython();
52+
scope.Set(nameof(tuple), tuple);
53+
scope.Set(nameof(accept), accept);
54+
scope.Exec($"{nameof(accept)}({nameof(tuple)})");
55+
Assert.AreEqual(expected: tuple, actual: restored);
56+
}
57+
}
58+
59+
[Test]
60+
public void TupleRoundtripObject() {
61+
TupleRoundtripObject<ValueTuple<int, string, object>, ValueTuple>();
62+
}
63+
static void TupleRoundtripObject<T, TTuple>() {
64+
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object());
65+
using (Py.GIL()) {
66+
var pyTuple = TupleCodec<TTuple>.Instance.TryEncode(tuple);
67+
Assert.IsTrue(TupleCodec<TTuple>.Instance.TryDecode(pyTuple, out object restored));
68+
Assert.AreEqual(expected: tuple, actual: restored);
69+
}
70+
}
71+
72+
[Test]
73+
public void TupleRoundtripGeneric() {
74+
TupleRoundtripGeneric<ValueTuple<int, string, object>, ValueTuple>();
75+
}
76+
77+
static void TupleRoundtripGeneric<T, TTuple>() {
78+
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object());
79+
using (Py.GIL()) {
80+
var pyTuple = TupleCodec<TTuple>.Instance.TryEncode(tuple);
81+
Assert.IsTrue(TupleCodec<TTuple>.Instance.TryDecode(pyTuple, out T restored));
82+
Assert.AreEqual(expected: tuple, actual: restored);
83+
}
84+
}
85+
}
86+
}

src/embed_tests/Python.EmbeddingTest.15.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
2424
<PythonBuildDir Condition="'$(TargetFramework)'=='net40' AND '$(PythonBuildDir)' == ''">$(SolutionDir)\bin\</PythonBuildDir>
2525
<PublishDir Condition="'$(TargetFramework)'!='net40'">$(OutputPath)\$(TargetFramework)_publish</PublishDir>
26-
<LangVersion>6</LangVersion>
26+
<LangVersion>7.3</LangVersion>
2727
<ErrorReport>prompt</ErrorReport>
2828
<CustomDefineConstants Condition="'$(CustomDefineConstants)' == ''">$(PYTHONNET_DEFINE_CONSTANTS)</CustomDefineConstants>
2929
<BaseDefineConstants>XPLAT</BaseDefineConstants>
@@ -77,6 +77,7 @@
7777

7878

7979
<ItemGroup>
80+
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
8081
<PackageReference Include="NUnit" Version="3.12.0" />
8182
<PackageReference Include="NUnit.ConsoleRunner" Version="3.11.1" />
8283
<PackageReference Include="NUnit3TestAdapter" Version="3.16.1">

src/embed_tests/Python.EmbeddingTest.csproj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<NoWarn>1591</NoWarn>
1515
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
1616
<PythonBuildDir Condition=" '$(PythonBuildDir)' == '' ">$(SolutionDir)\bin\</PythonBuildDir>
17-
<LangVersion>6</LangVersion>
17+
<LangVersion>7.3</LangVersion>
1818
<RestorePackages>true</RestorePackages>
1919
<ErrorReport>prompt</ErrorReport>
2020
</PropertyGroup>
@@ -73,13 +73,17 @@
7373
<Reference Include="nunit.framework, Version=3.12.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
7474
<HintPath>..\..\packages\NUnit.3.12.0\lib\net40\nunit.framework.dll</HintPath>
7575
</Reference>
76+
<Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
77+
<HintPath>..\..\packages\System.ValueTuple.4.5.0\lib\portable-net40+sl4+win8+wp8\System.ValueTuple.dll</HintPath>
78+
</Reference>
7679
<Reference Include="System" />
7780
</ItemGroup>
7881
<ItemGroup>
7982
<None Include="..\pythonnet.snk" />
8083
<None Include="packages.config" />
8184
</ItemGroup>
8285
<ItemGroup>
86+
<Compile Include="Codecs.cs" />
8387
<Compile Include="dynamic.cs" />
8488
<Compile Include="pyimport.cs" />
8589
<Compile Include="pyinitialize.cs" />

src/embed_tests/TestPyScope.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Threading;
23
using NUnit.Framework;
34
using Python.Runtime;
45

@@ -337,9 +338,12 @@ public void TestThread()
337338
//add function to the scope
338339
//can be call many times, more efficient than ast
339340
ps.Exec(
341+
"import clr\n" +
342+
"from System.Threading import Thread\n" +
340343
"def update():\n" +
341344
" global res, th_cnt\n" +
342345
" res += bb + 1\n" +
346+
" Thread.MemoryBarrier()\n" +
343347
" th_cnt += 1\n"
344348
);
345349
}
@@ -364,8 +368,9 @@ public void TestThread()
364368
{
365369
cnt = ps.Get<int>("th_cnt");
366370
}
367-
System.Threading.Thread.Sleep(10);
371+
Thread.Sleep(10);
368372
}
373+
Thread.MemoryBarrier();
369374
using (Py.GIL())
370375
{
371376
var result = ps.Get<int>("res");

src/embed_tests/packages.config

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
<?xml version="1.0" encoding="utf-8"?>
1+
<?xml version="1.0" encoding="utf-8"?>
22
<packages>
33
<package id="NUnit" version="3.12.0" targetFramework="net40" />
44
<package id="NUnit.ConsoleRunner" version="3.11.1" targetFramework="net40" />
5+
<package id="System.ValueTuple" version="4.5.0" targetFramework="net40" />
56
</packages>

src/runtime/Codecs/TupleCodecs.cs

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
namespace Python.Runtime.Codecs
2+
{
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Reflection;
7+
8+
[Obsolete(Util.UnstableApiMessage)]
9+
public sealed class TupleCodec<TTuple> : IPyObjectEncoder, IPyObjectDecoder
10+
{
11+
TupleCodec() { }
12+
public static TupleCodec<TTuple> Instance { get; } = new TupleCodec<TTuple>();
13+
14+
public bool CanEncode(Type type)
15+
{
16+
if (type == typeof(object) || type == typeof(TTuple)) return true;
17+
return type.Namespace == typeof(TTuple).Namespace
18+
// generic versions of tuples are named Tuple`TYPE_ARG_COUNT
19+
&& type.Name.StartsWith(typeof(TTuple).Name + '`');
20+
}
21+
22+
public PyObject TryEncode(object value)
23+
{
24+
if (value == null) return null;
25+
26+
var tupleType = value.GetType();
27+
if (tupleType == typeof(object)) return null;
28+
if (!this.CanEncode(tupleType)) return null;
29+
if (tupleType == typeof(TTuple)) return new PyTuple();
30+
31+
long fieldCount = tupleType.GetGenericArguments().Length;
32+
var tuple = Runtime.PyTuple_New(fieldCount);
33+
Exceptions.ErrorCheck(tuple);
34+
int fieldIndex = 0;
35+
foreach (FieldInfo field in tupleType.GetFields())
36+
{
37+
var item = field.GetValue(value);
38+
IntPtr pyItem = Converter.ToPython(item);
39+
Runtime.PyTuple_SetItem(tuple, fieldIndex, pyItem);
40+
fieldIndex++;
41+
}
42+
return new PyTuple(tuple);
43+
}
44+
45+
public bool CanDecode(PyObject objectType, Type targetType)
46+
=> objectType.Handle == Runtime.PyTupleType && this.CanEncode(targetType);
47+
48+
public bool TryDecode<T>(PyObject pyObj, out T value)
49+
{
50+
if (pyObj == null) throw new ArgumentNullException(nameof(pyObj));
51+
52+
value = default;
53+
54+
if (!Runtime.PyTuple_Check(pyObj.Handle)) return false;
55+
56+
if (typeof(T) == typeof(object))
57+
{
58+
bool converted = Decode(pyObj, out object result);
59+
if (converted)
60+
{
61+
value = (T)result;
62+
return true;
63+
}
64+
65+
return false;
66+
}
67+
68+
var itemTypes = typeof(T).GetGenericArguments();
69+
long itemCount = Runtime.PyTuple_Size(pyObj.Handle);
70+
if (itemTypes.Length != itemCount) return false;
71+
72+
if (itemCount == 0)
73+
{
74+
value = (T)EmptyTuple;
75+
return true;
76+
}
77+
78+
var elements = new object[itemCount];
79+
for (int itemIndex = 0; itemIndex < itemTypes.Length; itemIndex++)
80+
{
81+
IntPtr pyItem = Runtime.PyTuple_GetItem(pyObj.Handle, itemIndex);
82+
if (!Converter.ToManaged(pyItem, itemTypes[itemIndex], out elements[itemIndex], setError: false))
83+
{
84+
return false;
85+
}
86+
}
87+
var factory = tupleCreate[itemCount].MakeGenericMethod(itemTypes);
88+
value = (T)factory.Invoke(null, elements);
89+
return true;
90+
}
91+
92+
static bool Decode(PyObject tuple, out object value)
93+
{
94+
long itemCount = Runtime.PyTuple_Size(tuple.Handle);
95+
if (itemCount == 0)
96+
{
97+
value = EmptyTuple;
98+
return true;
99+
}
100+
var elements = new object[itemCount];
101+
var itemTypes = new Type[itemCount];
102+
value = null;
103+
for (int itemIndex = 0; itemIndex < elements.Length; itemIndex++)
104+
{
105+
var pyItem = Runtime.PyTuple_GetItem(tuple.Handle, itemIndex);
106+
if (!Converter.ToManaged(pyItem, typeof(object), out elements[itemIndex], setError: false))
107+
{
108+
return false;
109+
}
110+
111+
itemTypes[itemIndex] = elements[itemIndex]?.GetType() ?? typeof(object);
112+
}
113+
114+
var factory = tupleCreate[itemCount].MakeGenericMethod(itemTypes);
115+
value = factory.Invoke(null, elements);
116+
return true;
117+
}
118+
119+
static readonly MethodInfo[] tupleCreate =
120+
typeof(TTuple).GetMethods(BindingFlags.Public | BindingFlags.Static)
121+
.Where(m => m.Name == nameof(Tuple.Create))
122+
.OrderBy(m => m.GetParameters().Length)
123+
.ToArray();
124+
125+
static readonly object EmptyTuple = tupleCreate[0].Invoke(null, parameters: new object[0]);
126+
127+
public static void Register()
128+
{
129+
PyObjectConversions.RegisterEncoder(Instance);
130+
PyObjectConversions.RegisterDecoder(Instance);
131+
}
132+
}
133+
}

src/runtime/Python.Runtime.csproj

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<?xml version="1.0" encoding="utf-8"?>
1+
<?xml version="1.0" encoding="utf-8"?>
22
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
33
<PropertyGroup>
44
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -76,6 +76,8 @@
7676
<Reference Include="System" />
7777
</ItemGroup>
7878
<ItemGroup>
79+
<Compile Include="Codecs\TupleCodecs.cs" />
80+
<Compile Include="converterextensions.cs" />
7981
<Compile Include="finalizer.cs" />
8082
<Compile Include="Properties\AssemblyInfo.cs" />
8183
<Compile Include="..\SharedAssemblyInfo.cs">
@@ -175,4 +177,4 @@
175177
<Copy SourceFiles="$(TargetAssembly)" DestinationFolder="$(PythonBuildDir)" />
176178
<!--Copy SourceFiles="$(TargetAssemblyPdb)" Condition="Exists('$(TargetAssemblyPdb)')" DestinationFolder="$(PythonBuildDir)" /-->
177179
</Target>
178-
</Project>
180+
</Project>

src/runtime/Util.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ namespace Python.Runtime
55
{
66
internal static class Util
77
{
8+
internal const string UnstableApiMessage =
9+
"This API is unstable, and might be changed or removed in the next minor release";
10+
811
internal static Int64 ReadCLong(IntPtr tp, int offset)
912
{
1013
// On Windows, a C long is always 32 bits.

src/runtime/assemblymanager.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,7 @@ public static List<string> GetNames(string nsname)
452452
/// looking in the currently loaded assemblies for the named
453453
/// type. Returns null if the named type cannot be found.
454454
/// </summary>
455+
[Obsolete("Use LookupTypes and handle name conflicts")]
455456
public static Type LookupType(string qname)
456457
{
457458
foreach (Assembly assembly in assemblies)
@@ -465,6 +466,14 @@ public static Type LookupType(string qname)
465466
return null;
466467
}
467468

469+
/// <summary>
470+
/// Returns the <see cref="Type"/> objects for the given qualified name,
471+
/// looking in the currently loaded assemblies for the named
472+
/// type.
473+
/// </summary>
474+
public static IEnumerable<Type> LookupTypes(string qualifiedName)
475+
=> assemblies.Select(assembly => assembly.GetType(qualifiedName)).Where(type => type != null);
476+
468477
internal static Type[] GetTypes(Assembly a)
469478
{
470479
if (a.IsDynamic)

0 commit comments

Comments
 (0)