8000 full set of list codecs · pythonnet/pythonnet@cc6a92f · GitHub
[go: up one dir, main page]

Skip to content

Commit cc6a92f

Browse files
committed
full set of list codecs
1 parent d9e1e2c commit cc6a92f

File tree

2 files changed

+243
-23
lines changed

2 files changed

+243
-23
lines changed

src/embed_tests/Codecs.cs

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public void ListCodecTest()
100100
//maybe there can be a flag on listcodec to allow it.
101101
Assert.IsFalse(codec.CanDecode(x, typeof(List<int>)));
102102

103-
Action<System.Collections.IEnumerable> checkPlainEnumerable = (System.Collections.IEnumerable enumerable) =>
103+
Action<System.Collections.IEnumerable> checkUsingEnumerable = (System.Collections.IEnumerable enumerable) =>
104104
{
105105
Assert.IsNotNull(enumerable);
106106
IList<object> list = null;
@@ -111,14 +111,69 @@ public void ListCodecTest()
111111
Assert.AreEqual(list[2], 3);
112112
};
113113

114+
Action<System.Collections.IEnumerable> checkEmptyUsingEnumerable = (System.Collections.IEnumerable enumerable) =>
115+
{
116+
Assert.IsNotNull(enumerable);
117+
IList<object> list = null;
118+
list = enumerable.Cast<object>().ToList();
119+
Assert.AreEqual(list.Count, 0);
120+
};
121+
114122
//ensure a PyList can be converted to a plain IEnumerable
115123
System.Collections.IEnumerable plainEnumerable1 = null;
116124
Assert.DoesNotThrow(() => { codec.TryDecode<System.Collections.IEnumerable>(x, out plainEnumerable1); });
117-
checkPlainEnumerable(plainEnumerable1);
125+
checkUsingEnumerable(plainEnumerable1);
126+
127+
//can convert to any generic ienumerable. If the type is not assignable from the python element
128+
//it will be an exception during TryDecode
129+
Assert.IsTrue(codec.CanDecode(x, typeof(IEnumerable<int>)));
130+
Assert.IsTrue(codec.CanDecode(x, typeof(IEnumerable<double>)));
131+
Assert.IsTrue(codec.CanDecode(x, typeof(IEnumerable<string>)));
132+
133+
//cannot convert to ICollection or IList of any type since the python type is only iterable
134+
Assert.IsTrue(codec.CanDecode(x, typeof(ICollection<string>)));
135+
Assert.IsTrue(codec.CanDecode(x, typeof(ICollection<int>)));
136+
Assert.IsTrue(codec.CanDecode(x, typeof(IList<int>)));
137+
138+
IEnumerable<int> intEnumerable = null;
139+
Assert.DoesNotThrow(() => { codec.TryDecode<IEnumerable<int>>(x, out intEnumerable); });
140+
checkUsingEnumerable(intEnumerable);
141+
142+
Runtime.CheckExceptionOccurred();
143+
144+
IEnumerable<double> doubleEnumerable = null;
145+
Assert.DoesNotThrow(() => { codec.TryDecode<IEnumerable<double>>(x, out doubleEnumerable); });
146+
checkUsingEnumerable(doubleEnumerable);
147+
148+
Runtime.CheckExceptionOccurred();
149+
150+
IEnumerable<string> stringEnumerable = null;
151+
Assert.DoesNotThrow(() => { codec.TryDecode<IEnumerable<string>>(x, out stringEnumerable); });
152+
checkEmptyUsingEnumerable(stringEnumerable);
153+
154+
Runtime.CheckExceptionOccurred();
155+
156+
ICollection<string> stringCollection = null;
157+
Assert.DoesNotThrow(() => { codec.TryDecode<ICollection<string>>(x, out stringCollection); });
158+
checkEmptyUsingEnumerable(stringCollection);
159+
160+
Runtime.CheckExceptionOccurred();
161+
162+
ICollection<int> intCollection = null;
163+
Assert.DoesNotThrow(() => { codec.TryDecode<ICollection<int>>(x, out intCollection); });
164+
checkUsingEnumerable(intCollection);
165+
166+
Runtime.CheckExceptionOccurred();
167+
168+
IList<int> intList = null;
169+
Assert.DoesNotThrow(() => { codec.TryDecode<IList<int>>(x, out intList); });
170+
checkUsingEnumerable(intList);
118171

