diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c999d668..c22ac2f16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Added automatic NuGet package generation in appveyor and local builds - Added function that sets Py_NoSiteFlag to 1. - Added support for Jetson Nano. +- Added support for methodbinding to IEnumerable, ICollection, IList, Task, Action, Func ### Changed diff --git a/src/runtime/ListWrapper.cs b/src/runtime/ListWrapper.cs new file mode 100644 index 000000000..5173be4b0 --- /dev/null +++ b/src/runtime/ListWrapper.cs @@ -0,0 +1,201 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Python.Runtime { + /// + /// Implements IEnumerable for any python iterable. + /// + internal class IterableWrapper : IEnumerable { + private IntPtr iterObject; + + public IterableWrapper(IntPtr value) { + iterObject = Runtime.PyObject_GetIter(value); + if (iterObject == IntPtr.Zero) + Exceptions.RaiseTypeError("not an iterator"); + Runtime.XIncref(iterObject); + } + ~IterableWrapper() { + Runtime.XDecref(iterObject); + } + + public IEnumerator GetEnumerator() { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() { + IntPtr item; + while ((item = Runtime.PyIter_Next(iterObject)) != IntPtr.Zero) { + object obj = null; + if (!Converter.ToManaged(item, typeof(T), out obj, true)) { + Runtime.XDecref(item); + Runtime.XDecref(iterObject); + Exceptions.RaiseTypeError("wrong type in sequence"); + } + + Runtime.XDecref(item); + yield return obj; + } + } + } + + /// + /// Implements IList for any python sequence. + /// Some methods/properties are only available on certaintypes of sequences, like list + /// + internal class ListWrapper : IterableWrapper, IList + { + private IntPtr seq; + public ListWrapper(IntPtr value) : base(value) + { + this.seq = value; + Runtime.XIncref(value); + bool IsSeqObj = Runtime.PySequence_Check(value); + if (!IsSeqObj) + Exceptions.RaiseTypeError("not a sequence"); + + } + ~ListWrapper() + { + Runtime.XDecref(seq); + } + public T this[int index] + { + get + { + IntPtr item = Runtime.PySequence_GetItem(seq, index); + object obj; + + if (!Converter.ToManaged(item, typeof(T), out obj, true)) { + Runtime.XDecref(item); + Exceptions.RaiseTypeError("wrong type in sequence"); + } + + return (T)obj; + } + set + { + IntPtr pyItem = Converter.ToPython(value, typeof(T)); + if (pyItem == IntPtr.Zero) + throw new Exception("failed to set item"); + + var result = Runtime.PySequence_SetItem(seq, index, pyItem); + Runtime.XDecref(pyItem); + if (result == -1) + throw new Exception("failed to set item"); + } + } + + public int Count + { + get + { + var len = Runtime.PySequence_Size(seq); + return (int)len; + } + } + + public bool IsReadOnly + { + get + { + return Runtime.PyTuple_Check(seq); //python tuples are immutable + } + + } + + public void Add(T item) { + if (IsReadOnly) + throw new NotImplementedException(); + + //only implemented if this is a list! + if (!Runtime.PyList_Check(seq)) + throw new NotImplementedException(); + + IntPtr pyItem = Converter.ToPython(item, typeof(T)); + if (pyItem == IntPtr.Zero) + throw new Exception("failed to add item"); + + var result = Runtime.PyList_Append(seq, pyItem); + Runtime.XDecref(pyItem); + if (result == -1) + throw new Exception("failed to add item"); + } + + public void Clear() { + if (IsReadOnly) + throw new NotImplementedException(); + var result = Runtime.PySequence_DelSlice(seq, 0, Count); + if (result == -1) + throw new Exception("failed to clear sequence"); + } + + public bool Contains(T item) + { + //not sure if IEquatable is implemented and this will work! + foreach (var element in this) + if (element.Equals(item)) return true; + + return false; + } + + public void CopyTo(T[] array, int arrayIndex) + { + for (int index = 0; index < Count; index++) + { + array[index + arrayIndex] = this[index]; + } + } + + public int IndexOf(T item) { + var index = 0; + foreach (var element in this) { + if (element.Equals(item)) return index; + index++; + } + + return -1; + } + + public void Insert(int index, T item) + { + if (IsReadOnly) + throw new NotImplementedException(); + + //only implemented if this is a list! + if (!Runtime.PyList_Check(seq)) + throw new NotImplementedException(); + + IntPtr pyItem = Converter.ToPython(item, typeof(T)); + if (pyItem == IntPtr.Zero) + throw new Exception("failed to insert item"); + + var result = Runtime.PyList_Insert(seq, index, pyItem); + Runtime.XDecref(pyItem); + if (result == -1) + throw new Exception("failed to insert item"); + } + + public bool InternalRemoveAt(int index) + { + if (IsReadOnly) + throw new NotImplementedException(); + if (index >= Count || index < 0) + throw new IndexOutOfRangeException(); + + return Runtime.PySequence_DelItem(seq, index) != 0; + } + + public bool Remove(T item) + { + return InternalRemoveAt(IndexOf(item)); + } + + public void RemoveAt(int index) + { + InternalRemoveAt(index); + } + } +} diff --git a/src/runtime/converter.cs b/src/runtime/converter.cs index e7e047419..c96270b81 100644 --- a/src/runtime/converter.cs +++ b/src/runtime/converter.cs @@ -6,6 +6,7 @@ using System.Runtime.InteropServices; using System.Security; using System.ComponentModel; +using System.Linq; namespace Python.Runtime { @@ -268,7 +269,6 @@ internal static IntPtr ToPythonImplicit(object value) return ToPython(value, objectType); } - /// /// Return a managed object for the given Python object, taking funny /// byref types into account. @@ -343,6 +343,23 @@ internal static bool ToManagedValue(IntPtr value, Type obType, return ToArray(value, obType, out result, setError); } + if (obType.IsGenericType) + { + if (obType.GetGenericTypeDefinition() == typeof(IEnumerable<>) || + obType.GetGenericTypeDefinition() == typeof(ICollection<>) || + obType.GetGenericTypeDefinition() == typeof(IList<>)) + { + //We could probably convert to a thin IEnumerable/IList/ICollection implementation around a python array + //but for simplicity right now convert to a List which is a copy of the python iterable. + return ToList(value, obType, out result, setError); + } + } + + if (obType.FullName == "System.Action") + { + return ToAction(value, obType, out result, setError); + } + if (obType.IsEnum) { return ToEnum(value, obType, out result, setError); @@ -847,6 +864,40 @@ private static void SetConversionError(IntPtr value, Type target) Exceptions.SetError(Exceptions.TypeError, $"Cannot convert {src} to {target}"); } + private static bool ToListOfT(IntPtr value, Type elementType, out object result) + { + result = null; + + IntPtr iterObject = Runtime.PyObject_GetIter(value); + if (iterObject == IntPtr.Zero) + return false; + + bool IsSeqObj = Runtime.PySequence_Check(value); + + try { + Type[] typeArgs = { elementType }; + if (IsSeqObj) { + var listType = typeof(ListWrapper<>); + object listObj = Activator.CreateInstance(listType.MakeGenericType(typeArgs), new Object[] { value }); + //IList list = (IList)Activator.CreateInstance(fullListType, new Object[] { value }); + + result = listObj; + } + else { + var listType = typeof(IterableWrapper<>); + IEnumerable list = (IEnumerable)Activator.CreateInstance(listType.MakeGenericType(typeArgs), new Object[] { value }); + result = list; + } + } + catch (Exception) { + return false; + + } + finally { + Runtime.XDecref(iterObject); + } + return true; + } /// /// Convert a Python value to a correctly typed managed array instance. @@ -857,47 +908,81 @@ private static bool ToArray(IntPtr value, Type obType, out object result, bool s { Type elementType = obType.GetElementType(); result = null; + bool success = ToListOfT(value, elementType, out result); + if (!success) + { + if (setError) + SetConversionError(value, obType); + return false; + } - bool IsSeqObj = Runtime.PySequence_Check(value); - var len = IsSeqObj ? Runtime.PySequence_Size(value) : -1; + IList list = result as IList; + if (list == null) { + IEnumerable enumerable = (IEnumerable)result; + var listType = typeof(List<>); + var constructedListType = listType.MakeGenericType(elementType); + list = (IList)Activator.CreateInstance(constructedListType); + foreach (var item in enumerable) { + list.Add(item); + } + } + Array items = Array.CreateInstance(elementType, list.Count); + list.CopyTo(items, 0); + result = items; + return true; + + } - IntPtr IterObject = Runtime.PyObject_GetIter(value); + /// + /// Convert a Python function to a System.Action. + /// The Python value must support the Python "BLAH" protocol. + /// + private static bool ToAction(IntPtr value, Type obType, out object result, bool setError) { - if(IterObject==IntPtr.Zero) { - if (setError) - { + result = null; + + PyObject obj = new PyObject(value); + + var args = obType.GetGenericArguments(); + + //Temporarily only deal with non-generic actions! + if (!obj.IsCallable() || args.Length != 0) { + if (setError) { SetConversionError(value, obType); } return false; } - 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; + Action action = () => { + var y = Runtime.Refcount(value); + PyObject py_action = new PyObject(value); + var py_args = new PyObject[0]; + var py_result = py_action.Invoke(py_args); + Runtime.XDecref(value); + }; + var x = Runtime.Refcount(value); + Runtime.XIncref(value); + result = action; - while ((item = Runtime.PyIter_Next(IterObject)) != IntPtr.Zero) - { - object obj = null; + return true; + } - if (!Converter.ToManaged(item, elementType, out obj, true)) - { - Runtime.XDecref(item); - return false; - } - list.Add(obj); - Runtime.XDecref(item); + /// + /// Convert a Python value to a correctly typed managed list instance. + /// The Python value must support the Python iterator protocol or and the + /// items in the sequence must be convertible to the target array type. + /// TODO - remove duplication with ToArray! + /// + private static bool ToList(IntPtr value, Type obType, out object result, bool setError) { + Type elementType = obType.GetGenericArguments()[0]; + var success = ToListOfT(value, elementType, out result); + if (!success) + { + if (setError) + SetConversionError(value, obType); + return false; } - Runtime.XDecref(IterObject); - - items = Array.CreateInstance(elementType, list.Count); - list.CopyTo(items, 0); - - result = items; return true; } diff --git a/src/testing/methodtest.cs b/src/testing/methodtest.cs index 91836b727..3a61a017c 100644 --- a/src/testing/methodtest.cs +++ b/src/testing/methodtest.cs @@ -117,6 +117,39 @@ public static int[] TestOverloadedParams(int v, int[] args) return args; } + public static int TestIList(System.Collections.Generic.IList arg) + { + return 1; + } + + public static int TestICollection(System.Collections.Generic.ICollection arg) + { + return 1; + } + + public int TestAction(System.Action action) + { + action(); + return 1; + } + + public int TestFuncObj(System.Func func, object arg) + { + return func.Invoke(arg); + + } + + public int TestTask(System.Threading.Tasks.Task task) + { + task.RunSynchronously(); + return task.Result; + } + + public int TestIEnumerable(System.Collections.Generic.IEnumerable arg) + { + return 1; + } + public static string TestOverloadedNoObject(int i) { return "Got int"; diff --git a/src/tests/test_method.py b/src/tests/test_method.py index 34f460d59..2a6352d74 100644 --- a/src/tests/test_method.py +++ b/src/tests/test_method.py @@ -204,6 +204,40 @@ def test_null_array_conversion(): r = ob.TestNullArrayConversion(None) assert r is None +def test_action(): + """Test python lambda as an Action""" + ob = MethodTest() + def func(): + return + r = ob.TestAction(func) + assert r == 1 + +def test_func_object(): + """Test python lambda as a Func""" + ob = MethodTest() + def func(arg): + return arg + 1 + r = ob.TestFunc(func, 0) + assert r == 1 + +def test_task(): + """Test python lambda as a Task""" + ob = MethodTest() + def func(): + return 1 + r = ob.TestTask(func) + assert r == 1 + +def test_ienumerable_args(): + """Test conversion of python lists and tuples to IEnumerable""" + ob = MethodTest() + x = ob.TestIEnumerable([1,2,3]) + y = ob.TestIEnumerable((1,2,3)) + +def test_icollection_args(): + """Test conversion of python lists and tuples to ICollection""" + ob = MethodTest() + x = ob.TestICollection([1,2,3]) def test_string_params_args(): """Test use of string params.""" pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy