From c7be482f9d1ce5825e9881174c4714391908ae22 Mon Sep 17 00:00:00 2001 From: Joe Lidbetter Date: Thu, 1 Aug 2019 14:21:16 +0100 Subject: [PATCH] Adds support to convert iterators to arrays --- AUTHORS.md | 1 + CHANGELOG.md | 1 + src/runtime/converter.cs | 37 ++++++++++++++++++++----------------- src/tests/test_array.py | 31 +++++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 17 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 6c2817aeb..39c2eb180 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -34,6 +34,7 @@ - Jan Krivanek ([@jakrivan](https://github.com/jakrivan)) - Jeff Reback ([@jreback](https://github.com/jreback)) - Joe Frayne ([@jfrayne](https://github.com/jfrayne)) +- Joe Lidbetter ([@jmlidbetter](https://github.com/jmlidbetter)) - John Burnett ([@johnburnett](https://github.com/johnburnett)) - John Wilkes ([@jbw3](https://github.com/jbw3)) - Luke Stratman ([@lstratman](https://github.com/lstratman)) diff --git a/CHANGELOG.md b/CHANGELOG.md index 705b33b7d..941045aef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Added argument types information to "No method matches given arguments" message - Moved wheel import in setup.py inside of a try/except to prevent pip collection failures - Removes PyLong_GetMax and PyClass_New when targetting Python3 +- Added support for converting python iterators to C# arrays ### Fixed diff --git a/src/runtime/converter.cs b/src/runtime/converter.cs index 11c67bf82..5d8769a73 100644 --- a/src/runtime/converter.cs +++ b/src/runtime/converter.cs @@ -837,17 +837,20 @@ private static void SetConversionError(IntPtr value, Type target) /// /// Convert a Python value to a correctly typed managed array instance. - /// The Python value must support the Python sequence protocol and the + /// The Python value must support the Python iterator protocol or and the /// items in the sequence must be convertible to the target array type. /// private static bool ToArray(IntPtr value, Type obType, out object result, bool setError) { Type elementType = obType.GetElementType(); - var size = Runtime.PySequence_Size(value); result = null; - if (size < 0) - { + bool IsSeqObj = Runtime.PySequence_Check(value); + var len = IsSeqObj ? Runtime.PySequence_Size(value) : -1; + + IntPtr IterObject = Runtime.PyObject_GetIter(value); + + if(IterObject==IntPtr.Zero) { if (setError) { SetConversionError(value, obType); @@ -855,21 +858,17 @@ private static bool ToArray(IntPtr value, Type obType, out object result, bool s return false; } - Array items = Array.CreateInstance(elementType, size); + Array items; + + var listType = typeof(List<>); + var constructedListType = listType.MakeGenericType(elementType); + IList list = IsSeqObj ? (IList) Activator.CreateInstance(constructedListType, new Object[] {(int) len}) : + (IList) Activator.CreateInstance(constructedListType); + IntPtr item; - // XXX - is there a better way to unwrap this if it is a real array? - for (var i = 0; i < size; i++) + while ((item = Runtime.PyIter_Next(IterObject)) != IntPtr.Zero) { object obj = null; - IntPtr item = Runtime.PySequence_GetItem(value, i); - if (item == IntPtr.Zero) - { - if (setError) - { - SetConversionError(value, obType); - return false; - } - } if (!Converter.ToManaged(item, elementType, out obj, true)) { @@ -877,10 +876,14 @@ private static bool ToArray(IntPtr value, Type obType, out object result, bool s return false; } - items.SetValue(obj, i); + list.Add(obj); Runtime.XDecref(item); } + Runtime.XDecref(IterObject); + items = Array.CreateInstance(elementType, list.Count); + list.CopyTo(items, 0); + result = items; return true; } diff --git a/src/tests/test_array.py b/src/tests/test_array.py index 7ccadddff..b492a66d3 100644 --- a/src/tests/test_array.py +++ b/src/tests/test_array.py @@ -1337,3 +1337,34 @@ def test_array_abuse(): with pytest.raises(TypeError): desc = Test.PublicArrayTest.__dict__['__setitem__'] desc(0, 0, 0) + + +@pytest.mark.skipif(PY2, reason="Only applies in Python 3") +def test_iterator_to_array(): + from System import Array, String + + d = {"a": 1, "b": 2, "c": 3} + keys_iterator = iter(d.keys()) + arr = Array[String](keys_iterator) + + Array.Sort(arr) + + assert arr[0] == "a" + assert arr[1] == "b" + assert arr[2] == "c" + + +@pytest.mark.skipif(PY2, reason="Only applies in Python 3") +def test_dict_keys_to_array(): + from System import Array, String + + d = {"a": 1, "b": 2, "c": 3} + d_keys = d.keys() + arr = Array[String](d_keys) + + Array.Sort(arr) + + assert arr[0] == "a" + assert arr[1] == "b" + assert arr[2] == "c" +