119172
//ensure a python class which implements the iterator protocol can be converter to a plain IEnumerable
120-
var locals = new PyDict();
121-
PythonEngine.Exec(@"
173+
var locals = new PyDict();
174+
using (Py.GIL())
175+
{
176+
PythonEngine.Exec(@"
122177
class foo():
123178
def __init__(self):
124179
self.counter = 0
@@ -131,11 +186,12 @@ raise StopIteration
131186
return self.counter
132187
foo_instance = foo()
133188
", null, locals.Handle);
189+
}
134190

135191
var foo = locals.GetItem("foo_instance");
136192
System.Collections.IEnumerable plainEnumerable2 = null;
137193
Assert.DoesNotThrow(() => { codec.TryDecode<System.Collections.IEnumerable>(x, out plainEnumerable2); });
138-
checkPlainEnumerable(plainEnumerable2);
194+
checkUsingEnumerable(plainEnumerable2);
139195

140196
//can convert to any generic ienumerable. If the type is not assignable from the python element
141197
//it will be an exception during TryDecode
@@ -148,9 +204,8 @@ raise StopIteration
148204
Assert.IsFalse(codec.CanDecode(foo, typeof(ICollection<int>)));
149205
Assert.IsFalse(codec.CanDecode(foo, typeof(IList<int>)));
150206

151-
IEnumerable<int> intEnumerable = null;
152207
Assert.DoesNotThrow(() => { codec.TryDecode<IEnumerable<int>>(x, out intEnumerable); });
153-
checkPlainEnumerable(intEnumerable);
208+
checkUsingEnumerable(intEnumerable);
154209
}
155210
}
156211
}

src/runtime/Codecs/ListCodec.cs

Lines changed: 181 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ private Tuple<CollectionRank, Type> GetRankAndType(Type collectionType)
7575

7676
public bool CanDecode(PyObject objectType, Type targetType)
7777
{
78+
//TODO - convert pyTuple to IReadOnlyList
79+
7880
//get the python object rank
7981
var pyRank = GetRank(objectType);
8082
if (pyRank == CollectionRank.None)
@@ -92,12 +94,14 @@ public bool CanDecode(PyObject objectType, Type targetType)
9294
return (int)pyRank >= (int)clrRank;
9395
}
9496

95-
private class GenericPyEnumerable<T> : IEnumerable<T>
97+
private class PyEnumerable<T> : IEnumerable<T>
9698
{
9799
protected PyObject iterObject;
100+
protected PyObject pyObject;
98101

99-
internal GenericPyEnumerable(PyObject pyObj)
102+
public PyEnumerable(PyObject pyObj)
100103
{
104+
pyObject = pyObj;
101105
iterObject = new PyObject(Runtime.PyObject_GetIter(pyObj.Handle));
102106
}
103107

@@ -109,6 +113,7 @@ IEnumerator IEnumerable.GetEnumerator()
109113
object obj = null;
110114
if (!Converter.ToManaged(item, typeof(object), out obj, true))
111115
{
116+
Exceptions.Clear();
112117
Runtime.XDecref(item);
113118
break;
114119
}
@@ -126,6 +131,7 @@ public IEnumerator<T> GetEnumerator()
126131
object obj = null;
127132
if (!Converter.ToManaged(item, typeof(T), out obj, true))
128133
{
134+
Exceptions.Clear();
129135
Runtime.XDecref(item);
130136
break;
131137
}
@@ -136,35 +142,194 @@ public IEnumerator<T> GetEnumerator()
136142
}
137143
}
138144

139-
private object ToPlainEnumerable(PyObject pyObj)
145+
private class PyCollection<T> : PyEnumerable<T>, ICollection<T>
140146
{
141-
return new GenericPyEnumerable<object>(pyObj);
147+
public PyCollection(PyObject pyObj) : base(pyObj)
148+
{
149+
150+
}
151+
152+
public int Count
153+
{
154+
get
155+
{
156+
return (int)Runtime.PySequence_Size(pyObject.Handle);
157+
}
158+
}
159+
160+
public virtual bool IsReadOnly => false;
161+
162+
public virtual void Add(T item)
163+
{
164+
//not implemented for Python sequence rank
165+
throw new NotImplementedException();
166+
}
167+
168+
public void Clear()
169+
{
170+
if (IsReadOnly)
171+
throw new NotImplementedException();
172+
var result = Runtime.PySequence_DelSlice(pyObject.Handle, 0, Count);
173+
if (result == -1)
174+
throw new Exception("failed to clear sequence");
175+
}
176+
177+
public bool Contains(T item)
178+
{
179+
//not sure if IEquatable is implemented and this will work!
180+
foreach (var element in this)
181+
if (element.Equals(item)) return true;
182+
183+
return false;
184+
}
185+
186+
protected T getItem(int index)
187+
{
188+
IntPtr item = Runtime.PySequence_GetItem(pyObject.Handle, index);
189+
object obj;
190+
191+
if (!Converter.ToManaged(item, typeof(T), out obj, true))
192+
{
193+
Exceptions.Clear();
194+
Runtime.XDecref(item);
195+
Exceptions.RaiseTypeError("wrong type in sequence");
196+
}
197+
198+
return (T)obj;
199+
}
200+
201+
public void CopyTo(T[] array, int arrayIndex)
202+
{
203+
for (int index = 0; index < Count; index++)
204+
{
205+
array[index + arrayIndex] = getItem(index);
206+
}
207+
}
208+
209+
protected bool removeAt(int index)
210+
{
211+
if (IsReadOnly)
212+
throw new NotImplementedException();
213+
if (index >= Count || index < 0)
214+
throw new IndexOutOfRangeException();
215+
216+
return Runtime.PySequence_DelItem(pyObject.Handle, index) != 0;
217+
}
218+
219+
protected int indexOf(T item)
220+
{
221+
var index = 0;
222+
foreach (var element in this)
223+
{
224+
if (element.Equals(item)) return index;
225+
index++;
226+
}
227+
228+
return -1;
229+
}
230+
231+
public bool Remove(T item)
232+
{
233+
return removeAt(indexOf(item));
234+
}
142235
}
143-
private object ToEnumerable<T>(PyObject pyObj)
236+
237+
private class PyList<T> : PyCollection<T>, IList<T>
144238
{
145-
return new GenericPyEnumerable<T>(pyObj);
239+
public PyList(PyObject pyObj) : base(pyObj)
240+
{
241+
242+
}
243+
244+
public T this[int index]
245+
{
246+
get
247+
{
248+
IntPtr item = Runtime.PySequence_GetItem(pyObject.Handle, index);
249+
object obj;
250+
251+
if (!Converter.ToManaged(item, typeof(T), out obj, true))
252+
{
253+
Exceptions.Clear();
254+
Runtime.XDecref(item);
255+
Exceptions.RaiseTypeError("wrong type in sequence");
256+
}
257+
258+
return (T)obj;
259+
}
260+
set
261+
{
262+
IntPtr pyItem = Converter.ToPython(value, typeof(T));
263+
if (pyItem == IntPtr.Zero)
264+
throw new Exception("failed to set item");
265+
266+
var result = Runtime.PySequence_SetItem(pyObject.Handle, index, pyItem);
267+
Runtime.XDecref(pyItem);
268+
if (result == -1)
269+
throw new Exception("failed to set item");
270+
}
271+
}
272+
273+
public int IndexOf(T item)
274+
{
275+
return indexOf(item);
276+
}
277+
278+
public void Insert(int index, T item)
279+
{
280+
if (IsReadOnly)
281+
throw new NotImplementedException();
282+
283+
IntPtr pyItem = Converter.ToPython(item, typeof(T));
284+
if (pyItem == IntPtr.Zero)
285+
throw new Exception("failed to insert item");
286+
287+
var result = Runtime.PyList_Insert(pyObject.Handle, index, pyItem);
288+
Runtime.XDecref(pyItem);
289+
if (result == -1)
290+
throw new Exception("failed to insert item");
291+
}
292+
293+
public void RemoveAt(int index)
294+
{
295+
removeAt(index);
296+
}
146297
}
147298

148299
F438 public bool TryDecode<T>(PyObject pyObj, out T value)
149300
{
150-
object var = null;
151301
//first see if T is a plan IEnumerable
152302
if (typeof(T) == typeof(System.Collections.IEnumerable))
153303
{
154-
var = new GenericPyEnumerable<object>(pyObj);
304+
object enumerable = new PyEnumerable<object>(pyObj);
305+
value = (T)enumerable;
306+
return true;
155307
}
156308

157309
//next use the rank to return the appropriate type
158-
var clrRank = GetRank(typeof(T));
159-
if (clrRank == CollectionRank.Iterable)
160-
var = new GenericPyEnumerable<int>(pyObj);
161-
else
310+
var rankAndType = GetRankAndType(typeof(T));
311+
if (rankAndType.Item1 == CollectionRank.None)
312+
throw new Exception("expected collection rank");
313+
314+
315+
var itemType = rankAndType.Item2;
316+
Type collectionType = null;
317+
if (rankAndType.Item1 == CollectionRank.Iterable)
162318
{
163-
//var = null;
319+
collectionType = typeof(PyEnumerable<>).MakeGenericType(itemType);
164320
}
165-
166-
value = (T)var;
167-
return false;
321+
else if (rankAndType.Item1 == CollectionRank.Sequence)
322+
{
323+
collectionType = typeof(PyCollection<>).MakeGenericType(itemType);
324+
}
325+
else if (rankAndType.Item1 == CollectionRank.List)
326+
{
327+
collectionType = typeof(PyList<>).MakeGenericType(itemType);
328+
}
329+
330+
var instance = Activator.CreateInstance(collectionType, new[] { pyObj });
331+
value = (T)instance;
332+
return true;
168333
}
169334

170335
public static ListCodec Instance { get; } = new ListCodec();

0 commit comments

Comments
 (0)
0