Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit707ef36

Browse files
authored
Lossless encoders for IList<T>, IEnumerable<T> and ICollection<T> (#1084)
1 parent1ab9cb1 commit707ef36

File tree

11 files changed

+720
-19
lines changed

11 files changed

+720
-19
lines changed

‎src/embed_tests/Codecs.cs

Lines changed: 232 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,39 @@
11
namespacePython.EmbeddingTest{
22
usingSystem;
33
usingSystem.Collections.Generic;
4-
usingSystem.Text;
4+
usingSystem.Linq;
55
usingNUnit.Framework;
66
usingPython.Runtime;
77
usingPython.Runtime.Codecs;
88

9-
publicclassCodecs{
9+
publicclassCodecs
10+
{
1011
[SetUp]
11-
publicvoidSetUp(){
12+
publicvoidSetUp()
13+
{
1214
PythonEngine.Initialize();
1315
}
1416

1517
[TearDown]
16-
publicvoidDispose(){
18+
publicvoidDispose()
19+
{
1720
PythonEngine.Shutdown();
1821
}
1922

2023
[Test]
21-
publicvoidConversionsGeneric(){
22-
ConversionsGeneric<ValueTuple<int,string,object>,ValueTuple>();
24+
publicvoidTupleConversionsGeneric()
25+
{
26+
TupleConversionsGeneric<ValueTuple<int,string,object>,ValueTuple>();
2327
}
2428

25-
staticvoidConversionsGeneric<T,TTuple>(){
29+
staticvoidTupleConversionsGeneric<T,TTuple>()
30+
{
2631
TupleCodec<TTuple>.Register();
2732
vartuple=Activator.CreateInstance(typeof(T),42,"42",newobject());
2833
Trestored=default;
2934
using(Py.GIL())
30-
using(varscope=Py.CreateScope()){
35+
using(varscope=Py.CreateScope())
36+
{
3137
voidAccept(Tvalue)=>restored=value;
3238
varaccept=newAction<T>(Accept).ToPython();
3339
scope.Set(nameof(tuple),tuple);
@@ -38,15 +44,18 @@ static void ConversionsGeneric<T, TTuple>() {
3844
}
3945

4046
[Test]
41-
publicvoidConversionsObject(){
42-
ConversionsObject<ValueTuple<int,string,object>,ValueTuple>();
47+
publicvoidTupleConversionsObject()
48+
{
49+
TupleConversionsObject<ValueTuple<int,string,object>,ValueTuple>();
4350
}
44-
staticvoidConversionsObject<T,TTuple>(){
51+
staticvoidTupleConversionsObject<T,TTuple>()
52+
{
4553
TupleCodec<TTuple>.Register();
4654
vartuple=Activator.CreateInstance(typeof(T),42,"42",newobject());
4755
Trestored=default;
4856
using(Py.GIL())
49-
using(varscope=Py.CreateScope()){
57+
using(varscope=Py.CreateScope())
58+
{
5059
voidAccept(objectvalue)=>restored=(T)value;
5160
varaccept=newAction<object>(Accept).ToPython();
5261
scope.Set(nameof(tuple),tuple);
@@ -57,31 +66,236 @@ static void ConversionsObject<T, TTuple>() {
5766
}
5867

5968
[Test]
60-
publicvoidTupleRoundtripObject(){
69+
publicvoidTupleRoundtripObject()
70+
{
6171
TupleRoundtripObject<ValueTuple<int,string,object>,ValueTuple>();
6272
}
63-
staticvoidTupleRoundtripObject<T,TTuple>(){
73+
staticvoidTupleRoundtripObject<T,TTuple>()
74+
{
6475
vartuple=Activator.CreateInstance(typeof(T),42,"42",newobject());
65-
using(Py.GIL()){
76+
using(Py.GIL())
77+
{
6678
varpyTuple=TupleCodec<TTuple>.Instance.TryEncode(tuple);
6779
Assert.IsTrue(TupleCodec<TTuple>.Instance.TryDecode(pyTuple,outobjectrestored));
6880
Assert.AreEqual(expected:tuple,actual:restored);
6981
}
7082
}
7183

7284
[Test]
73-
publicvoidTupleRoundtripGeneric(){
85+
publicvoidTupleRoundtripGeneric()
86+
{
7487
TupleRoundtripGeneric<ValueTuple<int,string,object>,ValueTuple>();
7588
}
7689

77-
staticvoidTupleRoundtripGeneric<T,TTuple>(){
90+
staticvoidTupleRoundtripGeneric<T,TTuple>()
91+
{
7892
vartuple=Activator.CreateInstance(typeof(T),42,"42",newobject());
79-
using(Py.GIL()){
93+
using(Py.GIL())
94+
{
8095
varpyTuple=TupleCodec<TTuple>.Instance.TryEncode(tuple);
8196
Assert.IsTrue(TupleCodec<TTuple>.Instance.TryDecode(pyTuple,outTrestored));
8297
Assert.AreEqual(expected:tuple,actual:restored);
8398
}
8499
}
100+
101+
staticPyObjectGetPythonIterable()
102+
{
103+
using(Py.GIL())
104+
{
105+
returnPythonEngine.Eval("map(lambda x: x, [1,2,3])");
106+
}
107+
}
108+
109+
[Test]
110+
publicvoidListDecoderTest()
111+
{
112+
varcodec=ListDecoder.Instance;
113+
varitems=newList<PyObject>(){newPyInt(1),newPyInt(2),newPyInt(3)};
114+
115+
varpyList=newPyList(items.ToArray());
116+
117+
varpyListType=pyList.GetPythonType();
118+
Assert.IsTrue(codec.CanDecode(pyListType,typeof(IList<bool>)));
119+
Assert.IsTrue(codec.CanDecode(pyListType,typeof(IList<int>)));
120+
Assert.IsFalse(codec.CanDecode(pyListType,typeof(System.Collections.IEnumerable)));
121+
Assert.IsFalse(codec.CanDecode(pyListType,typeof(IEnumerable<int>)));
122+
Assert.IsFalse(codec.CanDecode(pyListType,typeof(ICollection<float>)));
123+
Assert.IsFalse(codec.CanDecode(pyListType,typeof(bool)));
124+
125+
//we'd have to copy into a list instance to do this, it would not be lossless.
126+
//lossy converters can be implemented outside of the python.net core library
127+
Assert.IsFalse(codec.CanDecode(pyListType,typeof(List<int>)));
128+
129+
//convert to list of int
130+
IList<int>intList=null;
131+
Assert.DoesNotThrow(()=>{codec.TryDecode(pyList,outintList);});
132+
CollectionAssert.AreEqual(intList,newList<object>{1,2,3});
133+
134+
//convert to list of string. This will not work.
135+
//The ListWrapper class will throw a python exception when it tries to access any element.
136+
//TryDecode is a lossless conversion so there will be no exception at that point
137+
//interestingly, since the size of the python list can be queried without any conversion,
138+
//the IList will report a Count of 3.
139+
IList<string>stringList=null;
140+
Assert.DoesNotThrow(()=>{codec.TryDecode(pyList,outstringList);});
141+
Assert.AreEqual(stringList.Count,3);
142+
Assert.Throws(typeof(InvalidCastException),()=>{varx=stringList[0];});
143+
144+
//can't convert python iterable to list (this will require a copy which isn't lossless)
145+
varfoo=GetPythonIterable();
146+
varfooType=foo.GetPythonType();
147+
Assert.IsFalse(codec.CanDecode(fooType,typeof(IList<int>)));
148+
}
149+
150+
[Test]
151+
publicvoidSequenceDecoderTest()
152+
{
153+
varcodec=SequenceDecoder.Instance;
154+
varitems=newList<PyObject>(){newPyInt(1),newPyInt(2),newPyInt(3)};
155+
156+
//SequenceConverter can only convert to any ICollection
157+
varpyList=newPyList(items.ToArray());
158+
//it can convert a PyList, since PyList satisfies the python sequence protocol
159+
160+
Assert.IsFalse(codec.CanDecode(pyList,typeof(bool)));
161+
Assert.IsFalse(codec.CanDecode(pyList,typeof(IList<int>)));
162+
Assert.IsFalse(codec.CanDecode(pyList,typeof(System.Collections.IEnumerable)));
163+
Assert.IsFalse(codec.CanDecode(pyList,typeof(IEnumerable<int>)));
164+
165+
Assert.IsTrue(codec.CanDecode(pyList,typeof(ICollection<float>)));
166+
Assert.IsTrue(codec.CanDecode(pyList,typeof(ICollection<string>)));
167+
Assert.IsTrue(codec.CanDecode(pyList,typeof(ICollection<int>)));
168+
169+
//convert to collection of int
170+
ICollection<int>intCollection=null;
171+
Assert.DoesNotThrow(()=>{codec.TryDecode(pyList,outintCollection);});
172+
CollectionAssert.AreEqual(intCollection,newList<object>{1,2,3});
173+
174+
//no python exception should have occurred during the above conversion and check
175+
Runtime.CheckExceptionOccurred();
176+
177+
//convert to collection of string. This will not work.
178+
//The SequenceWrapper class will throw a python exception when it tries to access any element.
179+
//TryDecode is a lossless conversion so there will be no exception at that point
180+
//interestingly, since the size of the python sequence can be queried without any conversion,
181+
//the IList will report a Count of 3.
182+
ICollection<string>stringCollection=null;
183+
Assert.DoesNotThrow(()=>{codec.TryDecode(pyList,outstringCollection);});
184+
Assert.AreEqual(3,stringCollection.Count());
185+
Assert.Throws(typeof(InvalidCastException),()=>{
186+
string[]array=newstring[3];
187+
stringCollection.CopyTo(array,0);
188+
});
189+
190+
Runtime.CheckExceptionOccurred();
191+
192+
//can't convert python iterable to collection (this will require a copy which isn't lossless)
193+
//python iterables do not satisfy the python sequence protocol
194+
varfoo=GetPythonIterable();
195+
varfooType=foo.GetPythonType();
196+
Assert.IsFalse(codec.CanDecode(fooType,typeof(ICollection<int>)));
197+
198+
//python tuples do satisfy the python sequence protocol
199+
varpyTuple=newPyTuple(items.ToArray());
200+
varpyTupleType=pyTuple.GetPythonType();
201+
202+
Assert.IsTrue(codec.CanDecode(pyTupleType,typeof(ICollection<float>)));
203+
Assert.IsTrue(codec.CanDecode(pyTupleType,typeof(ICollection<int>)));
204+
Assert.IsTrue(codec.CanDecode(pyTupleType,typeof(ICollection<string>)));
205+
206+
//convert to collection of int
207+
ICollection<int>intCollection2=null;
208+
Assert.DoesNotThrow(()=>{codec.TryDecode(pyTuple,outintCollection2);});
209+
CollectionAssert.AreEqual(intCollection2,newList<object>{1,2,3});
210+
211+
//no python exception should have occurred during the above conversion and check
212+
Runtime.CheckExceptionOccurred();
213+
214+
//convert to collection of string. This will not work.
215+
//The SequenceWrapper class will throw a python exception when it tries to access any element.
216+
//TryDecode is a lossless conversion so there will be no exception at that point
217+
//interestingly, since the size of the python sequence can be queried without any conversion,
218+
//the IList will report a Count of 3.
219+
ICollection<string>stringCollection2=null;
220+
Assert.DoesNotThrow(()=>{codec.TryDecode(pyTuple,outstringCollection2);});
221+
Assert.AreEqual(3,stringCollection2.Count());
222+
Assert.Throws(typeof(InvalidCastException),()=>{
223+
string[]array=newstring[3];
224+
stringCollection2.CopyTo(array,0);
225+
});
226+
227+
Runtime.CheckExceptionOccurred();
228+
229+
}
230+
231+
[Test]
232+
publicvoidIterableDecoderTest()
233+
{
234+
varcodec=IterableDecoder.Instance;
235+
varitems=newList<PyObject>(){newPyInt(1),newPyInt(2),newPyInt(3)};
236+
237+
varpyList=newPyList(items.ToArray());
238+
varpyListType=pyList.GetPythonType();
239+
Assert.IsFalse(codec.CanDecode(pyListType,typeof(IList<bool>)));
240+
Assert.IsTrue(codec.CanDecode(pyListType,typeof(System.Collections.IEnumerable)));
241+
Assert.IsTrue(codec.CanDecode(pyListType,typeof(IEnumerable<int>)));
242+
Assert.IsFalse(codec.CanDecode(pyListType,typeof(ICollection<float>)));
243+
Assert.IsFalse(codec.CanDecode(pyListType,typeof(bool)));
244+
245+
//ensure a PyList can be converted to a plain IEnumerable
246+
System.Collections.IEnumerableplainEnumerable1=null;
247+
Assert.DoesNotThrow(()=>{codec.TryDecode(pyList,outplainEnumerable1);});
248+
CollectionAssert.AreEqual(plainEnumerable1,newList<object>{1,2,3});
249+
250+
//can convert to any generic ienumerable. If the type is not assignable from the python element
251+
//it will lead to an empty iterable when decoding. TODO - should it throw?
252+
Assert.IsTrue(codec.CanDecode(pyListType,typeof(IEnumerable<int>)));
253+
Assert.IsTrue(codec.CanDecode(pyListType,typeof(IEnumerable<double>)));
254+
Assert.IsTrue(codec.CanDecode(pyListType,typeof(IEnumerable<string>)));
255+
256+
IEnumerable<int>intEnumerable=null;
257+
Assert.DoesNotThrow(()=>{codec.TryDecode(pyList,outintEnumerable);});
258+
CollectionAssert.AreEqual(intEnumerable,newList<object>{1,2,3});
259+
260+
Runtime.CheckExceptionOccurred();
261+
262+
IEnumerable<double>doubleEnumerable=null;
263+
Assert.DoesNotThrow(()=>{codec.TryDecode(pyList,outdoubleEnumerable);});
264+
CollectionAssert.AreEqual(doubleEnumerable,newList<object>{1,2,3});
265+
266+
Runtime.CheckExceptionOccurred();
267+
268+
IEnumerable<string>stringEnumerable=null;
269+
Assert.DoesNotThrow(()=>{codec.TryDecode(pyList,outstringEnumerable);});
270+
271+
Assert.Throws(typeof(InvalidCastException),()=>{
272+
foreach(stringiteminstringEnumerable)
273+
{
274+
varx=item;
275+
}
276+
});
277+
Assert.Throws(typeof(InvalidCastException),()=>{
278+
stringEnumerable.Count();
279+
});
280+
281+
Runtime.CheckExceptionOccurred();
282+
283+
//ensure a python class which implements the iterator protocol can be converter to a plain IEnumerable
284+
varfoo=GetPythonIterable();
285+
varfooType=foo.GetPythonType();
286+
System.Collections.IEnumerableplainEnumerable2=null;
287+
Assert.DoesNotThrow(()=>{codec.TryDecode(pyList,outplainEnumerable2);});
288+
CollectionAssert.AreEqual(plainEnumerable2,newList<object>{1,2,3});
289+
290+
//can convert to any generic ienumerable. If the type is not assignable from the python element
291+
//it will be an exception during TryDecode
292+
Assert.IsTrue(codec.CanDecode(fooType,typeof(IEnumerable<int>)));
293+
Assert.IsTrue(codec.CanDecode(fooType,typeof(IEnumerable<double>)));
294+
Assert.IsTrue(codec.CanDecode(fooType,typeof(IEnumerable<string>)));
295+
296+
Assert.DoesNotThrow(()=>{codec.TryDecode(pyList,outintEnumerable);});
297+
CollectionAssert.AreEqual(intEnumerable,newList<object>{1,2,3});
298+
}
85299
}
86300

87301
/// <summary>

‎src/runtime/Codecs/IterableDecoder.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
usingSystem;
2+
usingSystem.Collections.Generic;
3+
4+
namespacePython.Runtime.Codecs
5+
{
6+
publicclassIterableDecoder:IPyObjectDecoder
7+
{
8+
internalstaticboolIsIterable(TypetargetType)
9+
{
10+
//if it is a plain IEnumerable, we can decode it using sequence protocol.
11+
if(targetType==typeof(System.Collections.IEnumerable))
12+
returntrue;
13+
14+
if(!targetType.IsGenericType)
15+
returnfalse;
16+
17+
returntargetType.GetGenericTypeDefinition()==typeof(IEnumerable<>);
18+
}
19+
20+
internalstaticboolIsIterable(PyObjectobjectType)
21+
{
22+
returnobjectType.HasAttr("__iter__");
23+
}
24+
25+
publicboolCanDecode(PyObjectobjectType,TypetargetType)
26+
{
27+
returnIsIterable(objectType)&&IsIterable(targetType);
28+
}
29+
30+
publicboolTryDecode<T>(PyObjectpyObj,outTvalue)
31+
{
32+
//first see if T is a plan IEnumerable
33+
if(typeof(T)==typeof(System.Collections.IEnumerable))
34+
{
35+
objectenumerable=newCollectionWrappers.IterableWrapper<object>(pyObj);
36+
value=(T)enumerable;
37+
returntrue;
38+
}
39+
40+
varelementType=typeof(T).GetGenericArguments()[0];
41+
varcollectionType=typeof(CollectionWrappers.IterableWrapper<>).MakeGenericType(elementType);
42+
43+
varinstance=Activator.CreateInstance(collectionType,new[]{pyObj});
44+
value=(T)instance;
45+
returntrue;
46+
}
47+
48+
publicstaticIterableDecoderInstance{get;}=newIterableDecoder();
49+
50+
publicstaticvoidRegister()
51+
{
52+
PyObjectConversions.RegisterDecoder(Instance);
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp