Skip to content

Commit 2e51321

Browse files
Joe LidbetterJoe Lidbetter
Joe Lidbetter
authored and
Joe Lidbetter
committed
Adds support to convert iterators to arrays
1 parent 6f635a4 commit 2e51321

File tree

4 files changed

+96
-13
lines changed

4 files changed

+96
-13
lines changed

AUTHORS.md

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
- Jan Krivanek ([@jakrivan](https://github.com/jakrivan))
3434
- Jeff Reback ([@jreback](https://github.com/jreback))
3535
- Joe Frayne ([@jfrayne](https://github.com/jfrayne))
36+
- Joe Lidbetter ([@jmlidbetter](https://github.com/jmlidbetter))
3637
- John Burnett ([@johnburnett](https://github.com/johnburnett))
3738
- John Wilkes ([@jbw3](https://github.com/jbw3))
3839
- Luke Stratman ([@lstratman](https://github.com/lstratman))

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
1515

1616
- Added argument types information to "No method matches given arguments" message
1717
- Moved wheel import in setup.py inside of a try/except to prevent pip collection failures
18+
- Added support for converting python iterators to C# arrays
1819

1920
### Fixed
2021

src/runtime/converter.cs

+63-13
Original file line numberDiff line numberDiff line change
@@ -837,48 +837,98 @@ private static void SetConversionError(IntPtr value, Type target)
837837

838838
/// <summary>
839839
/// Convert a Python value to a correctly typed managed array instance.
840-
/// The Python value must support the Python sequence protocol and the
841-
/// items in the sequence must be convertible to the target array type.
840+
/// The Python value must support either the Python sequence protocol or
841+
/// the Python iterator protocol and the items in the sequence must be
842+
/// convertible to the target array type.
842843
/// </summary>
843844
private static bool ToArray(IntPtr value, Type obType, out object result, bool setError)
844845
{
845846
Type elementType = obType.GetElementType();
846-
var size = Runtime.PySequence_Size(value);
847847
result = null;
848848

849-
if (size < 0)
849+
bool IsSeqObj = Runtime.PySequence_Check(value);
850+
851+
// If object does not support Sequence Protocol check if it supports iterator protocol
852+
IntPtr IterObject = IntPtr.Zero;
853+
if(!IsSeqObj)
850854
{
855+
IterObject = Runtime.PyObject_GetIter(value);
856+
}
857+
858+
if(!IsSeqObj && IterObject==IntPtr.Zero) {
859+
// Bail if both sequence check and iterator conversion attempt fail
851860
if (setError)
852861
{
853862
SetConversionError(value, obType);
854863
}
855864
return false;
856865
}
857866

858-
Array items = Array.CreateInstance(elementType, size);
867+
Array items;
859868

860869
// XXX - is there a better way to unwrap this if it is a real array?
861-
for (var i = 0; i < size; i++)
870+
if (IsSeqObj)
862871
{
863-
object obj = null;
864-
IntPtr item = Runtime.PySequence_GetItem(value, i);
865-
if (item == IntPtr.Zero)
872+
// If we have a sequence object use the sequence protocol
873+
var size = Runtime.PySequence_Size(value);
874+
if (size < 0)
866875
{
867876
if (setError)
868877
{
869878
SetConversionError(value, obType);
879+
}
880+
return false;
881+
}
882+
883+
items = Array.CreateInstance(elementType, size);
884+
for (var i = 0; i < size; i++)
885+
{
886+
object obj = null;
887+
IntPtr item = Runtime.PySequence_GetItem(value, i);
888+
if (item == IntPtr.Zero)
889+
{
890+
if (setError)
891+
{
892+
SetConversionError(value, obType);
893+
}
870894
return false;
871895
}
896+
897+
if (!Converter.ToManaged(item, elementType, out obj, true))
898+
{
899+
Runtime.XDecref(item);
900+
return false;
901+
}
902+
903+
items.SetValue(obj, i);
904+
Runtime.XDecref(item);
872905
}
906+
}
907+
else
908+
{
909+
// Otherwise use iterator protocol
910+
var listType = typeof(List<>);
911+
var constructedListType = listType.MakeGenericType(elementType);
912+
IList list = (IList) Activator.CreateInstance(constructedListType);
913+
IntPtr item;
873914

874-
if (!Converter.ToManaged(item, elementType, out obj, true))
915+
while ((item = Runtime.PyIter_Next(IterObject)) != IntPtr.Zero)
875916
{
917+
object obj = null;
918+
919+
if (!Converter.ToManaged(item, elementType, out obj, true))
920+
{
921+
Runtime.XDecref(item);
922+
return false;
923+
}
924+
925+
list.Add(obj);
876926
Runtime.XDecref(item);
877-
return false;
878927
}
928+
Runtime.XDecref(IterObject);
879929

880-
items.SetValue(obj, i);
881-
Runtime.XDecref(item);
930+
items = Array.CreateInstance(elementType, list.Count);
931+
list.CopyTo(items, 0);
882932
}
883933

884934
result = items;

src/tests/test_array.py

+31
Original file line numberDiff line numberDiff line change
@@ -1337,3 +1337,34 @@ def test_array_abuse():
13371337
with pytest.raises(TypeError):
13381338
desc = Test.PublicArrayTest.__dict__['__setitem__']
13391339
desc(0, 0, 0)
1340+
1341+
1342+
@pytest.mark.skipif(PY2, reason="Only applies in Python 3")
1343+
def test_iterator_to_array():
1344+
from System import Array, String
1345+
1346+
d = {"a": 1, "b": 2, "c": 3}
1347+
keys_iterator = iter(d.keys())
1348+
arr = Array[String](keys_iterator)
1349+
1350+
Array.Sort(arr)
1351+
1352+
assert arr[0] == "a"
1353+
assert arr[1] == "b"
1354+
assert arr[2] == "c"
1355+
1356+
1357+
@pytest.mark.skipif(PY2, reason="Only applies in Python 3")
1358+
def test_dict_keys_to_array():
1359+
from System import Array, String
1360+
1361+
d = {"a": 1, "b": 2, "c": 3}
1362+
d_keys = d.keys()
1363+
arr = Array[String](d_keys)
1364+
1365+
Array.Sort(arr)
1366+
1367+
assert arr[0] == "a"
1368+
assert arr[1] == "b"
1369+
assert arr[2] == "c"
1370+

0 commit comments

Comments
 (0)