From 1d17421739ee822ed40938a99baf756e42ac40aa Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Mon, 23 Nov 2020 17:38:16 -0800 Subject: [PATCH] allow creating new .NET arrays from Python using Array[T](dim1, dim2, ...) syntax fixes https://github.com/pythonnet/pythonnet/issues/251 --- CHANGELOG.md | 2 + src/runtime/BorrowedReference.cs | 5 ++ src/runtime/NewReference.cs | 8 +++ src/runtime/arrayobject.cs | 96 ++++++++++++++++++++++++++++++-- src/runtime/clrobject.cs | 6 +- src/runtime/managedtype.cs | 2 + src/runtime/runtime.cs | 16 +++++- src/tests/test_array.py | 14 +++++ 8 files changed, 143 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63ba315e2..13838e0ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ### Added +- Ability to instantiate new .NET arrays using `Array[T](dim1, dim2, ...)` syntax + ### Changed - Drop support for Python 2, 3.4, and 3.5 - `clr.AddReference` may now throw errors besides `FileNotFoundException`, that provide more diff --git a/src/runtime/BorrowedReference.cs b/src/runtime/BorrowedReference.cs index 8ae382e77..d82763d40 100644 --- a/src/runtime/BorrowedReference.cs +++ b/src/runtime/BorrowedReference.cs @@ -22,5 +22,10 @@ public BorrowedReference(IntPtr pointer) { this.pointer = pointer; } + + public static bool operator ==(BorrowedReference a, BorrowedReference b) + => a.pointer == b.pointer; + public static bool operator !=(BorrowedReference a, BorrowedReference b) + => a.pointer != b.pointer; } } diff --git a/src/runtime/NewReference.cs b/src/runtime/NewReference.cs index 89d53bb36..a4ed75918 100644 --- a/src/runtime/NewReference.cs +++ b/src/runtime/NewReference.cs @@ -28,6 +28,14 @@ public PyObject MoveToPyObject() return result; } + /// Moves ownership of this instance to unmanged pointer + public IntPtr DangerousMoveToPointerOrNull() + { + var result = this.pointer; + this.pointer = IntPtr.Zero; + return result; + } + /// /// Removes this reference to a Python object, and sets it to null. /// diff --git a/src/runtime/arrayobject.cs b/src/runtime/arrayobject.cs index 0db84dd90..e6a4bee19 100644 --- a/src/runtime/arrayobject.cs +++ b/src/runtime/arrayobject.cs @@ -20,21 +20,109 @@ internal override bool CanSubclass() return false; } - public static IntPtr tp_new(IntPtr tp, IntPtr args, IntPtr kw) + public static IntPtr tp_new(IntPtr tpRaw, IntPtr args, IntPtr kw) { + if (kw != IntPtr.Zero) + { + return Exceptions.RaiseTypeError("array constructor takes no keyword arguments"); + } + + var tp = new BorrowedReference(tpRaw); + var self = GetManagedObject(tp) as ArrayObject; - if (Runtime.PyTuple_Size(args) != 1) + + long[] dimensions = new long[Runtime.PyTuple_Size(args)]; + if (dimensions.Length == 0) { - return Exceptions.RaiseTypeError("array expects 1 argument"); + return Exceptions.RaiseTypeError("array constructor requires at least one integer argument or an object convertible to array"); } + if (dimensions.Length != 1) + { + return CreateMultidimensional(self.type.GetElementType(), dimensions, + shapeTuple: new BorrowedReference(args), + pyType: tp) + .DangerousMoveToPointerOrNull(); + } + IntPtr op = Runtime.PyTuple_GetItem(args, 0); + + // create single dimensional array + if (Runtime.PyInt_Check(op)) + { + dimensions[0] = Runtime.PyLong_AsLongLong(op); + if (dimensions[0] == -1 && Exceptions.ErrorOccurred()) + { + Exceptions.Clear(); + } + else + { + return NewInstance(self.type.GetElementType(), tp, dimensions) + .DangerousMoveToPointerOrNull(); + } + } object result; + // this implements casting to Array[T] if (!Converter.ToManaged(op, self.type, out result, true)) { return IntPtr.Zero; } - return CLRObject.GetInstHandle(result, tp); + return CLRObject.GetInstHandle(result, tp) + .DangerousGetAddress(); + } + + static NewReference CreateMultidimensional(Type elementType, long[] dimensions, BorrowedReference shapeTuple, BorrowedReference pyType) + { + for (int dimIndex = 0; dimIndex < dimensions.Length; dimIndex++) + { + BorrowedReference dimObj = Runtime.PyTuple_GetItem(shapeTuple, dimIndex); + PythonException.ThrowIfIsNull(dimObj); + + if (!Runtime.PyInt_Check(dimObj)) + { + Exceptions.RaiseTypeError("array constructor expects integer dimensions"); + return default; + } + + dimensions[dimIndex] = Runtime.PyLong_AsLongLong(dimObj); + if (dimensions[dimIndex] == -1 && Exceptions.ErrorOccurred()) + { + Exceptions.RaiseTypeError("array constructor expects integer dimensions"); + return default; + } + } + + return NewInstance(elementType, pyType, dimensions); + } + + static NewReference NewInstance(Type elementType, BorrowedReference arrayPyType, long[] dimensions) + { + object result; + try + { + result = Array.CreateInstance(elementType, dimensions); + } + catch (ArgumentException badArgument) + { + Exceptions.SetError(Exceptions.ValueError, badArgument.Message); + return default; + } + catch (OverflowException overflow) + { + Exceptions.SetError(overflow); + return default; + } + catch (NotSupportedException notSupported) + { + Exceptions.SetError(notSupported); + return default; + } + catch (OutOfMemoryException oom) + { + Exceptions.SetError(Exceptions.MemoryError, oom.Message); + return default; + } + return CLRObject.GetInstHandle(result, arrayPyType); } diff --git a/src/runtime/clrobject.cs b/src/runtime/clrobject.cs index 0b62fecba..a79662ccc 100644 --- a/src/runtime/clrobject.cs +++ b/src/runtime/clrobject.cs @@ -51,7 +51,11 @@ static CLRObject GetInstance(object ob) return GetInstance(ob, cc.tpHandle); } - + internal static NewReference GetInstHandle(object ob, BorrowedReference pyType) + { + CLRObject co = GetInstance(ob, pyType.DangerousGetAddress()); + return NewReference.DangerousFromPointer(co.pyHandle); + } internal static IntPtr GetInstHandle(object ob, IntPtr pyType) { CLRObject co = GetInstance(ob, pyType); diff --git a/src/runtime/managedtype.cs b/src/runtime/managedtype.cs index bc2805d80..87a89b00a 100644 --- a/src/runtime/managedtype.cs +++ b/src/runtime/managedtype.cs @@ -75,6 +75,8 @@ internal void FreeGCHandle() } } + internal static ManagedType GetManagedObject(BorrowedReference ob) + => GetManagedObject(ob.DangerousGetAddress()); /// /// Given a Python object, return the associated managed object or null. /// diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index a11e9002e..10aa165c8 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -1013,6 +1013,8 @@ internal static unsafe IntPtr PyObject_TYPE(IntPtr op) ? new IntPtr((void*)(*((uint*)p + n))) : new IntPtr((void*)(*((ulong*)p + n))); } + internal static unsafe BorrowedReference PyObject_TYPE(BorrowedReference op) + => new BorrowedReference(PyObject_TYPE(op.DangerousGetAddress())); /// /// Managed version of the standard Python C API PyObject_Type call. @@ -1202,6 +1204,8 @@ internal static long PyObject_Size(IntPtr pointer) [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern bool PyNumber_Check(IntPtr ob); + internal static bool PyInt_Check(BorrowedReference ob) + => PyObject_TypeCheck(ob, new BorrowedReference(PyIntType)); internal static bool PyInt_Check(IntPtr ob) { return PyObject_TypeCheck(ob, PyIntType); @@ -1291,6 +1295,8 @@ internal static object PyLong_AsUnsignedLong(IntPtr value) return PyLong_AsUnsignedLong64(value); } + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern long PyLong_AsLongLong(BorrowedReference value); [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern long PyLong_AsLongLong(IntPtr value); @@ -1829,11 +1835,15 @@ internal static IntPtr PyTuple_New(long size) [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr PyTuple_New(IntPtr size); + internal static BorrowedReference PyTuple_GetItem(BorrowedReference pointer, long index) + => PyTuple_GetItem(pointer, new IntPtr(index)); internal static IntPtr PyTuple_GetItem(IntPtr pointer, long index) { return PyTuple_GetItem(pointer, new IntPtr(index)); } + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] + private static extern BorrowedReference PyTuple_GetItem(BorrowedReference pointer, IntPtr index); [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr PyTuple_GetItem(IntPtr pointer, IntPtr index); @@ -1950,10 +1960,14 @@ internal static bool PyType_Check(IntPtr ob) [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern bool PyType_IsSubtype(IntPtr t1, IntPtr t2); + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern bool PyType_IsSubtype(BorrowedReference t1, BorrowedReference t2); internal static bool PyObject_TypeCheck(IntPtr ob, IntPtr tp) + => PyObject_TypeCheck(new BorrowedReference(ob), new BorrowedReference(tp)); + internal static bool PyObject_TypeCheck(BorrowedReference ob, BorrowedReference tp) { - IntPtr t = PyObject_TYPE(ob); + BorrowedReference t = PyObject_TYPE(ob); return (t == tp) || PyType_IsSubtype(t, tp); } diff --git a/src/tests/test_array.py b/src/tests/test_array.py index 428bb2065..9ab044b29 100644 --- a/src/tests/test_array.py +++ b/src/tests/test_array.py @@ -1174,6 +1174,20 @@ def test_boxed_value_type_mutation_result(): assert items[i].X == i + 1 assert items[i].Y == i + 1 +def test_create_array_from_shape(): + from System import Array + + value = Array[int](3) + assert value[1] == 0 + assert value.Length == 3 + + value = Array[int](3, 4) + assert value[1, 1] == 0 + assert value.GetLength(0) == 3 + assert value.GetLength(1) == 4 + + with pytest.raises(ValueError): + Array[int](-1) def test_special_array_creation(): """Test using the Array[] syntax for creating arrays.""" 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